diff mbox series

[kvm-unit-tests,v2,1/5] s390x: Add specification exception test

Message ID 20211005090921.1816373-2-scgl@linux.ibm.com (mailing list archive)
State New, archived
Headers show
Series [kvm-unit-tests,v2,1/5] s390x: Add specification exception test | expand

Commit Message

Janis Schoetterl-Glausch Oct. 5, 2021, 9:09 a.m. UTC
Generate specification exceptions and check that they occur.
With the iterations argument one can check if specification
exception interpretation occurs, e.g. by using a high value and
checking that the debugfs counters are substantially lower.
The argument is also useful for estimating the performance benefit
of interpretation.

Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
---
 s390x/Makefile      |   1 +
 s390x/spec_ex.c     | 182 ++++++++++++++++++++++++++++++++++++++++++++
 s390x/unittests.cfg |   3 +
 3 files changed, 186 insertions(+)
 create mode 100644 s390x/spec_ex.c

Comments

Janis Schoetterl-Glausch Oct. 5, 2021, 11:14 a.m. UTC | #1
Oops, forgot to Cc the lists on the cover letter, see below.


Test that specification exceptions cause the correct interruption code
during both normal and transactional execution.

The last three patches are cosmetic only and could be dropped.

Unrelated: There should not be * in the file patterns in MAINTAINERS,
should there?

v1 -> v2
	Add license and test description
	Split test patch into normal test and transactional execution test
	Add comments to
		invalid PSW fixup function
		with_transaction
	Rename some variables/functions
	Pass mask as single parameter to asm
	Get rid of report_info_if macro
	Introduce report_pass/fail and use them

Janis Schoetterl-Glausch (5):
  s390x: Add specification exception test
  s390x: Test specification exceptions during transaction
  lib: Introduce report_pass and report_fail
  Use report_fail(...) instead of report(0/false, ...)
  Use report_pass(...) instead of report(1/true, ...)

 s390x/Makefile           |   1 +
 lib/s390x/asm/arch_def.h |   1 +
 lib/libcflat.h           |   6 +-
 lib/report.c             |  20 ++-
 lib/s390x/css_lib.c      |  30 ++--
 x86/vmx.h                |  31 ++--
 arm/psci.c               |   2 +-
 arm/timer.c              |   2 +-
 s390x/css.c              |  22 +--
 s390x/diag288.c          |   2 +-
 s390x/selftest.c         |   2 +-
 s390x/smp.c              |  16 +-
 s390x/spec_ex.c          | 345 +++++++++++++++++++++++++++++++++++++++
 x86/asyncpf.c            |  11 +-
 x86/emulator.c           |   2 +-
 x86/hyperv_stimer.c      |  24 ++-
 x86/hyperv_synic.c       |   2 +-
 x86/svm_tests.c          | 180 ++++++++++----------
 x86/syscall.c            |   2 +-
 x86/taskswitch2.c        |   2 +-
 x86/tsc_adjust.c         |   2 +-
 x86/vmx.c                |  23 ++-
 x86/vmx_tests.c          | 172 ++++++++++---------
 s390x/unittests.cfg      |   3 +
 24 files changed, 630 insertions(+), 273 deletions(-)
 create mode 100644 s390x/spec_ex.c

