diff mbox

[kvm-unit-tests,v2,2/2] s390x: pgm interrupt handler and a way to test them

Message ID 20170531123925.4547-3-david@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

David Hildenbrand May 31, 2017, 12:39 p.m. UTC
The program interrupt handler will detect unexpected program interrupts and
allow to expect + verify program interrupts for testing purposes.

We need "-fno-delete-null-pointer-checks", otherwise trying to access the
lowcore at address 0 makes GCC generate very weird code.

Add two tests to test for simple operation and addressing exceptions.

Signed-off-by: David Hildenbrand <david@redhat.com>
---
 lib/s390x/asm-offsets.c   |  3 ++
 lib/s390x/asm/arch_def.h  | 61 ++++++++++++++++++++++++++++++++++-
 lib/s390x/asm/interrupt.h | 18 +++++++++++
 lib/s390x/interrupt.c     | 82 +++++++++++++++++++++++++++++++++++++++++++++++
 s390x/Makefile            |  2 ++
 s390x/cstart64.S          | 55 +++++++++++++++++++++++++++++++
 s390x/selftest.c          | 13 ++++++++
 7 files changed, 233 insertions(+), 1 deletion(-)
 create mode 100644 lib/s390x/asm/interrupt.h
 create mode 100644 lib/s390x/interrupt.c

Comments

Paolo Bonzini May 31, 2017, 12:51 p.m. UTC | #1
On 31/05/2017 14:39, David Hildenbrand wrote:
> +	expect_pgm_int();
> +	*((unsigned int*)-1) = 1;
> +	check_pgm_int_code(PGM_INT_CODE_ADDRESSING);

QEMU gets

ABORT: selftest: Unexpected program interrupt: 5 at 0x12b4c, ilen 4

It seems that QEMU doesn't point the PSW at the next instruction:

	IN: main
	0x0000000000012b44:  lghi       %r1,-1
	0x0000000000012b48:  lhi        %r3,1
	0x0000000000012b4c:  st %r3,0(%r1)
	0x0000000000012b50:  lghi       %r2,5
	0x0000000000012b54:  brasl      %r14,0x128e8
	0x0000000000012b4c:  st %r3,0(%r1)
	0x0000000000012b50:  lghi       %r2,5
	0x0000000000012b54:  brasl      %r14,0x128e8     # this is expect_pgm_int

	Trace 0x7f5ff6c7f520 [0: 0000000000012b44] main
	Trace 0x7f5ff6c7bc20 [0: 000000000001004e] 
	Trace 0x7f5ff6c7c960 [0: 0000000000012918] handle_pgm_int
	Trace 0x7f5ff6c7d280 [0: 00000000000100a4] 

and now it gets another program interrupt:

	IN: main
	0x0000000000012b4c:  st %r3,0(%r1)
	0x0000000000012b50:  lghi       %r2,5
	0x0000000000012b54:  brasl      %r14,0x128e8

	Trace 0x7f5ff6c7f600 [0: 0000000000012b4c] main
	Trace 0x7f5ff6c7bc20 [0: 000000000001004e] 
	Trace 0x7f5ff6c7c960 [0: 0000000000012918] handle_pgm_int

Thanks,

Paolo
Thomas Huth May 31, 2017, 1:22 p.m. UTC | #2
On 31.05.2017 14:39, David Hildenbrand wrote:
> The program interrupt handler will detect unexpected program interrupts and
> allow to expect + verify program interrupts for testing purposes.
> 
> We need "-fno-delete-null-pointer-checks", otherwise trying to access the
> lowcore at address 0 makes GCC generate very weird code.
> 
> Add two tests to test for simple operation and addressing exceptions.
> 
> Signed-off-by: David Hildenbrand <david@redhat.com>
> ---
[...]
> diff --git a/lib/s390x/interrupt.c b/lib/s390x/interrupt.c
> new file mode 100644
> index 0000000..8d861a2
> --- /dev/null
> +++ b/lib/s390x/interrupt.c
> @@ -0,0 +1,82 @@
> +/*
> + * s390x interrupt handling
> + *
> + * Copyright (c) 2017 Red Hat Inc
> + *
> + * Authors:
> + *  David Hildenbrand <david@redhat.com>
> + *
> + * This code is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU Library General Public License version 2.
> + */
> +#include <libcflat.h>
> +#include <asm/interrupt.h>
> +#include <asm/barrier.h>
> +
> +static bool pgm_int_expected;
> +static struct lowcore *lc;

