diff mbox series

[kvm-unit-tests,v5,24/29] powerpc: interrupt tests

Message ID 20231216134257.1743345-25-npiggin@gmail.com (mailing list archive)
State New, archived
Headers show
Series powerpc: updates, P10, PNV support | expand

Commit Message

Nicholas Piggin Dec. 16, 2023, 1:42 p.m. UTC
Add basic testing of various kinds of interrupts, machine check,
page fault, illegal, decrementer, trace, syscall, etc.

This has a known failure on QEMU TCG pseries machines where MSR[ME]
can be incorrectly set to 0.

Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
 lib/powerpc/asm/ppc_asm.h |  21 +-
 powerpc/Makefile.common   |   3 +-
 powerpc/interrupts.c      | 422 ++++++++++++++++++++++++++++++++++++++
 powerpc/unittests.cfg     |   3 +
 4 files changed, 445 insertions(+), 4 deletions(-)
 create mode 100644 powerpc/interrupts.c

Comments

Thomas Huth Dec. 19, 2023, 1:57 p.m. UTC | #1
On 16/12/2023 14.42, Nicholas Piggin wrote:
> Add basic testing of various kinds of interrupts, machine check,
> page fault, illegal, decrementer, trace, syscall, etc.
> 
> This has a known failure on QEMU TCG pseries machines where MSR[ME]
> can be incorrectly set to 0.
> 
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> ---
>   lib/powerpc/asm/ppc_asm.h |  21 +-
>   powerpc/Makefile.common   |   3 +-
>   powerpc/interrupts.c      | 422 ++++++++++++++++++++++++++++++++++++++
>   powerpc/unittests.cfg     |   3 +
>   4 files changed, 445 insertions(+), 4 deletions(-)
>   create mode 100644 powerpc/interrupts.c
> 
> diff --git a/lib/powerpc/asm/ppc_asm.h b/lib/powerpc/asm/ppc_asm.h
> index ef2d91dd..778e78ee 100644
> --- a/lib/powerpc/asm/ppc_asm.h
> +++ b/lib/powerpc/asm/ppc_asm.h
> @@ -35,17 +35,32 @@
>   
>   #endif /* __BYTE_ORDER__ */
>   
> +#define SPR_DSISR	0x012
> +#define SPR_DAR		0x013
> +#define SPR_DEC		0x016
> +#define SPR_SRR0	0x01A
> +#define SPR_SRR1	0x01B
> +#define SPR_FSCR	0x099
> +#define   FSCR_PREFIX	0x2000
> +#define SPR_HDEC	0x136
>   #define SPR_HSRR0	0x13A
>   #define SPR_HSRR1	0x13B
> +#define SPR_LPCR	0x13E
> +#define   LPCR_HDICE	0x1UL
> +#define SPR_HEIR	0x153
> +#define SPR_SIAR	0x31C
>   
>   /* Machine State Register definitions: */
>   #define MSR_LE_BIT	0
>   #define MSR_EE_BIT	15			/* External Interrupts Enable */
>   #define MSR_HV_BIT	60			/* Hypervisor mode */
>   #define MSR_SF_BIT	63			/* 64-bit mode */
> -#define MSR_ME		0x1000ULL
>   
> -#define SPR_HSRR0	0x13A
> -#define SPR_HSRR1	0x13B
> +#define MSR_DR		0x0010ULL
> +#define MSR_IR		0x0020ULL
> +#define MSR_BE		0x0200ULL		/* Branch Trace Enable */
> +#define MSR_SE		0x0400ULL		/* Single Step Enable */
> +#define MSR_EE		0x8000ULL
> +#define MSR_ME		0x1000ULL
>   
>   #endif /* _ASMPOWERPC_PPC_ASM_H */
> diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
> index a7af225b..b340a53b 100644
> --- a/powerpc/Makefile.common
> +++ b/powerpc/Makefile.common
> @@ -11,7 +11,8 @@ tests-common = \
>   	$(TEST_DIR)/rtas.elf \
>   	$(TEST_DIR)/emulator.elf \
>   	$(TEST_DIR)/tm.elf \
> -	$(TEST_DIR)/sprs.elf
> +	$(TEST_DIR)/sprs.elf \
> +	$(TEST_DIR)/interrupts.elf
>   
>   tests-all = $(tests-common) $(tests)
>   all: directories $(TEST_DIR)/boot_rom.bin $(tests-all)
> diff --git a/powerpc/interrupts.c b/powerpc/interrupts.c
> new file mode 100644
> index 00000000..3217b15e
> --- /dev/null
> +++ b/powerpc/interrupts.c
> @@ -0,0 +1,422 @@
> +/*
> + * Test interrupts
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
> +#include <libcflat.h>
> +#include <util.h>
> +#include <migrate.h>
> +#include <alloc.h>
> +#include <asm/handlers.h>
> +#include <asm/hcall.h>
> +#include <asm/processor.h>
> +#include <asm/barrier.h>
> +
> +#define SPR_LPCR	0x13E
> +#define LPCR_HDICE	0x1UL
> +#define SPR_DEC		0x016
> +#define SPR_HDEC	0x136
> +
> +#define MSR_DR		0x0010ULL
> +#define MSR_IR		0x0020ULL
> +#define MSR_EE		0x8000ULL
> +#define MSR_ME		0x1000ULL

Why don't you use the definitions from ppc_asm.h above?

> +static bool cpu_has_heir(void)
> +{
> +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> +
> +	if (!machine_is_powernv())
> +		return false;
> +
> +	/* POWER6 has HEIR, but QEMU powernv support does not go that far */
> +	switch (pvr >> 16) {
> +	case 0x4b:			/* POWER8E */
> +	case 0x4c:			/* POWER8NVL */
> +	case 0x4d:			/* POWER8 */
> +	case 0x4e:			/* POWER9 */
> +	case 0x80:			/* POWER10 */

I'd suggest to introduce some #defines for those PVR values instead of using 
magic numbers all over the place?

> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static bool cpu_has_prefix(void)
> +{
> +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> +	switch (pvr >> 16) {
> +	case 0x80:			/* POWER10 */
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static bool cpu_has_lev_in_srr1(void)
> +{
> +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> +	switch (pvr >> 16) {
> +	case 0x80:			/* POWER10 */
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static bool regs_is_prefix(volatile struct pt_regs *regs)
> +{
> +	return (regs->msr >> (63-34)) & 1;

You introduced a bunch of new #define MSR_xx statements ... why not for this 
one, too?

> +}
> +
> +static void regs_advance_insn(struct pt_regs *regs)
> +{
> +	if (regs_is_prefix(regs))
> +		regs->nip += 8;
> +	else
> +		regs->nip += 4;
> +}
> +
> +static volatile bool got_interrupt;
> +static volatile struct pt_regs recorded_regs;
> +
> +static void mce_handler(struct pt_regs *regs, void *opaque)
> +{
> +	got_interrupt = true;
> +	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
> +	regs_advance_insn(regs);
> +}
> +
> +static void test_mce(void)
> +{
> +	unsigned long addr = -4ULL;
> +	uint8_t tmp;
> +
> +	handle_exception(0x200, mce_handler, NULL);
> +
> +	if (machine_is_powernv()) {
> +		enable_mcheck();
> +	} else {
> +		report(mfmsr() & MSR_ME, "pseries machine has MSR[ME]=1");
> +		if (!(mfmsr() & MSR_ME)) { /* try to fix it */
> +			enable_mcheck();
> +		}
> +		if (mfmsr() & MSR_ME) {
> +			disable_mcheck();
> +			report(mfmsr() & MSR_ME, "pseries is unable to change MSR[ME]");
> +			if (!(mfmsr() & MSR_ME)) { /* try to fix it */
> +				enable_mcheck();
> +			}
> +		}
> +	}
> +
> +	asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
> +
> +	report(got_interrupt, "MCE on access to invalid real address");
> +	report(mfspr(SPR_DAR) == addr, "MCE sets DAR correctly");
> +	got_interrupt = false;
> +}
> +
> +static void dseg_handler(struct pt_regs *regs, void *data)
> +{
> +	got_interrupt = true;
> +	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
> +	regs_advance_insn(regs);
> +	regs->msr &= ~MSR_DR;
> +}
> +
> +static void test_dseg(void)
> +{
> +	uint64_t msr, tmp;
> +
> +	report_prefix_push("data segment");
> +
> +	/* Some HV start in radix mode and need 0x300 */
> +	handle_exception(0x300, &dseg_handler, NULL);
> +	handle_exception(0x380, &dseg_handler, NULL);
> +
> +	asm volatile(
> +"		mfmsr	%0		\n \
> +		ori	%0,%0,%2	\n \
> +		mtmsrd	%0		\n \
> +		lbz	%1,0(0)		"
> +		: "=r"(msr), "=r"(tmp) : "i"(MSR_DR): "memory");
> +
> +	report(got_interrupt, "interrupt on NULL dereference");
> +	got_interrupt = false;
> +
> +	handle_exception(0x300, NULL, NULL);
> +	handle_exception(0x380, NULL, NULL);
> +
> +	report_prefix_pop();
> +}
> +
> +static void dec_handler(struct pt_regs *regs, void *data)
> +{
> +	got_interrupt = true;
> +	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
> +	regs->msr &= ~MSR_EE;
> +}
> +
> +static void test_dec(void)
> +{
> +	uint64_t msr;
> +
> +	report_prefix_push("decrementer");
> +
> +	handle_exception(0x900, &dec_handler, NULL);
> +
> +	asm volatile(
> +"		mtdec	%1		\n \
> +		mfmsr	%0		\n \
> +		ori	%0,%0,%2	\n \
> +		mtmsrd	%0,1		"
> +		: "=r"(msr) : "r"(10000), "i"(MSR_EE): "memory");
> +
> +	while (!got_interrupt)
> +		;

Maybe add a timeout (in case the interrupt never fires)?

> +	report(got_interrupt, "interrupt on decrementer underflow");
> +	got_interrupt = false;
> +
> +	handle_exception(0x900, NULL, NULL);
> +
> +	if (!machine_is_powernv())
> +		goto done;
> +
> +	handle_exception(0x980, &dec_handler, NULL);
> +
> +	mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_HDICE);
> +	asm volatile(
> +"		mtspr	0x136,%1	\n \
> +		mtdec	%3		\n \
> +		mfmsr	%0		\n \
> +		ori	%0,%0,%2	\n \
> +		mtmsrd	%0,1		"
> +		: "=r"(msr) : "r"(10000), "i"(MSR_EE), "r"(0x7fffffff): "memory");
> +
> +	while (!got_interrupt)
> +		;

dito?

> +	mtspr(SPR_LPCR, mfspr(SPR_LPCR) & ~LPCR_HDICE);
> +
> +	report(got_interrupt, "interrupt on hdecrementer underflow");
> +	got_interrupt = false;
> +
> +	handle_exception(0x980, NULL, NULL);
> +
> +done:
> +	report_prefix_pop();
> +}

  Thomas
Nicholas Piggin Dec. 22, 2023, 9:58 a.m. UTC | #2
On Tue Dec 19, 2023 at 11:57 PM AEST, Thomas Huth wrote:
> On 16/12/2023 14.42, Nicholas Piggin wrote:
> > Add basic testing of various kinds of interrupts, machine check,
> > page fault, illegal, decrementer, trace, syscall, etc.
> > 
> > This has a known failure on QEMU TCG pseries machines where MSR[ME]
> > can be incorrectly set to 0.
> > 
> > Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> > ---
> >   lib/powerpc/asm/ppc_asm.h |  21 +-
> >   powerpc/Makefile.common   |   3 +-
> >   powerpc/interrupts.c      | 422 ++++++++++++++++++++++++++++++++++++++
> >   powerpc/unittests.cfg     |   3 +
> >   4 files changed, 445 insertions(+), 4 deletions(-)
> >   create mode 100644 powerpc/interrupts.c
> > 
> > diff --git a/lib/powerpc/asm/ppc_asm.h b/lib/powerpc/asm/ppc_asm.h
> > index ef2d91dd..778e78ee 100644
> > --- a/lib/powerpc/asm/ppc_asm.h
> > +++ b/lib/powerpc/asm/ppc_asm.h
> > @@ -35,17 +35,32 @@
> >   
> >   #endif /* __BYTE_ORDER__ */
> >   
> > +#define SPR_DSISR	0x012
> > +#define SPR_DAR		0x013
> > +#define SPR_DEC		0x016
> > +#define SPR_SRR0	0x01A
> > +#define SPR_SRR1	0x01B
> > +#define SPR_FSCR	0x099
> > +#define   FSCR_PREFIX	0x2000
> > +#define SPR_HDEC	0x136
> >   #define SPR_HSRR0	0x13A
> >   #define SPR_HSRR1	0x13B
> > +#define SPR_LPCR	0x13E
> > +#define   LPCR_HDICE	0x1UL
> > +#define SPR_HEIR	0x153
> > +#define SPR_SIAR	0x31C
> >   
> >   /* Machine State Register definitions: */
> >   #define MSR_LE_BIT	0
> >   #define MSR_EE_BIT	15			/* External Interrupts Enable */
> >   #define MSR_HV_BIT	60			/* Hypervisor mode */
> >   #define MSR_SF_BIT	63			/* 64-bit mode */
> > -#define MSR_ME		0x1000ULL
> >   
> > -#define SPR_HSRR0	0x13A
> > -#define SPR_HSRR1	0x13B
> > +#define MSR_DR		0x0010ULL
> > +#define MSR_IR		0x0020ULL
> > +#define MSR_BE		0x0200ULL		/* Branch Trace Enable */
> > +#define MSR_SE		0x0400ULL		/* Single Step Enable */
> > +#define MSR_EE		0x8000ULL
> > +#define MSR_ME		0x1000ULL
> >   
> >   #endif /* _ASMPOWERPC_PPC_ASM_H */
> > diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
> > index a7af225b..b340a53b 100644
> > --- a/powerpc/Makefile.common
> > +++ b/powerpc/Makefile.common
> > @@ -11,7 +11,8 @@ tests-common = \
> >   	$(TEST_DIR)/rtas.elf \
> >   	$(TEST_DIR)/emulator.elf \
> >   	$(TEST_DIR)/tm.elf \
> > -	$(TEST_DIR)/sprs.elf
> > +	$(TEST_DIR)/sprs.elf \
> > +	$(TEST_DIR)/interrupts.elf
> >   
> >   tests-all = $(tests-common) $(tests)
> >   all: directories $(TEST_DIR)/boot_rom.bin $(tests-all)
> > diff --git a/powerpc/interrupts.c b/powerpc/interrupts.c
> > new file mode 100644
> > index 00000000..3217b15e
> > --- /dev/null
> > +++ b/powerpc/interrupts.c
> > @@ -0,0 +1,422 @@
> > +/*
> > + * Test interrupts
> > + *
> > + * This work is licensed under the terms of the GNU LGPL, version 2.
> > + */
> > +#include <libcflat.h>
> > +#include <util.h>
> > +#include <migrate.h>
> > +#include <alloc.h>
> > +#include <asm/handlers.h>
> > +#include <asm/hcall.h>
> > +#include <asm/processor.h>
> > +#include <asm/barrier.h>
> > +
> > +#define SPR_LPCR	0x13E
> > +#define LPCR_HDICE	0x1UL
> > +#define SPR_DEC		0x016
> > +#define SPR_HDEC	0x136
> > +
> > +#define MSR_DR		0x0010ULL
> > +#define MSR_IR		0x0020ULL
> > +#define MSR_EE		0x8000ULL
> > +#define MSR_ME		0x1000ULL
>
> Why don't you use the definitions from ppc_asm.h above?

Yeah, should be more consistent with those. I'll take a look.

>
> > +static bool cpu_has_heir(void)
> > +{
> > +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> > +
> > +	if (!machine_is_powernv())
> > +		return false;
> > +
> > +	/* POWER6 has HEIR, but QEMU powernv support does not go that far */
> > +	switch (pvr >> 16) {
> > +	case 0x4b:			/* POWER8E */
> > +	case 0x4c:			/* POWER8NVL */
> > +	case 0x4d:			/* POWER8 */
> > +	case 0x4e:			/* POWER9 */
> > +	case 0x80:			/* POWER10 */
>
> I'd suggest to introduce some #defines for those PVR values instead of using 
> magic numbers all over the place?

Yeah you're right.

>
> > +		return true;
> > +	default:
> > +		return false;
> > +	}
> > +}
> > +
> > +static bool cpu_has_prefix(void)
> > +{
> > +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> > +	switch (pvr >> 16) {
> > +	case 0x80:			/* POWER10 */
> > +		return true;
> > +	default:
> > +		return false;
> > +	}
> > +}
> > +
> > +static bool cpu_has_lev_in_srr1(void)
> > +{
> > +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> > +	switch (pvr >> 16) {
> > +	case 0x80:			/* POWER10 */
> > +		return true;
> > +	default:
> > +		return false;
> > +	}
> > +}
> > +
> > +static bool regs_is_prefix(volatile struct pt_regs *regs)
> > +{
> > +	return (regs->msr >> (63-34)) & 1;
>
> You introduced a bunch of new #define MSR_xx statements ... why not for this 
> one, too?

It's an interrupt-specific bit so SRR1_xx, but yes it should be a
define.

>
> > +}
> > +
> > +static void regs_advance_insn(struct pt_regs *regs)
> > +{
> > +	if (regs_is_prefix(regs))
> > +		regs->nip += 8;
> > +	else
> > +		regs->nip += 4;
> > +}
> > +
> > +static volatile bool got_interrupt;
> > +static volatile struct pt_regs recorded_regs;
> > +
> > +static void mce_handler(struct pt_regs *regs, void *opaque)
> > +{
> > +	got_interrupt = true;
> > +	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
> > +	regs_advance_insn(regs);
> > +}
> > +
> > +static void test_mce(void)
> > +{
> > +	unsigned long addr = -4ULL;
> > +	uint8_t tmp;
> > +
> > +	handle_exception(0x200, mce_handler, NULL);
> > +
> > +	if (machine_is_powernv()) {
> > +		enable_mcheck();
> > +	} else {
> > +		report(mfmsr() & MSR_ME, "pseries machine has MSR[ME]=1");
> > +		if (!(mfmsr() & MSR_ME)) { /* try to fix it */
> > +			enable_mcheck();
> > +		}
> > +		if (mfmsr() & MSR_ME) {
> > +			disable_mcheck();
> > +			report(mfmsr() & MSR_ME, "pseries is unable to change MSR[ME]");
> > +			if (!(mfmsr() & MSR_ME)) { /* try to fix it */
> > +				enable_mcheck();
> > +			}
> > +		}
> > +	}
> > +
> > +	asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
> > +
> > +	report(got_interrupt, "MCE on access to invalid real address");
> > +	report(mfspr(SPR_DAR) == addr, "MCE sets DAR correctly");
> > +	got_interrupt = false;
> > +}
> > +
> > +static void dseg_handler(struct pt_regs *regs, void *data)
> > +{
> > +	got_interrupt = true;
> > +	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
> > +	regs_advance_insn(regs);
> > +	regs->msr &= ~MSR_DR;
> > +}
> > +
> > +static void test_dseg(void)
> > +{
> > +	uint64_t msr, tmp;
> > +
> > +	report_prefix_push("data segment");
> > +
> > +	/* Some HV start in radix mode and need 0x300 */
> > +	handle_exception(0x300, &dseg_handler, NULL);
> > +	handle_exception(0x380, &dseg_handler, NULL);
> > +
> > +	asm volatile(
> > +"		mfmsr	%0		\n \
> > +		ori	%0,%0,%2	\n \
> > +		mtmsrd	%0		\n \
> > +		lbz	%1,0(0)		"
> > +		: "=r"(msr), "=r"(tmp) : "i"(MSR_DR): "memory");
> > +
> > +	report(got_interrupt, "interrupt on NULL dereference");
> > +	got_interrupt = false;
> > +
> > +	handle_exception(0x300, NULL, NULL);
> > +	handle_exception(0x380, NULL, NULL);
> > +
> > +	report_prefix_pop();
> > +}
> > +
> > +static void dec_handler(struct pt_regs *regs, void *data)
> > +{
> > +	got_interrupt = true;
> > +	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
> > +	regs->msr &= ~MSR_EE;
> > +}
> > +
> > +static void test_dec(void)
> > +{
> > +	uint64_t msr;
> > +
> > +	report_prefix_push("decrementer");
> > +
> > +	handle_exception(0x900, &dec_handler, NULL);
> > +
> > +	asm volatile(
> > +"		mtdec	%1		\n \
> > +		mfmsr	%0		\n \
> > +		ori	%0,%0,%2	\n \
> > +		mtmsrd	%0,1		"
> > +		: "=r"(msr) : "r"(10000), "i"(MSR_EE): "memory");
> > +
> > +	while (!got_interrupt)
> > +		;
>
> Maybe add a timeout (in case the interrupt never fires)?

Yeah that would improve things.

> > +	report(got_interrupt, "interrupt on decrementer underflow");
> > +	got_interrupt = false;
> > +
> > +	handle_exception(0x900, NULL, NULL);
> > +
> > +	if (!machine_is_powernv())
> > +		goto done;
> > +
> > +	handle_exception(0x980, &dec_handler, NULL);
> > +
> > +	mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_HDICE);
> > +	asm volatile(
> > +"		mtspr	0x136,%1	\n \
> > +		mtdec	%3		\n \
> > +		mfmsr	%0		\n \
> > +		ori	%0,%0,%2	\n \
> > +		mtmsrd	%0,1		"
> > +		: "=r"(msr) : "r"(10000), "i"(MSR_EE), "r"(0x7fffffff): "memory");
> > +
> > +	while (!got_interrupt)
> > +		;
>
> dito?

Will do.

Thanks,
Nick
diff mbox series

Patch

diff --git a/lib/powerpc/asm/ppc_asm.h b/lib/powerpc/asm/ppc_asm.h
index ef2d91dd..778e78ee 100644
--- a/lib/powerpc/asm/ppc_asm.h
+++ b/lib/powerpc/asm/ppc_asm.h
@@ -35,17 +35,32 @@ 
 
 #endif /* __BYTE_ORDER__ */
 
+#define SPR_DSISR	0x012
+#define SPR_DAR		0x013
+#define SPR_DEC		0x016
+#define SPR_SRR0	0x01A
+#define SPR_SRR1	0x01B
+#define SPR_FSCR	0x099
+#define   FSCR_PREFIX	0x2000
+#define SPR_HDEC	0x136
 #define SPR_HSRR0	0x13A
 #define SPR_HSRR1	0x13B
+#define SPR_LPCR	0x13E
+#define   LPCR_HDICE	0x1UL
+#define SPR_HEIR	0x153
+#define SPR_SIAR	0x31C
 
 /* Machine State Register definitions: */
 #define MSR_LE_BIT	0
 #define MSR_EE_BIT	15			/* External Interrupts Enable */
 #define MSR_HV_BIT	60			/* Hypervisor mode */
 #define MSR_SF_BIT	63			/* 64-bit mode */
-#define MSR_ME		0x1000ULL
 
-#define SPR_HSRR0	0x13A
-#define SPR_HSRR1	0x13B
+#define MSR_DR		0x0010ULL
+#define MSR_IR		0x0020ULL
+#define MSR_BE		0x0200ULL		/* Branch Trace Enable */
+#define MSR_SE		0x0400ULL		/* Single Step Enable */
+#define MSR_EE		0x8000ULL
+#define MSR_ME		0x1000ULL
 
 #endif /* _ASMPOWERPC_PPC_ASM_H */
diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
index a7af225b..b340a53b 100644
--- a/powerpc/Makefile.common
+++ b/powerpc/Makefile.common
@@ -11,7 +11,8 @@  tests-common = \
 	$(TEST_DIR)/rtas.elf \
 	$(TEST_DIR)/emulator.elf \
 	$(TEST_DIR)/tm.elf \
-	$(TEST_DIR)/sprs.elf
+	$(TEST_DIR)/sprs.elf \
+	$(TEST_DIR)/interrupts.elf
 
 tests-all = $(tests-common) $(tests)
 all: directories $(TEST_DIR)/boot_rom.bin $(tests-all)
diff --git a/powerpc/interrupts.c b/powerpc/interrupts.c
new file mode 100644
index 00000000..3217b15e
--- /dev/null
+++ b/powerpc/interrupts.c
@@ -0,0 +1,422 @@ 
+/*
+ * Test interrupts
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include <libcflat.h>
+#include <util.h>
+#include <migrate.h>
+#include <alloc.h>
+#include <asm/handlers.h>
+#include <asm/hcall.h>
+#include <asm/processor.h>
+#include <asm/barrier.h>
+
+#define SPR_LPCR	0x13E
+#define LPCR_HDICE	0x1UL
+#define SPR_DEC		0x016
+#define SPR_HDEC	0x136
+
+#define MSR_DR		0x0010ULL
+#define MSR_IR		0x0020ULL
+#define MSR_EE		0x8000ULL
+#define MSR_ME		0x1000ULL
+
+static bool cpu_has_heir(void)
+{
+	uint32_t pvr = mfspr(287);	/* Processor Version Register */
+
+	if (!machine_is_powernv())
+		return false;
+
+	/* POWER6 has HEIR, but QEMU powernv support does not go that far */
+	switch (pvr >> 16) {
+	case 0x4b:			/* POWER8E */
+	case 0x4c:			/* POWER8NVL */
+	case 0x4d:			/* POWER8 */
+	case 0x4e:			/* POWER9 */
+	case 0x80:			/* POWER10 */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cpu_has_prefix(void)
+{
+	uint32_t pvr = mfspr(287);	/* Processor Version Register */
+	switch (pvr >> 16) {
+	case 0x80:			/* POWER10 */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cpu_has_lev_in_srr1(void)
+{
+	uint32_t pvr = mfspr(287);	/* Processor Version Register */
+	switch (pvr >> 16) {
+	case 0x80:			/* POWER10 */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool regs_is_prefix(volatile struct pt_regs *regs)
+{
+	return (regs->msr >> (63-34)) & 1;
+}
+
+static void regs_advance_insn(struct pt_regs *regs)
+{
+	if (regs_is_prefix(regs))
+		regs->nip += 8;
+	else
+		regs->nip += 4;
+}
+
+static volatile bool got_interrupt;
+static volatile struct pt_regs recorded_regs;
+
+static void mce_handler(struct pt_regs *regs, void *opaque)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+	regs_advance_insn(regs);
+}
+
+static void test_mce(void)
+{
+	unsigned long addr = -4ULL;
+	uint8_t tmp;
+
+	handle_exception(0x200, mce_handler, NULL);
+
+	if (machine_is_powernv()) {
+		enable_mcheck();
+	} else {
+		report(mfmsr() & MSR_ME, "pseries machine has MSR[ME]=1");
+		if (!(mfmsr() & MSR_ME)) { /* try to fix it */
+			enable_mcheck();
+		}
+		if (mfmsr() & MSR_ME) {
+			disable_mcheck();
+			report(mfmsr() & MSR_ME, "pseries is unable to change MSR[ME]");
+			if (!(mfmsr() & MSR_ME)) { /* try to fix it */
+				enable_mcheck();
+			}
+		}
+	}
+
+	asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
+
+	report(got_interrupt, "MCE on access to invalid real address");
+	report(mfspr(SPR_DAR) == addr, "MCE sets DAR correctly");
+	got_interrupt = false;
+}
+
+static void dseg_handler(struct pt_regs *regs, void *data)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+	regs_advance_insn(regs);
+	regs->msr &= ~MSR_DR;
+}
+
+static void test_dseg(void)
+{
+	uint64_t msr, tmp;
+
+	report_prefix_push("data segment");
+
+	/* Some HV start in radix mode and need 0x300 */
+	handle_exception(0x300, &dseg_handler, NULL);
+	handle_exception(0x380, &dseg_handler, NULL);
+
+	asm volatile(
+"		mfmsr	%0		\n \
+		ori	%0,%0,%2	\n \
+		mtmsrd	%0		\n \
+		lbz	%1,0(0)		"
+		: "=r"(msr), "=r"(tmp) : "i"(MSR_DR): "memory");
+
+	report(got_interrupt, "interrupt on NULL dereference");
+	got_interrupt = false;
+
+	handle_exception(0x300, NULL, NULL);
+	handle_exception(0x380, NULL, NULL);
+
+	report_prefix_pop();
+}
+
+static void dec_handler(struct pt_regs *regs, void *data)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+	regs->msr &= ~MSR_EE;
+}
+
+static void test_dec(void)
+{
+	uint64_t msr;
+
+	report_prefix_push("decrementer");
+
+	handle_exception(0x900, &dec_handler, NULL);
+
+	asm volatile(
+"		mtdec	%1		\n \
+		mfmsr	%0		\n \
+		ori	%0,%0,%2	\n \
+		mtmsrd	%0,1		"
+		: "=r"(msr) : "r"(10000), "i"(MSR_EE): "memory");
+
+	while (!got_interrupt)
+		;
+
+	report(got_interrupt, "interrupt on decrementer underflow");
+	got_interrupt = false;
+
+	handle_exception(0x900, NULL, NULL);
+
+	if (!machine_is_powernv())
+		goto done;
+
+	handle_exception(0x980, &dec_handler, NULL);
+
+	mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_HDICE);
+	asm volatile(
+"		mtspr	0x136,%1	\n \
+		mtdec	%3		\n \
+		mfmsr	%0		\n \
+		ori	%0,%0,%2	\n \
+		mtmsrd	%0,1		"
+		: "=r"(msr) : "r"(10000), "i"(MSR_EE), "r"(0x7fffffff): "memory");
+
+	while (!got_interrupt)
+		;
+
+	mtspr(SPR_LPCR, mfspr(SPR_LPCR) & ~LPCR_HDICE);
+
+	report(got_interrupt, "interrupt on hdecrementer underflow");
+	got_interrupt = false;
+
+	handle_exception(0x980, NULL, NULL);
+
+done:
+	report_prefix_pop();
+}
+
+
+static volatile uint64_t recorded_heir;
+
+static void heai_handler(struct pt_regs *regs, void *data)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+	regs_advance_insn(regs);
+	if (cpu_has_heir())
+		recorded_heir = mfspr(SPR_HEIR);
+}
+
+static void program_handler(struct pt_regs *regs, void *data)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+	regs_advance_insn(regs);
+}
+
+/*
+ * This tests invalid instruction handling. powernv (HV) should take an
+ * HEAI interrupt with the HEIR SPR set to the instruction image. pseries
+ * (guest) should take a program interrupt. CPUs which support prefix
+ * should report prefix instruction in (H)SRR1[34].
+ */
+static void test_illegal(void)
+{
+	report_prefix_push("illegal instruction");
+
+	if (machine_is_powernv()) {
+		handle_exception(0xe40, &heai_handler, NULL);
+	} else {
+		handle_exception(0x700, &program_handler, NULL);
+	}
+
+	asm volatile(".long 0x12345678" ::: "memory");
+	report(got_interrupt, "interrupt on invalid instruction");
+	got_interrupt = false;
+	if (cpu_has_heir())
+		report(recorded_heir == 0x12345678, "HEIR: 0x%08lx", recorded_heir);
+	report(!regs_is_prefix(&recorded_regs), "(H)SRR1 prefix bit: %d", regs_is_prefix(&recorded_regs));
+
+	if (cpu_has_prefix()) {
+		mtspr(SPR_FSCR, mfspr(SPR_FSCR) | FSCR_PREFIX);
+		asm volatile(".balign 8 ; .long 0x04000123; .long 0x00badc0d");
+		report(got_interrupt, "interrupt on invalid prefix instruction");
+		got_interrupt = false;
+		if (cpu_has_heir())
+			report(recorded_heir == 0x0400012300badc0d, "HEIR: 0x%08lx", recorded_heir);
+		report(regs_is_prefix(&recorded_regs), "(H)SRR1 prefix bit: %d", regs_is_prefix(&recorded_regs));
+	}
+
+	handle_exception(0xe40, NULL, NULL);
+	handle_exception(0x700, NULL, NULL);
+
+	report_prefix_pop();
+}
+
+static void sc_handler(struct pt_regs *regs, void *data)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+}
+
+static void test_sc(void)
+{
+	report_prefix_push("syscall");
+
+	handle_exception(0xc00, &sc_handler, NULL);
+
+	asm volatile("sc 0" ::: "memory");
+
+	report(got_interrupt, "interrupt on sc 0 instruction");
+	got_interrupt = false;
+	if (cpu_has_lev_in_srr1())
+		report(((recorded_regs.msr >> 20) & 0x3) == 0, "SRR1 set LEV=0");
+	if (machine_is_powernv()) {
+		asm volatile("sc 1" ::: "memory");
+
+		report(got_interrupt, "interrupt on sc 1 instruction");
+		got_interrupt = false;
+		if (cpu_has_lev_in_srr1())
+			report(((recorded_regs.msr >> 20) & 0x3) == 1, "SRR1 set LEV=1");
+	}
+
+	handle_exception(0xc00, NULL, NULL);
+
+	report_prefix_pop();
+}
+
+
+static void trace_handler(struct pt_regs *regs, void *data)
+{
+	got_interrupt = true;
+	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
+	regs->msr &= ~(MSR_SE | MSR_BE);
+}
+
+static void program_trace_handler(struct pt_regs *regs, void *data)
+{
+	regs->msr &= ~(MSR_SE | MSR_BE);
+	regs->nip += 4;
+}
+
+extern char trace_insn[];
+extern char trace_insn2[];
+extern char trace_insn3[];
+extern char trace_rfid[];
+
+static void test_trace(void)
+{
+	unsigned long msr;
+
+	report_prefix_push("trace");
+
+	handle_exception(0xd00, &trace_handler, NULL);
+
+	msr = mfmsr() | MSR_SE;
+	asm volatile(
+	"	mtmsr	%0		\n"
+	".global trace_insn		\n"
+	"trace_insn:			\n"
+	"	nop			\n"
+	: : "r"(msr) : "memory");
+
+	report(got_interrupt, "interrupt on single step");
+	got_interrupt = false;
+	report(recorded_regs.nip == (unsigned long)trace_insn + 4,
+			"single step interrupt at the correct address");
+	report(mfspr(SPR_SIAR) == (unsigned long)trace_insn,
+			"single step recorded SIAR at the correct address");
+
+	msr = mfmsr() | MSR_SE;
+	asm volatile(
+	"	mtmsr	%0		\n"
+	".global trace_insn2		\n"
+	"trace_insn2:			\n"
+	"	b	1f		\n"
+	"	nop			\n"
+	"1:				\n"
+	: : "r"(msr) : "memory");
+
+	report(got_interrupt, "interrupt on single step branch");
+	got_interrupt = false;
+	report(recorded_regs.nip == (unsigned long)trace_insn2 + 8,
+			"single step interrupt at the correct address");
+	report(mfspr(SPR_SIAR) == (unsigned long)trace_insn2,
+			"single step recorded SIAR at the correct address");
+
+	msr = mfmsr() | MSR_BE;
+	asm volatile(
+	"	mtmsr	%0		\n"
+	".global trace_insn3		\n"
+	"trace_insn3:			\n"
+	"	nop			\n"
+	"	b	1f		\n"
+	"	nop			\n"
+	"1:				\n"
+	: : "r"(msr) : "memory");
+
+	report(got_interrupt, "interrupt on branch trace");
+	got_interrupt = false;
+	report(recorded_regs.nip == (unsigned long)trace_insn3 + 12,
+			"branch trace interrupt at the correct address");
+	report(mfspr(SPR_SIAR) == (unsigned long)trace_insn3 + 4,
+			"branch trace recorded SIAR at the correct address");
+
+	handle_exception(0x700, &program_trace_handler, NULL);
+	msr = mfmsr() | MSR_SE;
+	asm volatile(
+	"	mtmsr	%0		\n"
+	"	trap			\n"
+	: : "r"(msr) : "memory");
+
+	report(!got_interrupt, "no interrupt on single step trap");
+	got_interrupt = false;
+	handle_exception(0x700, NULL, NULL);
+
+	msr = mfmsr() | MSR_SE;
+	mtspr(SPR_SRR0, (unsigned long)trace_rfid);
+	mtspr(SPR_SRR1, mfmsr());
+	asm volatile(
+	"	mtmsr	%0		\n"
+	"	rfid			\n"
+	".global trace_rfid		\n"
+	"trace_rfid:			\n"
+	: : "r"(msr) : "memory");
+
+	report(!got_interrupt, "no interrupt on single step rfid");
+	got_interrupt = false;
+	handle_exception(0xd00, NULL, NULL);
+
+	report_prefix_pop();
+}
+
+
+int main(int argc, char **argv)
+{
+	report_prefix_push("interrupts");
+
+	test_mce();
+	test_dseg();
+	test_illegal();
+	test_dec();
+	test_sc();
+	test_trace();
+
+	report_prefix_pop();
+
+	return report_summary();
+}
diff --git a/powerpc/unittests.cfg b/powerpc/unittests.cfg
index 5d6ba852..1ae9a00e 100644
--- a/powerpc/unittests.cfg
+++ b/powerpc/unittests.cfg
@@ -69,6 +69,9 @@  groups = rtas
 [emulator]
 file = emulator.elf
 
+[interrupts]
+file = interrupts.elf
+
 [h_cede_tm]
 file = tm.elf
 machine = pseries