base-commit: fe26131eec769cef7ad7e2e1e4e192aa0bdb2bba
Janis Schoetterl-Glausch Oct. 5, 2021, 1:32 p.m. UTC | #2
On 10/5/21 1:56 PM, Janosch Frank wrote:
> On 10/5/21 11:09, Janis Schoetterl-Glausch wrote:
>> Generate specification exceptions and check that they occur.
>> With the iterations argument one can check if specification
>> exception interpretation occurs, e.g. by using a high value and
>> checking that the debugfs counters are substantially lower.
>> The argument is also useful for estimating the performance benefit
>> of interpretation.
>>
>> Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
>> ---
>>   s390x/Makefile      |   1 +
>>   s390x/spec_ex.c     | 182 ++++++++++++++++++++++++++++++++++++++++++++
>>   s390x/unittests.cfg |   3 +
>>   3 files changed, 186 insertions(+)
>>   create mode 100644 s390x/spec_ex.c
>>
>> diff --git a/s390x/Makefile b/s390x/Makefile
>> index ef8041a..57d7c9e 100644
>> --- a/s390x/Makefile
>> +++ b/s390x/Makefile
>> @@ -24,6 +24,7 @@ tests += $(TEST_DIR)/mvpg.elf
>>   tests += $(TEST_DIR)/uv-host.elf
>>   tests += $(TEST_DIR)/edat.elf
>>   tests += $(TEST_DIR)/mvpg-sie.elf
>> +tests += $(TEST_DIR)/spec_ex.elf
>>     tests_binary = $(patsubst %.elf,%.bin,$(tests))
>>   ifneq ($(HOST_KEY_DOCUMENT),)
>> diff --git a/s390x/spec_ex.c b/s390x/spec_ex.c
>> new file mode 100644
>> index 0000000..dd0ee53
>> --- /dev/null
>> +++ b/s390x/spec_ex.c
>> @@ -0,0 +1,182 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * © Copyright IBM Corp. 2021
>> + *
>> + * Specification exception test.
>> + * Tests that specification exceptions occur when expected.
>> + */
>> +#include <stdlib.h>
>> +#include <libcflat.h>
>> +#include <asm/interrupt.h>
>> +#include <asm/facility.h>
>> +
>> +static struct lowcore *lc = (struct lowcore *) 0;
>> +
>> +static bool expect_invalid_psw;
>> +static struct psw expected_psw;
>> +static struct psw fixup_psw;
>> +
>> +/* The standard program exception handler cannot deal with invalid old PSWs,
>> + * especially not invalid instruction addresses, as in that case one cannot
>> + * find the instruction following the faulting one from the old PSW.
>> + * The PSW to return to is set by load_psw.
>> + */
>> +static void fixup_invalid_psw(void)
>> +{
>> +    if (expect_invalid_psw) {
>> +        report(expected_psw.mask == lc->pgm_old_psw.mask
>> +               && expected_psw.addr == lc->pgm_old_psw.addr,
>> +               "Invalid program new PSW as expected");
>> +        expect_invalid_psw = false;
>> +    }
>> +    lc->pgm_old_psw = fixup_psw;
>> +}
>> +
>> +static void load_psw(struct psw psw)
>> +{
>> +    uint64_t r0 = 0, r1 = 0;
>> +
>> +    asm volatile (
>> +        "    epsw    %0,%1\n"
>> +        "    st    %0,%[mask]\n"
>> +        "    st    %1,4+%[mask]\n"
> 
> You're grabbing the mask for the fixup psw, right?

Yes

> Why don't you use the extract_psw_mask() function for that?

No reason, sounds like a good idea to use the function.
> 
> Also I'd recommend not mixing named operands and numeric operands, especially when the variables are then called r0 and r1.

I suppose I didn't name them because they're just scratch registers.
But using extract_psw_mask() will get rid of them anyway
> 
>> +        "    larl    %0,nop%=\n"
>> +        "    stg    %0,%[addr]\n"
> 
> This stores the address of the nop to the fixup psw addr.
> So far so good, but why is it only called "addr"?
> 
>> +        "    lpswe    %[psw]\n"
>> +        "nop%=:    nop\n"
>> +        : "+&r"(r0), "+&a"(r1), [mask] "=&R"(fixup_psw.mask),
>> +          [addr] "=&R"(fixup_psw.addr)
>> +        : [psw] "Q"(psw)
>> +        : "cc", "memory"
>> +    );
> 
> You made this a bit complicated and didn't document it.
> /*
>  * Setup fixup_psw before loading an invalid PSW so that *fixup_invalid_psw() can bring us back onto the right track.
>  */
> >> +}
>> +
>> +static void psw_bit_12_is_1(void)
>> +{
>> +    expected_psw.mask = 0x0008000000000000;
>> +    expected_psw.addr = 0x00000000deadbeee;
>> +    expect_invalid_psw = true;
>> +    load_psw(expected_psw);
>> +}
>> +