Cosmetic suggestion:
Maybe make it "static struct lowcore const *lc = 0" to be more explicit?

Apart from that, patch looks fine to me now, so with or without that change:

Reviewed-by: Thomas Huth <thuth@redhat.com>
Thomas Huth May 31, 2017, 1:25 p.m. UTC | #3
On 31.05.2017 14:51, Paolo Bonzini wrote:
> 
> 
> On 31/05/2017 14:39, David Hildenbrand wrote:
>> +	expect_pgm_int();
>> +	*((unsigned int*)-1) = 1;
>> +	check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
> 
> QEMU gets
> 
> ABORT: selftest: Unexpected program interrupt: 5 at 0x12b4c, ilen 4
> 
> It seems that QEMU doesn't point the PSW at the next instruction

David mentioned it in the cover letter ... you likely need this patch
here first:

http://patchwork.ozlabs.org/patch/768173/

 Thomas
David Hildenbrand May 31, 2017, 1:26 p.m. UTC | #4
On 31.05.2017 14:51, Paolo Bonzini wrote:
> 
> 
> On 31/05/2017 14:39, David Hildenbrand wrote:
>> +	expect_pgm_int();
>> +	*((unsigned int*)-1) = 1;
>> +	check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
> 
> QEMU gets
> 
> ABORT: selftest: Unexpected program interrupt: 5 at 0x12b4c, ilen 4
> 
> It seems that QEMU doesn't point the PSW at the next instruction:
> 
> 	IN: main
> 	0x0000000000012b44:  lghi       %r1,-1
> 	0x0000000000012b48:  lhi        %r3,1
> 	0x0000000000012b4c:  st %r3,0(%r1)
> 	0x0000000000012b50:  lghi       %r2,5
> 	0x0000000000012b54:  brasl      %r14,0x128e8
> 	0x0000000000012b4c:  st %r3,0(%r1)
> 	0x0000000000012b50:  lghi       %r2,5
> 	0x0000000000012b54:  brasl      %r14,0x128e8     # this is expect_pgm_int
> 
> 	Trace 0x7f5ff6c7f520 [0: 0000000000012b44] main
> 	Trace 0x7f5ff6c7bc20 [0: 000000000001004e] 
> 	Trace 0x7f5ff6c7c960 [0: 0000000000012918] handle_pgm_int
> 	Trace 0x7f5ff6c7d280 [0: 00000000000100a4] 
> 
> and now it gets another program interrupt:

Exactly, the emulator doesn't properly forward the PSW.

> 
> 	IN: main
> 	0x0000000000012b4c:  st %r3,0(%r1)
> 	0x0000000000012b50:  lghi       %r2,5
> 	0x0000000000012b54:  brasl      %r14,0x128e8
> 
> 	Trace 0x7f5ff6c7f600 [0: 0000000000012b4c] main
> 	Trace 0x7f5ff6c7bc20 [0: 000000000001004e] 
> 	Trace 0x7f5ff6c7c960 [0: 0000000000012918] handle_pgm_int
> 
> Thanks,
> 
> Paolo
> 

Yes, known problem as described in the cover letter. Happens with
current TCG and old KVM. Patch for TCG is on the list.