[...]
Thomas Huth Oct. 5, 2021, 2:51 p.m. UTC | #3
On 05/10/2021 11.09, Janis Schoetterl-Glausch wrote:
> Generate specification exceptions and check that they occur.
> With the iterations argument one can check if specification
> exception interpretation occurs, e.g. by using a high value and
> checking that the debugfs counters are substantially lower.
> The argument is also useful for estimating the performance benefit
> of interpretation.
> 
> Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
> ---
>   s390x/Makefile      |   1 +
>   s390x/spec_ex.c     | 182 ++++++++++++++++++++++++++++++++++++++++++++
>   s390x/unittests.cfg |   3 +
>   3 files changed, 186 insertions(+)
>   create mode 100644 s390x/spec_ex.c
> 
> diff --git a/s390x/Makefile b/s390x/Makefile
> index ef8041a..57d7c9e 100644
> --- a/s390x/Makefile
> +++ b/s390x/Makefile
> @@ -24,6 +24,7 @@ tests += $(TEST_DIR)/mvpg.elf
>   tests += $(TEST_DIR)/uv-host.elf
>   tests += $(TEST_DIR)/edat.elf
>   tests += $(TEST_DIR)/mvpg-sie.elf
> +tests += $(TEST_DIR)/spec_ex.elf
>   
>   tests_binary = $(patsubst %.elf,%.bin,$(tests))
>   ifneq ($(HOST_KEY_DOCUMENT),)
> diff --git a/s390x/spec_ex.c b/s390x/spec_ex.c
> new file mode 100644
> index 0000000..dd0ee53
> --- /dev/null
> +++ b/s390x/spec_ex.c
> @@ -0,0 +1,182 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * © Copyright IBM Corp. 2021

Could we please avoid non-ASCII characters in source code if possible? ... 
it's maybe best if you do the Copyright line similar to the other *.c files 
from IBM that are already in the repository.

> + * Specification exception test.
> + * Tests that specification exceptions occur when expected.
> + */
> +#include <stdlib.h>
> +#include <libcflat.h>
> +#include <asm/interrupt.h>
> +#include <asm/facility.h>
> +
> +static struct lowcore *lc = (struct lowcore *) 0;
> +
> +static bool expect_invalid_psw;
> +static struct psw expected_psw;
> +static struct psw fixup_psw;
> +
> +/* The standard program exception handler cannot deal with invalid old PSWs,
> + * especially not invalid instruction addresses, as in that case one cannot
> + * find the instruction following the faulting one from the old PSW.
> + * The PSW to return to is set by load_psw.
> + */
> +static void fixup_invalid_psw(void)
> +{
> +	if (expect_invalid_psw) {
> +		report(expected_psw.mask == lc->pgm_old_psw.mask
> +		       && expected_psw.addr == lc->pgm_old_psw.addr,
> +		       "Invalid program new PSW as expected");
> +		expect_invalid_psw = false;
> +	}
> +	lc->pgm_old_psw = fixup_psw;
> +}
> +
> +static void load_psw(struct psw psw)
> +{
> +	uint64_t r0 = 0, r1 = 0;
> +
> +	asm volatile (
> +		"	epsw	%0,%1\n"
> +		"	st	%0,%[mask]\n"
> +		"	st	%1,4+%[mask]\n"
> +		"	larl	%0,nop%=\n"
> +		"	stg	%0,%[addr]\n"
> +		"	lpswe	%[psw]\n"
> +		"nop%=:	nop\n"
> +		: "+&r"(r0), "+&a"(r1), [mask] "=&R"(fixup_psw.mask),
> +		  [addr] "=&R"(fixup_psw.addr)

stg uses long displacement, so maybe the constraint should rather be "T" 
instead?

> +		: [psw] "Q"(psw)
> +		: "cc", "memory"
> +	);
> +}
> +
> +static void psw_bit_12_is_1(void)
> +{
> +	expected_psw.mask = 0x0008000000000000;
> +	expected_psw.addr = 0x00000000deadbeee;
> +	expect_invalid_psw = true;
> +	load_psw(expected_psw);
> +}
> +
> +static void bad_alignment(void)
> +{
> +	uint32_t words[5] = {0, 0, 0};
> +	uint32_t (*bad_aligned)[4];
> +
> +	register uint64_t r1 asm("6");
> +	register uint64_t r2 asm("7");
> +	if (((uintptr_t)&words[0]) & 0xf)
> +		bad_aligned = (uint32_t (*)[4])&words[0];
> +	else
> +		bad_aligned = (uint32_t (*)[4])&words[1];
> +	asm volatile ("lpq %0,%2"
> +		      : "=r"(r1), "=r"(r2)
> +		      : "T"(*bad_aligned)
> +	);
> +}
> +
> +static void not_even(void)
> +{
> +	uint64_t quad[2];
> +
> +	register uint64_t r1 asm("7");
> +	register uint64_t r2 asm("8");
> +	asm volatile (".insn	rxy,0xe3000000008f,%0,%2" //lpq %0,%2
> +		      : "=r"(r1), "=r"(r2)
> +		      : "T"(quad)
> +	);
> +}
> +
> +struct spec_ex_trigger {
> +	const char *name;
> +	void (*func)(void);
> +	void (*fixup)(void);
> +};
> +
> +static const struct spec_ex_trigger spec_ex_triggers[] = {
> +	{ "psw_bit_12_is_1", &psw_bit_12_is_1, &fixup_invalid_psw},
> +	{ "bad_alignment", &bad_alignment, NULL},
> +	{ "not_even", &not_even, NULL},
> +	{ NULL, NULL, NULL},
> +};
> +
> +struct args {
> +	uint64_t iterations;
> +};
> +
> +static void test_spec_ex(struct args *args,
> +			 const struct spec_ex_trigger *trigger)
> +{
> +	uint16_t expected_pgm = PGM_INT_CODE_SPECIFICATION;
> +	uint16_t pgm;
> +	unsigned int i;
> +
> +	for (i = 0; i < args->iterations; i++) {
> +		expect_pgm_int();
> +		register_pgm_cleanup_func(trigger->fixup);
> +		trigger->func();
> +		register_pgm_cleanup_func(NULL);
> +		pgm = clear_pgm_int();
> +		if (pgm != expected_pgm) {
> +			report(0,
> +			       "Program interrupt: expected(%d) == received(%d)",
> +			       expected_pgm,
> +			       pgm);
> +			return;
> +		}
> +	}
> +	report(1,
> +	       "Program interrupt: always expected(%d) == received(%d)",
> +	       expected_pgm,
> +	       expected_pgm);
> +}
> +
> +static struct args parse_args(int argc, char **argv)
> +{
> +	struct args args = {
> +		.iterations = 1,
> +	};
> +	unsigned int i;
> +	long arg;
> +	bool no_arg;
> +	char *end;
> +
> +	for (i = 1; i < argc; i++) {
> +		no_arg = true;
> +		if (i < argc - 1) {
> +			no_arg = *argv[i+1] == '\0';
> +			arg = strtol(argv[i+1], &end, 10);

Nit: It's more common to use spaces around the "+" (i.e. "i + 1")

> +			no_arg |= *end != '\0';
> +			no_arg |= arg < 0;
> +		}
> +
> +		if (!strcmp("--iterations", argv[i])) {
> +			if (no_arg)
> +				report_abort("--iterations needs a positive parameter");
> +			args.iterations = arg;
> +			++i;
> +		} else {
> +			report_abort("Unsupported parameter '%s'",
> +				     argv[i]);
> +		}
> +	}
> +	return args;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	unsigned int i;
> +
> +	struct args args = parse_args(argc, argv);
> +
> +	report_prefix_push("specification exception");
> +	for (i = 0; spec_ex_triggers[i].name; i++) {
> +		report_prefix_push(spec_ex_triggers[i].name);
> +		test_spec_ex(&args, &spec_ex_triggers[i]);
> +		report_prefix_pop();
> +	}
> +	report_prefix_pop();
> +
> +	return report_summary();
> +}

Apart from the nits, this looks fine to me.

  Thomas