Thanks for testing!
David Hildenbrand May 31, 2017, 1:30 p.m. UTC | #5
On 31.05.2017 15:22, Thomas Huth wrote:
> On 31.05.2017 14:39, David Hildenbrand wrote:
>> The program interrupt handler will detect unexpected program interrupts and
>> allow to expect + verify program interrupts for testing purposes.
>>
>> We need "-fno-delete-null-pointer-checks", otherwise trying to access the
>> lowcore at address 0 makes GCC generate very weird code.
>>
>> Add two tests to test for simple operation and addressing exceptions.
>>
>> Signed-off-by: David Hildenbrand <david@redhat.com>
>> ---
> [...]
>> diff --git a/lib/s390x/interrupt.c b/lib/s390x/interrupt.c
>> new file mode 100644
>> index 0000000..8d861a2
>> --- /dev/null
>> +++ b/lib/s390x/interrupt.c
>> @@ -0,0 +1,82 @@
>> +/*
>> + * s390x interrupt handling
>> + *
>> + * Copyright (c) 2017 Red Hat Inc
>> + *
>> + * Authors:
>> + *  David Hildenbrand <david@redhat.com>
>> + *
>> + * This code is free software; you can redistribute it and/or modify it
>> + * under the terms of the GNU Library General Public License version 2.
>> + */
>> +#include <libcflat.h>
>> +#include <asm/interrupt.h>
>> +#include <asm/barrier.h>
>> +
>> +static bool pgm_int_expected;
>> +static struct lowcore *lc;
> 
> Cosmetic suggestion:
> Maybe make it "static struct lowcore const *lc = 0" to be more explicit?

You most probably mean
static struct lowcore *const lc = 0;

(otherwise it won't compile)

makes sense. If I have to resend, I'll add it.

> 
> Apart from that, patch looks fine to me now, so with or without that change:
> 
> Reviewed-by: Thomas Huth <thuth@redhat.com>
> 

Thanks!
Paolo Bonzini May 31, 2017, 2:23 p.m. UTC | #6
On 31/05/2017 15:25, Thomas Huth wrote:
> On 31.05.2017 14:51, Paolo Bonzini wrote:
>>
>>
>> On 31/05/2017 14:39, David Hildenbrand wrote:
>>> +	expect_pgm_int();
>>> +	*((unsigned int*)-1) = 1;
>>> +	check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
>>
>> QEMU gets
>>
>> ABORT: selftest: Unexpected program interrupt: 5 at 0x12b4c, ilen 4
>>
>> It seems that QEMU doesn't point the PSW at the next instruction
> 
> David mentioned it in the cover letter ... you likely need this patch
> here first:
> 
> http://patchwork.ozlabs.org/patch/768173/
> 
>  Thomas
> 

That helps indeed. :)

Paolo
diff mbox

Patch

diff --git a/lib/s390x/asm-offsets.c b/lib/s390x/asm-offsets.c
index 1e8d5ce..f1012c6 100644
--- a/lib/s390x/asm-offsets.c
+++ b/lib/s390x/asm-offsets.c
@@ -54,6 +54,9 @@  int main(void)
 	OFFSET(GEN_LC_PGM_NEW_PSW, lowcore, pgm_new_psw);
 	OFFSET(GEN_LC_MCCK_NEW_PSW, lowcore, mcck_new_psw);
 	OFFSET(GEN_LC_IO_NEW_PSW, lowcore, io_new_psw);
+	OFFSET(GEN_LC_SW_INT_GRS, lowcore, sw_int_grs);
+	OFFSET(GEN_LC_SW_INT_FPRS, lowcore, sw_int_fprs);
+	OFFSET(GEN_LC_SW_INT_FPC, lowcore, sw_int_fpc);
 	OFFSET(GEN_LC_MCCK_EXT_SA_ADDR, lowcore, mcck_ext_sa_addr);
 	OFFSET(GEN_LC_FPRS_SA, lowcore, fprs_sa);
 	OFFSET(GEN_LC_GRS_SA, lowcore, grs_sa);
diff --git a/lib/s390x/asm/arch_def.h b/lib/s390x/asm/arch_def.h
index efa7f12..07d467e 100644
--- a/lib/s390x/asm/arch_def.h
+++ b/lib/s390x/asm/arch_def.h
@@ -64,7 +64,11 @@  struct lowcore {
 	struct psw	pgm_new_psw;			/* 0x01d0 */
 	struct psw	mcck_new_psw;			/* 0x01e0 */
 	struct psw	io_new_psw;			/* 0x01f0 */
-	uint8_t		pad_0x0200[0x11b0 - 0x0200];	/* 0x0200 */
+	/* sw definition: save area for registers in interrupt handlers */
+	uint64_t	sw_int_grs[16];			/* 0x0200 */
+	uint64_t	sw_int_fprs[16];		/* 0x0280 */
+	uint32_t	sw_int_fpc;			/* 0x0300 */
+	uint8_t		pad_0x0304[0x11b0 - 0x0304];	/* 0x0304 */
 	uint64_t	mcck_ext_sa_addr;		/* 0x11b0 */
 	uint8_t		pad_0x11b8[0x1200 - 0x11b8];	/* 0x11b8 */
 	uint64_t	fprs_sa[16];			/* 0x1200 */
@@ -84,4 +88,59 @@  struct lowcore {
 	uint8_t		pgm_int_tdb[0x1900 - 0x1800];	/* 0x1800 */
 } __attribute__ ((__packed__));
 
+#define PGM_INT_CODE_OPERATION			0x01
+#define PGM_INT_CODE_PRIVILEGED_OPERATION	0x02
+#define PGM_INT_CODE_EXECUTE			0x03
+#define PGM_INT_CODE_PROTECTION			0x04
+#define PGM_INT_CODE_ADDRESSING			0x05
+#define PGM_INT_CODE_SPECIFICATION		0x06
+#define PGM_INT_CODE_DATA			0x07
+#define PGM_INT_CODE_FIXED_POINT_OVERFLOW	0x08
+#define PGM_INT_CODE_FIXED_POINT_DIVIDE		0x09
+#define PGM_INT_CODE_DECIMAL_OVERFLOW		0x0a
+#define PGM_INT_CODE_DECIMAL_DIVIDE		0x0b
+#define PGM_INT_CODE_HFP_EXPONENT_OVERFLOW	0x0c
+#define PGM_INT_CODE_HFP_EXPONENT_UNDERFLOW	0x0d
+#define PGM_INT_CODE_HFP_SIGNIFICANCE		0x0e
+#define PGM_INT_CODE_HFP_DIVIDE			0x0f
+#define PGM_INT_CODE_SEGMENT_TRANSLATION	0x10
+#define PGM_INT_CODE_PAGE_TRANSLATION		0x11
+#define PGM_INT_CODE_TRANSLATION_SPEC		0x12
+#define PGM_INT_CODE_SPECIAL_OPERATION		0x13
+#define PGM_INT_CODE_OPERAND			0x15
+#define PGM_INT_CODE_TRACE_TABLE		0x16
+#define PGM_INT_CODE_VECTOR_PROCESSING		0x1b
+#define PGM_INT_CODE_SPACE_SWITCH_EVENT		0x1c
+#define PGM_INT_CODE_HFP_SQUARE_ROOT		0x1d
+#define PGM_INT_CODE_PC_TRANSLATION_SPEC	0x1f
+#define PGM_INT_CODE_AFX_TRANSLATION		0x20
+#define PGM_INT_CODE_ASX_TRANSLATION		0x21
+#define PGM_INT_CODE_LX_TRANSLATION		0x22
+#define PGM_INT_CODE_EX_TRANSLATION		0x23
+#define PGM_INT_CODE_PRIMARY_AUTHORITY		0x24
+#define PGM_INT_CODE_SECONDARY_AUTHORITY	0x25
+#define PGM_INT_CODE_LFX_TRANSLATION		0x26
+#define PGM_INT_CODE_LSX_TRANSLATION		0x27
+#define PGM_INT_CODE_ALET_SPECIFICATION		0x28
+#define PGM_INT_CODE_ALEN_TRANSLATION		0x29
+#define PGM_INT_CODE_ALE_SEQUENCE		0x2a
+#define PGM_INT_CODE_ASTE_VALIDITY		0x2b
+#define PGM_INT_CODE_ASTE_SEQUENCE		0x2c
+#define PGM_INT_CODE_EXTENDED_AUTHORITY		0x2d
+#define PGM_INT_CODE_LSTE_SEQUENCE		0x2e
+#define PGM_INT_CODE_ASTE_INSTANCE		0x2f
+#define PGM_INT_CODE_STACK_FULL			0x30
+#define PGM_INT_CODE_STACK_EMPTY		0x31
+#define PGM_INT_CODE_STACK_SPECIFICATION	0x32
+#define PGM_INT_CODE_STACK_TYPE			0x33
+#define PGM_INT_CODE_STACK_OPERATION		0x34
+#define PGM_INT_CODE_ASCE_TYPE			0x38
+#define PGM_INT_CODE_REGION_FIRST_TRANS		0x39
+#define PGM_INT_CODE_REGION_SECOND_TRANS	0x3a
+#define PGM_INT_CODE_REGION_THIRD_TRANS		0x3b
+#define PGM_INT_CODE_MONITOR_EVENT		0x40
+#define PGM_INT_CODE_PER			0x80
+#define PGM_INT_CODE_CRYPTO_OPERATION		0x119
+#define PGM_INT_CODE_TX_ABORTED_EVENT		0x200
+
 #endif
diff --git a/lib/s390x/asm/interrupt.h b/lib/s390x/asm/interrupt.h
new file mode 100644
index 0000000..383d312
--- /dev/null
+++ b/lib/s390x/asm/interrupt.h
@@ -0,0 +1,18 @@ 
+/*
+ * Copyright (c) 2017 Red Hat Inc
+ *
+ * Authors:
+ *  David Hildenbrand <david@redhat.com>
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License version 2.
+ */
+#ifndef _ASMS390X_IRQ_H_
+#define _ASMS390X_IRQ_H_
+#include <asm/arch_def.h>
+
+void handle_pgm_int(void);
+void expect_pgm_int(void);
+void check_pgm_int_code(uint16_t code);
+
+#endif
diff --git a/lib/s390x/interrupt.c b/lib/s390x/interrupt.c
new file mode 100644
index 0000000..8d861a2
--- /dev/null
+++ b/lib/s390x/interrupt.c
@@ -0,0 +1,82 @@ 
+/*
+ * s390x interrupt handling
+ *
+ * Copyright (c) 2017 Red Hat Inc
+ *
+ * Authors:
+ *  David Hildenbrand <david@redhat.com>
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License version 2.
+ */
+#include <libcflat.h>
+#include <asm/interrupt.h>
+#include <asm/barrier.h>
+
+static bool pgm_int_expected;
+static struct lowcore *lc;
+
+void expect_pgm_int(void)
+{
+	pgm_int_expected = true;
+	lc->pgm_int_code = 0;
+	mb();
+}
+
+void check_pgm_int_code(uint16_t code)
+{
+	mb();
+	report("Program interrupt: expected(%d) == received(%d)",
+	       code == lc->pgm_int_code, code, lc->pgm_int_code);
+}
+
+static void fixup_pgm_int(void)
+{
+	switch (lc->pgm_int_code) {
+	case PGM_INT_CODE_SEGMENT_TRANSLATION:
+	case PGM_INT_CODE_PAGE_TRANSLATION:
+	case PGM_INT_CODE_TRACE_TABLE:
+	case PGM_INT_CODE_AFX_TRANSLATION:
+	case PGM_INT_CODE_ASX_TRANSLATION:
+	case PGM_INT_CODE_LX_TRANSLATION:
+	case PGM_INT_CODE_EX_TRANSLATION:
+	case PGM_INT_CODE_PRIMARY_AUTHORITY:
+	case PGM_INT_CODE_SECONDARY_AUTHORITY:
+	case PGM_INT_CODE_LFX_TRANSLATION:
+	case PGM_INT_CODE_LSX_TRANSLATION:
+	case PGM_INT_CODE_ALEN_TRANSLATION:
+	case PGM_INT_CODE_ALE_SEQUENCE:
+	case PGM_INT_CODE_ASTE_VALIDITY:
+	case PGM_INT_CODE_ASTE_SEQUENCE:
+	case PGM_INT_CODE_EXTENDED_AUTHORITY:
+	case PGM_INT_CODE_LSTE_SEQUENCE:
+	case PGM_INT_CODE_ASTE_INSTANCE:
+	case PGM_INT_CODE_STACK_FULL:
+	case PGM_INT_CODE_STACK_EMPTY:
+	case PGM_INT_CODE_STACK_SPECIFICATION:
+	case PGM_INT_CODE_STACK_TYPE:
+	case PGM_INT_CODE_STACK_OPERATION:
+	case PGM_INT_CODE_ASCE_TYPE:
+	case PGM_INT_CODE_REGION_FIRST_TRANS:
+	case PGM_INT_CODE_REGION_SECOND_TRANS:
+	case PGM_INT_CODE_REGION_THIRD_TRANS:
+	case PGM_INT_CODE_PER:
+	case PGM_INT_CODE_CRYPTO_OPERATION:
+		/* The interrupt was nullified, the old PSW points at the
+		 * responsible instruction. Forward the PSW so we don't loop.
+		 */
+		lc->pgm_old_psw.addr += lc->pgm_int_id;
+	}
+	/* suppressed/terminated/completed point already at the next address */
+}
+
+void handle_pgm_int(void)
+{
+	if (!pgm_int_expected)
+		report_abort("Unexpected program interrupt: %d at %#lx, ilen %d\n",
+			     lc->pgm_int_code, lc->pgm_old_psw.addr,
+			     lc->pgm_int_id);
+
+	pgm_int_expected = false;
+	fixup_pgm_int();
+}
diff --git a/s390x/Makefile b/s390x/Makefile
index 4e4b94a..b48f8ab 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -10,6 +10,7 @@  CFLAGS += -Wextra
 CFLAGS += -I $(SRCDIR)/lib
 CFLAGS += -O2
 CFLAGS += -march=z900
+CFLAGS += -fno-delete-null-pointer-checks
 LDFLAGS += -nostdlib
 
 # We want to keep intermediate files
@@ -23,6 +24,7 @@  cflatobjs += lib/alloc.o
 cflatobjs += lib/s390x/io.o
 cflatobjs += lib/s390x/stack.o
 cflatobjs += lib/s390x/sclp-ascii.o
+cflatobjs += lib/s390x/interrupt.o
 
 OBJDIRS += lib/s390x
 
diff --git a/s390x/cstart64.S b/s390x/cstart64.S
index 28cd59d..4d0c877 100644
--- a/s390x/cstart64.S
+++ b/s390x/cstart64.S
@@ -10,6 +10,8 @@ 
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Library General Public License version 2.
  */
+#include <asm/asm-offsets.h>
+
 .section .init
 
 /* entry point - for KVM + TCG we directly start in 64 bit mode */
@@ -21,6 +23,10 @@  start:
 	larl	%r1, initital_psw
 	lpswe	0(%r1)
 init_psw_cont:
+	/* setup pgm interrupt handler */
+	larl	%r1, pgm_int_psw
+	mvc	GEN_LC_PGM_NEW_PSW(16), 0(%r1)
+	/* setup cr0, enabling e.g. AFP-register control */
 	larl	%r1, initital_cr0
 	lctlg	%c0, %c0, 0(%r1)
 	/* call setup() */
@@ -36,9 +42,58 @@  init_psw_cont:
 	/* call exit() */
 	j exit
 
+pgm_int:
+	/* save grs 0-15 */
+	stmg	%r0, %r15, GEN_LC_SW_INT_GRS
+	/* save fprs 0-15 + fpc */
+	larl	%r1, GEN_LC_SW_INT_FPRS
+	std	%f0, 0(%r1)
+	std	%f1, 8(%r1)
+	std	%f2, 16(%r1)
+	std	%f3, 24(%r1)
+	std	%f4, 32(%r1)
+	std	%f5, 40(%r1)
+	std	%f6, 48(%r1)
+	std	%f7, 56(%r1)
+	std	%f8, 64(%r1)
+	std	%f9, 72(%r1)
+	std	%f10, 80(%r1)
+	std	%f11, 88(%r1)
+	std	%f12, 96(%r1)
+	std	%f13, 104(%r1)
+	std	%f14, 112(%r1)
+	std	%f15, 120(%r1)
+	stfpc	GEN_LC_SW_INT_FPC
+	/* call our c handler */
+	brasl	%r14, handle_pgm_int
+	/* restore fprs 0-15 + fpc */
+	larl	%r1, GEN_LC_SW_INT_FPRS
+	ld	%f0, 0(%r1)
+	ld	%f1, 8(%r1)
+	ld	%f2, 16(%r1)
+	ld	%f3, 24(%r1)
+	ld	%f4, 32(%r1)
+	ld	%f5, 40(%r1)
+	ld	%f6, 48(%r1)
+	ld	%f7, 56(%r1)
+	ld	%f8, 64(%r1)
+	ld	%f9, 72(%r1)
+	ld	%f10, 80(%r1)
+	ld	%f11, 88(%r1)
+	ld	%f12, 96(%r1)
+	ld	%f13, 104(%r1)
+	ld	%f14, 112(%r1)
+	ld	%f15, 120(%r1)
+	lfpc	GEN_LC_SW_INT_FPC
+	/* restore grs 0-15 */
+	lmg	%r0, %r15, GEN_LC_SW_INT_GRS
+	lpswe	GEN_LC_PGM_OLD_PSW
+
 	.align	8
 initital_psw:
 	.quad	0x0000000180000000, init_psw_cont
+pgm_int_psw:
+	.quad	0x0000000180000000, pgm_int
 initital_cr0:
 	/* enable AFP-register control, so FP regs (+BFP instr) can be used */
 	.quad	0x0000000000040000
diff --git a/s390x/selftest.c b/s390x/selftest.c
index 4558e47..ca94158 100644
--- a/s390x/selftest.c
+++ b/s390x/selftest.c
@@ -10,6 +10,7 @@ 
  */
 #include <libcflat.h>
 #include <util.h>
+#include <asm/interrupt.h>
 
 static void test_fp(void)
 {
@@ -25,6 +26,17 @@  static void test_fp(void)
 	report("3.0/2.0 == 1.5", c == 1.5);
 }
 
+static void test_pgm_int(void)
+{
+	expect_pgm_int();
+	asm volatile("	.insn e,0x0001"); /* used for SW breakpoints in QEMU */
+	check_pgm_int_code(PGM_INT_CODE_OPERATION);
+
+	expect_pgm_int();
+	*((unsigned int*)-1) = 1;
+	check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
+}
+
 int main(int argc, char**argv)
 {
 	report_prefix_push("selftest");
@@ -36,6 +48,7 @@  int main(int argc, char**argv)
 	report("argv[2] == 123", !strcmp(argv[2], "123"));
 
 	test_fp();
+	test_pgm_int();
 
 	return report_summary();
 }