Janis Schoetterl-Glausch Oct. 5, 2021, 4:14 p.m. UTC | #4
On 10/5/21 4:51 PM, Thomas Huth wrote:
> On 05/10/2021 11.09, Janis Schoetterl-Glausch wrote:
>> Generate specification exceptions and check that they occur.
>> With the iterations argument one can check if specification
>> exception interpretation occurs, e.g. by using a high value and
>> checking that the debugfs counters are substantially lower.
>> The argument is also useful for estimating the performance benefit
>> of interpretation.
>>
>> Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
>> ---
>>   s390x/Makefile      |   1 +
>>   s390x/spec_ex.c     | 182 ++++++++++++++++++++++++++++++++++++++++++++
>>   s390x/unittests.cfg |   3 +
>>   3 files changed, 186 insertions(+)
>>   create mode 100644 s390x/spec_ex.c
>>
>> diff --git a/s390x/Makefile b/s390x/Makefile
>> index ef8041a..57d7c9e 100644
>> --- a/s390x/Makefile
>> +++ b/s390x/Makefile
>> @@ -24,6 +24,7 @@ tests += $(TEST_DIR)/mvpg.elf
>>   tests += $(TEST_DIR)/uv-host.elf
>>   tests += $(TEST_DIR)/edat.elf
>>   tests += $(TEST_DIR)/mvpg-sie.elf
>> +tests += $(TEST_DIR)/spec_ex.elf
>>     tests_binary = $(patsubst %.elf,%.bin,$(tests))
>>   ifneq ($(HOST_KEY_DOCUMENT),)
>> diff --git a/s390x/spec_ex.c b/s390x/spec_ex.c
>> new file mode 100644
>> index 0000000..dd0ee53
>> --- /dev/null
>> +++ b/s390x/spec_ex.c
>> @@ -0,0 +1,182 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * © Copyright IBM Corp. 2021
> 
> Could we please avoid non-ASCII characters in source code if possible? ... it's maybe best if you do the Copyright line similar to the other *.c files from IBM that are already in the repository.

Yes, I'll remove it. I thought it would be fine since it's in a comment,
didn't consider that it might cause trouble with some mail clients.
So that's grounds for removal by itself.
> 
>> + * Specification exception test.
>> + * Tests that specification exceptions occur when expected.
>> + */
>> +#include <stdlib.h>
>> +#include <libcflat.h>
>> +#include <asm/interrupt.h>
>> +#include <asm/facility.h>
>> +
>> +static struct lowcore *lc = (struct lowcore *) 0;
>> +
>> +static bool expect_invalid_psw;
>> +static struct psw expected_psw;
>> +static struct psw fixup_psw;
>> +
>> +/* The standard program exception handler cannot deal with invalid old PSWs,
>> + * especially not invalid instruction addresses, as in that case one cannot
>> + * find the instruction following the faulting one from the old PSW.
>> + * The PSW to return to is set by load_psw.
>> + */
>> +static void fixup_invalid_psw(void)
>> +{
>> +    if (expect_invalid_psw) {
>> +        report(expected_psw.mask == lc->pgm_old_psw.mask
>> +               && expected_psw.addr == lc->pgm_old_psw.addr,
>> +               "Invalid program new PSW as expected");
>> +        expect_invalid_psw = false;
>> +    }
>> +    lc->pgm_old_psw = fixup_psw;
>> +}
>> +
>> +static void load_psw(struct psw psw)
>> +{
>> +    uint64_t r0 = 0, r1 = 0;
>> +
>> +    asm volatile (
>> +        "    epsw    %0,%1\n"
>> +        "    st    %0,%[mask]\n"
>> +        "    st    %1,4+%[mask]\n"
>> +        "    larl    %0,nop%=\n"
>> +        "    stg    %0,%[addr]\n"
>> +        "    lpswe    %[psw]\n"
>> +        "nop%=:    nop\n"
>> +        : "+&r"(r0), "+&a"(r1), [mask] "=&R"(fixup_psw.mask),
>> +          [addr] "=&R"(fixup_psw.addr)
> 
> stg uses long displacement, so maybe the constraint should rather be "T" instead?

Good catch.
> 
>> +        : [psw] "Q"(psw)
>> +        : "cc", "memory"
>> +    );
>> +}
>> +

[...]

>> +static struct args parse_args(int argc, char **argv)
>> +{
>> +    struct args args = {
>> +        .iterations = 1,
>> +    };
>> +    unsigned int i;
>> +    long arg;
>> +    bool no_arg;
>> +    char *end;
>> +
>> +    for (i = 1; i < argc; i++) {
>> +        no_arg = true;
>> +        if (i < argc - 1) {
>> +            no_arg = *argv[i+1] == '\0';
>> +            arg = strtol(argv[i+1], &end, 10);
> 
> Nit: It's more common to use spaces around the "+" (i.e. "i + 1")

Ok
> 
>> +            no_arg |= *end != '\0';
>> +            no_arg |= arg < 0;
>> +        }
>> +
>> +        if (!strcmp("--iterations", argv[i])) {
>> +            if (no_arg)
>> +                report_abort("--iterations needs a positive parameter");
>> +            args.iterations = arg;
>> +            ++i;
>> +        } else {
>> +            report_abort("Unsupported parameter '%s'",
>> +                     argv[i]);
>> +        }
>> +    }
>> +    return args;
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> +    unsigned int i;
>> +
>> +    struct args args = parse_args(argc, argv);
>> +
>> +    report_prefix_push("specification exception");
>> +    for (i = 0; spec_ex_triggers[i].name; i++) {
>> +        report_prefix_push(spec_ex_triggers[i].name);
>> +        test_spec_ex(&args, &spec_ex_triggers[i]);
>> +        report_prefix_pop();
>> +    }
>> +    report_prefix_pop();
>> +
>> +    return report_summary();
>> +}
> 
> Apart from the nits, this looks fine to me.

Thanks for the review.
> 
>  Thomas
>
diff mbox series

Patch

diff --git a/s390x/Makefile b/s390x/Makefile
index ef8041a..57d7c9e 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -24,6 +24,7 @@  tests += $(TEST_DIR)/mvpg.elf
 tests += $(TEST_DIR)/uv-host.elf
 tests += $(TEST_DIR)/edat.elf
 tests += $(TEST_DIR)/mvpg-sie.elf
+tests += $(TEST_DIR)/spec_ex.elf
 
 tests_binary = $(patsubst %.elf,%.bin,$(tests))
 ifneq ($(HOST_KEY_DOCUMENT),)
diff --git a/s390x/spec_ex.c b/s390x/spec_ex.c
new file mode 100644
index 0000000..dd0ee53
--- /dev/null
+++ b/s390x/spec_ex.c
@@ -0,0 +1,182 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * © Copyright IBM Corp. 2021
+ *
+ * Specification exception test.
+ * Tests that specification exceptions occur when expected.
+ */
+#include <stdlib.h>
+#include <libcflat.h>
+#include <asm/interrupt.h>
+#include <asm/facility.h>
+
+static struct lowcore *lc = (struct lowcore *) 0;
+
+static bool expect_invalid_psw;
+static struct psw expected_psw;
+static struct psw fixup_psw;
+
+/* The standard program exception handler cannot deal with invalid old PSWs,
+ * especially not invalid instruction addresses, as in that case one cannot
+ * find the instruction following the faulting one from the old PSW.
+ * The PSW to return to is set by load_psw.
+ */
+static void fixup_invalid_psw(void)
+{
+	if (expect_invalid_psw) {
+		report(expected_psw.mask == lc->pgm_old_psw.mask
+		       && expected_psw.addr == lc->pgm_old_psw.addr,
+		       "Invalid program new PSW as expected");
+		expect_invalid_psw = false;
+	}
+	lc->pgm_old_psw = fixup_psw;
+}
+
+static void load_psw(struct psw psw)
+{
+	uint64_t r0 = 0, r1 = 0;
+
+	asm volatile (
+		"	epsw	%0,%1\n"
+		"	st	%0,%[mask]\n"
+		"	st	%1,4+%[mask]\n"
+		"	larl	%0,nop%=\n"
+		"	stg	%0,%[addr]\n"
+		"	lpswe	%[psw]\n"
+		"nop%=:	nop\n"
+		: "+&r"(r0), "+&a"(r1), [mask] "=&R"(fixup_psw.mask),
+		  [addr] "=&R"(fixup_psw.addr)
+		: [psw] "Q"(psw)
+		: "cc", "memory"
+	);
+}
+
+static void psw_bit_12_is_1(void)
+{
+	expected_psw.mask = 0x0008000000000000;
+	expected_psw.addr = 0x00000000deadbeee;
+	expect_invalid_psw = true;
+	load_psw(expected_psw);
+}
+
+static void bad_alignment(void)
+{
+	uint32_t words[5] = {0, 0, 0};
+	uint32_t (*bad_aligned)[4];
+
+	register uint64_t r1 asm("6");
+	register uint64_t r2 asm("7");
+	if (((uintptr_t)&words[0]) & 0xf)
+		bad_aligned = (uint32_t (*)[4])&words[0];
+	else
+		bad_aligned = (uint32_t (*)[4])&words[1];
+	asm volatile ("lpq %0,%2"
+		      : "=r"(r1), "=r"(r2)
+		      : "T"(*bad_aligned)
+	);
+}
+
+static void not_even(void)
+{
+	uint64_t quad[2];
+
+	register uint64_t r1 asm("7");
+	register uint64_t r2 asm("8");
+	asm volatile (".insn	rxy,0xe3000000008f,%0,%2" //lpq %0,%2
+		      : "=r"(r1), "=r"(r2)
+		      : "T"(quad)
+	);
+}
+
+struct spec_ex_trigger {
+	const char *name;
+	void (*func)(void);
+	void (*fixup)(void);
+};
+
+static const struct spec_ex_trigger spec_ex_triggers[] = {
+	{ "psw_bit_12_is_1", &psw_bit_12_is_1, &fixup_invalid_psw},
+	{ "bad_alignment", &bad_alignment, NULL},
+	{ "not_even", &not_even, NULL},
+	{ NULL, NULL, NULL},
+};
+
+struct args {
+	uint64_t iterations;
+};
+
+static void test_spec_ex(struct args *args,
+			 const struct spec_ex_trigger *trigger)
+{
+	uint16_t expected_pgm = PGM_INT_CODE_SPECIFICATION;
+	uint16_t pgm;
+	unsigned int i;
+
+	for (i = 0; i < args->iterations; i++) {
+		expect_pgm_int();
+		register_pgm_cleanup_func(trigger->fixup);
+		trigger->func();
+		register_pgm_cleanup_func(NULL);
+		pgm = clear_pgm_int();
+		if (pgm != expected_pgm) {
+			report(0,
+			       "Program interrupt: expected(%d) == received(%d)",
+			       expected_pgm,
+			       pgm);
+			return;
+		}
+	}
+	report(1,
+	       "Program interrupt: always expected(%d) == received(%d)",
+	       expected_pgm,
+	       expected_pgm);
+}
+
+static struct args parse_args(int argc, char **argv)
+{
+	struct args args = {
+		.iterations = 1,
+	};
+	unsigned int i;
+	long arg;
+	bool no_arg;
+	char *end;
+
+	for (i = 1; i < argc; i++) {
+		no_arg = true;
+		if (i < argc - 1) {
+			no_arg = *argv[i+1] == '\0';
+			arg = strtol(argv[i+1], &end, 10);
+			no_arg |= *end != '\0';
+			no_arg |= arg < 0;
+		}
+
+		if (!strcmp("--iterations", argv[i])) {
+			if (no_arg)
+				report_abort("--iterations needs a positive parameter");
+			args.iterations = arg;
+			++i;
+		} else {
+			report_abort("Unsupported parameter '%s'",
+				     argv[i]);
+		}
+	}
+	return args;
+}
+
+int main(int argc, char **argv)
+{
+	unsigned int i;
+
+	struct args args = parse_args(argc, argv);
+
+	report_prefix_push("specification exception");
+	for (i = 0; spec_ex_triggers[i].name; i++) {
+		report_prefix_push(spec_ex_triggers[i].name);
+		test_spec_ex(&args, &spec_ex_triggers[i]);
+		report_prefix_pop();
+	}
+	report_prefix_pop();
+
+	return report_summary();
+}
diff --git a/s390x/unittests.cfg b/s390x/unittests.cfg
index 9e1802f..5f43d52 100644
--- a/s390x/unittests.cfg
+++ b/s390x/unittests.cfg
@@ -109,3 +109,6 @@  file = edat.elf
 
 [mvpg-sie]
 file = mvpg-sie.elf
+
+[spec_ex]
+file = spec_ex.elf