[kvm-unit-tests,2/2] powerpc: Add Special Purpose Register persistency test

Submitted by Thomas Huth on March 9, 2017, 5:27 p.m.

Details

Message ID 1489080427-27103-3-git-send-email-thuth@redhat.com
State Superseded
Headers show

Commit Message

Thomas Huth March 9, 2017, 5:27 p.m.
This test has two purposes: First, check whether the hypervisor can be
destabilized by writing random values into the SPRs of the PowerPC CPU
(this indeed revealed a bug last year, see CVE-2016-3044).
Second, this test can be used to check whether the SPRs are synchronized
properly between the KVM host CPU and QEMU, e.g. when migrating the VM
from one QEMU instance to another.
The test first fills the various SPRs with some non-zero value, then reads
the values back into a first array. It then either sleeps a short period
of time (for testing without migration, in the hope that we're rescheduled
on another host CPU), or it waits for a key or NMI (with the '-w' option)
so that it is possible to migrate the VM before continuing. The test then
finally reads the values from the SPRs back into another array and then
compares them with the initial values.
Currently the test only supports the SPRs from the PowerISA v2.01
(PowerPC 970) and PowerISA v2.07 specification (i.e. POWER8 CPUs),
but other versions should be pretty easy to add later.

Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 powerpc/Makefile.common |   3 +-
 powerpc/cstart64.S      |   2 +
 powerpc/sprs.c          | 303 ++++++++++++++++++++++++++++++++++++++++++++++++
 powerpc/unittests.cfg   |   5 +
 4 files changed, 312 insertions(+), 1 deletion(-)
 create mode 100644 powerpc/sprs.c

Comments

Cédric Le Goater March 10, 2017, 8:48 a.m.
On 03/09/2017 06:27 PM, Thomas Huth wrote:
> This test has two purposes: First, check whether the hypervisor can be
> destabilized by writing random values into the SPRs of the PowerPC CPU
> (this indeed revealed a bug last year, see CVE-2016-3044).
> Second, this test can be used to check whether the SPRs are synchronized
> properly between the KVM host CPU and QEMU, e.g. when migrating the VM
> from one QEMU instance to another.
> The test first fills the various SPRs with some non-zero value, then reads
> the values back into a first array. It then either sleeps a short period
> of time (for testing without migration, in the hope that we're rescheduled
> on another host CPU), or it waits for a key or NMI (with the '-w' option)
> so that it is possible to migrate the VM before continuing. The test then
> finally reads the values from the SPRs back into another array and then
> compares them with the initial values.
> Currently the test only supports the SPRs from the PowerISA v2.01
> (PowerPC 970) and PowerISA v2.07 specification (i.e. POWER8 CPUs),
> but other versions should be pretty easy to add later.
> 
> Signed-off-by: Thomas Huth <thuth@redhat.com>

It looks good to me. I gave it a try and it worked fine with some
extra tuning in the migration script.  

LDFLAGS needs fix for binutils 2.28 but that is another issue.

Tested-by: Cédric Le Goater <clg@kaod.org>

C.

> ---
>  powerpc/Makefile.common |   3 +-
>  powerpc/cstart64.S      |   2 +
>  powerpc/sprs.c          | 303 ++++++++++++++++++++++++++++++++++++++++++++++++
>  powerpc/unittests.cfg   |   5 +
>  4 files changed, 312 insertions(+), 1 deletion(-)
>  create mode 100644 powerpc/sprs.c
> 
> diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
> index 37f8caa..92809a5 100644
> --- a/powerpc/Makefile.common
> +++ b/powerpc/Makefile.common
> @@ -9,7 +9,8 @@ tests-common = \
>  	$(TEST_DIR)/spapr_hcall.elf \
>  	$(TEST_DIR)/rtas.elf \
>  	$(TEST_DIR)/emulator.elf \
> -	$(TEST_DIR)/tm.elf
> +	$(TEST_DIR)/tm.elf \
> +	$(TEST_DIR)/sprs.elf
>  
>  tests-all = $(tests-common) $(tests)
>  all: $(TEST_DIR)/boot_rom.bin $(tests-all)
> diff --git a/powerpc/cstart64.S b/powerpc/cstart64.S
> index 2204e3b..ec673b3 100644
> --- a/powerpc/cstart64.S
> +++ b/powerpc/cstart64.S
> @@ -247,6 +247,8 @@ call_handler:
>  	.globl __start_interrupts
>  __start_interrupts:
>  
> +VECTOR(0x100)
> +VECTOR(0x200)
>  VECTOR(0x300)
>  VECTOR(0x400)
>  VECTOR(0x500)
> diff --git a/powerpc/sprs.c b/powerpc/sprs.c
> new file mode 100644
> index 0000000..7792db4
> --- /dev/null
> +++ b/powerpc/sprs.c
> @@ -0,0 +1,303 @@
> +/*
> + * Test Special Purpose Registers
> + *
> + * Copyright 2017  Thomas Huth, Red Hat Inc.
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + *
> + * The basic idea of this test is to check whether the contents of the Special
> + * Purpose Registers (SPRs) are preserved correctly during migration. So we
> + * fill in the SPRs with a well-known value, read the values back (since not
> + * all bits might be retained in the SPRs), then wait for a key or NMI (if the
> + * '-w' option has been specified) so that the user has a chance to migrate the
> + * VM. Alternatively, the test can also simply sleep a little bit with the
> + * H_CEDE hypercall, in the hope that we'll get scheduled to another host CPU
> + * and thus register contents might have changed, too (in case of bugs).
> + * Finally, we read back the values from the SPRs and compare them with the
> + * values before the migration. Mismatches are reported as test failures.
> + * Note that we do not test all SPRs since some of the registers change their
> + * content automatically, and some are only accessible with hypervisor privi-
> + * ledges or have bad side effects, so we have to omit those registers.
> + */
> +#include <libcflat.h>
> +#include <util.h>
> +#include <alloc.h>
> +#include <asm/handlers.h>
> +#include <asm/hcall.h>
> +#include <asm/processor.h>
> +
> +#define mfspr(nr) ({ \
> +	uint64_t ret; \
> +	asm volatile("mfspr %0,%1" : "=r"(ret) : "i"(nr)); \
> +	ret; \
> +})
> +
> +#define mtspr(nr, val) \
> +	asm volatile("mtspr %0,%1" : : "i"(nr), "r"(val))
> +
> +uint64_t before[1024], after[1024];
> +
> +volatile int nmi_occurred;
> +
> +static void nmi_handler(struct pt_regs *regs __unused, void *opaque __unused)
> +{
> +	nmi_occurred = 1;
> +}
> +
> +static int h_get_term_char(uint64_t termno)
> +{
> +	register uint64_t r3 asm("r3") = 0x54; /* H_GET_TERM_CHAR */
> +	register uint64_t r4 asm("r4") = termno;
> +	register uint64_t r5 asm("r5");
> +
> +	asm volatile (" sc 1 "	: "+r"(r3), "+r"(r4), "=r"(r5)
> +				: "r"(r3),  "r"(r4));
> +
> +	return r3 == H_SUCCESS && r4 > 0 ? r5 >> 48 : 0;
> +}
> +
> +/* Common SPRs for all PowerPC CPUs */
> +static void set_sprs_common(uint64_t val)
> +{
> +	mtspr(9, val);		/* CTR */
> +	// mtspr(273, val);	/* SPRG1 */  /* Used by our exception handler */
> +	mtspr(274, val);	/* SPRG2 */
> +	mtspr(275, val);	/* SPRG3 */
> +}
> +
> +/* SPRs from PowerPC Operating Environment Architecture, Book III, Vers. 2.01 */
> +static void set_sprs_book3s_201(uint64_t val)
> +{
> +	mtspr(18, val);		/* DSISR */
> +	mtspr(19, val);		/* DAR */
> +	mtspr(152, val);	/* CTRL */
> +	mtspr(256, val);	/* VRSAVE */
> +	mtspr(786, val);	/* MMCRA */
> +	mtspr(795, val);	/* MMCR0 */
> +	mtspr(798, val);	/* MMCR1 */
> +}
> +
> +/* SPRs from PowerISA 2.07 Book III-S */
> +static void set_sprs_book3s_207(uint64_t val)
> +{
> +	mtspr(3, val);		/* DSCR */
> +	mtspr(13, val);		/* AMR */
> +	mtspr(17, val);		/* DSCR */
> +	mtspr(18, val);		/* DSISR */
> +	mtspr(19, val);		/* DAR */
> +	mtspr(29, val);		/* AMR */
> +	mtspr(61, val);		/* IAMR */
> +	// mtspr(152, val);	/* CTRL */  /* TODO: Needs a fix in KVM */
> +	mtspr(153, val);	/* FSCR */
> +	mtspr(157, val);	/* UAMOR */
> +	mtspr(159, val);	/* PSPB */
> +	mtspr(256, val);	/* VRSAVE */
> +	// mtspr(272, val);	/* SPRG0 */ /* Used by our exception handler */
> +	mtspr(769, val);	/* MMCR2 */
> +	mtspr(770, val);	/* MMCRA */
> +	mtspr(771, val);	/* PMC1 */
> +	mtspr(772, val);	/* PMC2 */
> +	mtspr(773, val);	/* PMC3 */
> +	mtspr(774, val);	/* PMC4 */
> +	mtspr(775, val);	/* PMC5 */
> +	mtspr(776, val);	/* PMC6 */
> +	mtspr(779, (val & 0xfffffffffbab3fffULL) | 0xfa0b2070);	/* MMCR0 */
> +	mtspr(784, val);	/* SIER */
> +	mtspr(785, val);	/* MMCR2 */
> +	mtspr(786, val);	/* MMCRA */
> +	mtspr(787, val);	/* PMC1 */
> +	mtspr(788, val);	/* PMC2 */
> +	mtspr(789, val);	/* PMC3 */
> +	mtspr(790, val);	/* PMC4 */
> +	mtspr(791, val);	/* PMC5 */
> +	mtspr(792, val);	/* PMC6 */
> +	mtspr(795, (val & 0xfffffffffbab3fffULL) | 0xfa0b2070);	/* MMCR0 */
> +	mtspr(796, val);	/* SIAR */
> +	mtspr(797, val);	/* SDAR */
> +	mtspr(798, val);	/* MMCR1 */
> +	mtspr(800, val);	/* BESCRS */
> +	mtspr(801, val);	/* BESCCRSU */
> +	mtspr(802, val);	/* BESCRR */
> +	mtspr(803, val);	/* BESCRRU */
> +	mtspr(804, val);	/* EBBHR */
> +	mtspr(805, val);	/* EBBRR */
> +	mtspr(806, val);	/* BESCR */
> +	mtspr(815, val);	/* TAR */
> +}
> +
> +static void set_sprs(uint64_t val)
> +{
> +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> +
> +	set_sprs_common(val);
> +
> +	switch (pvr >> 16) {
> +	case 0x39:			/* PPC970 */
> +	case 0x3C:			/* PPC970FX */
> +	case 0x44:			/* PPC970MP */
> +		set_sprs_book3s_201(val);
> +		break;
> +	case 0x4b:			/* POWER8E */
> +	case 0x4c:			/* POWER8NVL */
> +	case 0x4d:			/* POWER8 */
> +		set_sprs_book3s_207(val);
> +		break;
> +	default:
> +		puts("Warning: Unknown processor version!\n");
> +	}
> +}
> +
> +static void get_sprs_common(uint64_t *v)
> +{
> +	v[9] = mfspr(9);	/* CTR */
> +	// v[273] = mfspr(273);	/* SPRG1 */ /* Used by our exception handler */
> +	v[274] = mfspr(274);	/* SPRG2 */
> +	v[275] = mfspr(275);	/* SPRG3 */
> +}
> +
> +static void get_sprs_book3s_201(uint64_t *v)
> +{
> +	v[18] = mfspr(18);	/* DSISR */
> +	v[19] = mfspr(19);	/* DAR */
> +	v[136] = mfspr(136);	/* CTRL */
> +	v[256] = mfspr(256);	/* VRSAVE */
> +	v[786] = mfspr(786);	/* MMCRA */
> +	v[795] = mfspr(795);	/* MMCR0 */
> +	v[798] = mfspr(798);	/* MMCR1 */
> +}
> +
> +static void get_sprs_book3s_207(uint64_t *v)
> +{
> +	v[3] = mfspr(3);	/* DSCR */
> +	v[13] = mfspr(13);	/* AMR */
> +	v[17] = mfspr(17);	/* DSCR */
> +	v[18] = mfspr(18);	/* DSISR */
> +	v[19] = mfspr(19);	/* DAR */
> +	v[29] = mfspr(29);	/* AMR */
> +	v[61] = mfspr(61);	/* IAMR */
> +	//v[136] = mfspr(136);	/* CTRL */  /* TODO: Needs a fix in KVM */
> +	v[153] = mfspr(153);	/* FSCR */
> +	v[157] = mfspr(157);	/* UAMOR */
> +	v[159] = mfspr(159);	/* PSPB */
> +	v[256] = mfspr(256);	/* VRSAVE */
> +	v[259] = mfspr(259);	/* SPRG3 (read only) */
> +	// v[272] = mfspr(272);	/* SPRG0 */  /* Used by our exception handler */
> +	v[769] = mfspr(769);	/* MMCR2 */
> +	v[770] = mfspr(770);	/* MMCRA */
> +	v[771] = mfspr(771);	/* PMC1 */
> +	v[772] = mfspr(772);	/* PMC2 */
> +	v[773] = mfspr(773);	/* PMC3 */
> +	v[774] = mfspr(774);	/* PMC4 */
> +	v[775] = mfspr(775);	/* PMC5 */
> +	v[776] = mfspr(776);	/* PMC6 */
> +	v[779] = mfspr(779);	/* MMCR0 */
> +	v[780] = mfspr(780);	/* SIAR (read only) */
> +	v[781] = mfspr(781);	/* SDAR (read only) */
> +	v[782] = mfspr(782);	/* MMCR1 (read only) */
> +	v[784] = mfspr(784);	/* SIER */
> +	v[785] = mfspr(785);	/* MMCR2 */
> +	v[786] = mfspr(786);	/* MMCRA */
> +	v[787] = mfspr(787);	/* PMC1 */
> +	v[788] = mfspr(788);	/* PMC2 */
> +	v[789] = mfspr(789);	/* PMC3 */
> +	v[790] = mfspr(790);	/* PMC4 */
> +	v[791] = mfspr(791);	/* PMC5 */
> +	v[792] = mfspr(792);	/* PMC6 */
> +	v[795] = mfspr(795);	/* MMCR0 */
> +	v[796] = mfspr(796);	/* SIAR */
> +	v[797] = mfspr(797);	/* SDAR */
> +	v[798] = mfspr(798);	/* MMCR1 */
> +	v[800] = mfspr(800);	/* BESCRS */
> +	v[801] = mfspr(801);	/* BESCCRSU */
> +	v[802] = mfspr(802);	/* BESCRR */
> +	v[803] = mfspr(803);	/* BESCRRU */
> +	v[804] = mfspr(804);	/* EBBHR */
> +	v[805] = mfspr(805);	/* EBBRR */
> +	v[806] = mfspr(806);	/* BESCR */
> +	v[815] = mfspr(815);	/* TAR */
> +}
> +
> +static void get_sprs(uint64_t *v)
> +{
> +	uint32_t pvr = mfspr(287);	/* Processor Version Register */
> +
> +	get_sprs_common(v);
> +
> +	switch (pvr >> 16) {
> +	case 0x39:			/* PPC970 */
> +	case 0x3C:			/* PPC970FX */
> +	case 0x44:			/* PPC970MP */
> +		get_sprs_book3s_201(v);
> +		break;
> +	case 0x4b:			/* POWER8E */
> +	case 0x4c:			/* POWER8NVL */
> +	case 0x4d:			/* POWER8 */
> +		get_sprs_book3s_207(v);
> +		break;
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int i;
> +	bool waitkey = false;
> +	uint64_t pat = 0xcafefacec0debabeULL;
> +	const uint64_t patterns[] = {
> +		0xcafefacec0debabeULL, ~0xcafefacec0debabeULL,
> +		0xAAAA5555AAAA5555ULL, 0x5555AAAA5555AAAAULL,
> +		0x1234567890ABCDEFULL, 0xFEDCBA0987654321ULL,
> +		-1ULL,
> +	};
> +
> +	for (i = 1; i < argc; i++) {
> +		if (!strcmp(argv[i], "-w")) {
> +			waitkey = true;
> +		} else if (!strcmp(argv[i], "-p")) {
> +			i += 1;
> +			if (i >= argc || *argv[i] < '0'
> +			    || *argv[i] >= '0' + ARRAY_SIZE(patterns))
> +				report_abort("Error: bad value for -p");
> +			pat ^= patterns[*argv[i] - '0'];
> +		} else if (!strcmp(argv[i], "-t")) {
> +			/* Randomize with timebase register */
> +			asm volatile("mftb %0" : "=r"(i));
> +			pat ^= i;
> +			asm volatile("mftb %0" : "=r"(i));
> +			pat ^= ~(uint64_t)i << 32;
> +		} else {
> +			report_abort("Warning: Unsupported argument: %s",
> +			             argv[i]);
> +		}
> +	}
> +
> +	printf("Settings SPRs to 0x%lx...\n", pat);
> +	set_sprs(pat);
> +
> +	memset(before, 0, sizeof(before));
> +	memset(after, 0, sizeof(after));
> +
> +	get_sprs(before);
> +
> +	if (waitkey) {
> +		handle_exception(0x100, &nmi_handler, NULL);
> +		puts("Now migrate the VM, then press a key or send NMI...\n");
> +		while (!nmi_occurred && h_get_term_char(0) == 0)
> +			asm volatile(" nop " ::: "memory");
> +	} else {
> +		puts("Sleeping...\n");
> +		handle_exception(0x900, &dec_except_handler, NULL);
> +		asm volatile("mtdec %0" : : "r" (0x3FFFFFFF));
> +		hcall(H_CEDE);
> +	}
> +
> +	get_sprs(after);
> +
> +	puts("Checking SPRs...\n");
> +	for (i = 0; i < 1024; i++) {
> +		if (before[i] != 0 || after[i] != 0)
> +			report("SPR %d:\t0x%016lx <==> 0x%016lx",
> +				before[i] == after[i], i, before[i], after[i]);
> +	}
> +
> +	return report_summary();
> +}
> diff --git a/powerpc/unittests.cfg b/powerpc/unittests.cfg
> index 20dbde6..fb6b70e 100644
> --- a/powerpc/unittests.cfg
> +++ b/powerpc/unittests.cfg
> @@ -59,3 +59,8 @@ file = tm.elf
>  smp = 2,threads=2
>  extra_params = -append "h_cede_tm"
>  groups = nodefault,h_cede_tm
> +
> +[sprs]
> +file = sprs.elf
> +extra_params = -append '-w'
> +groups = migration
> 

--
To unsubscribe from this list: send the line "unsubscribe kvm-ppc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch hide | download patch | download mbox

diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
index 37f8caa..92809a5 100644
--- a/powerpc/Makefile.common
+++ b/powerpc/Makefile.common
@@ -9,7 +9,8 @@  tests-common = \
 	$(TEST_DIR)/spapr_hcall.elf \
 	$(TEST_DIR)/rtas.elf \
 	$(TEST_DIR)/emulator.elf \
-	$(TEST_DIR)/tm.elf
+	$(TEST_DIR)/tm.elf \
+	$(TEST_DIR)/sprs.elf
 
 tests-all = $(tests-common) $(tests)
 all: $(TEST_DIR)/boot_rom.bin $(tests-all)
diff --git a/powerpc/cstart64.S b/powerpc/cstart64.S
index 2204e3b..ec673b3 100644
--- a/powerpc/cstart64.S
+++ b/powerpc/cstart64.S
@@ -247,6 +247,8 @@  call_handler:
 	.globl __start_interrupts
 __start_interrupts:
 
+VECTOR(0x100)
+VECTOR(0x200)
 VECTOR(0x300)
 VECTOR(0x400)
 VECTOR(0x500)
diff --git a/powerpc/sprs.c b/powerpc/sprs.c
new file mode 100644
index 0000000..7792db4
--- /dev/null
+++ b/powerpc/sprs.c
@@ -0,0 +1,303 @@ 
+/*
+ * Test Special Purpose Registers
+ *
+ * Copyright 2017  Thomas Huth, Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ *
+ * The basic idea of this test is to check whether the contents of the Special
+ * Purpose Registers (SPRs) are preserved correctly during migration. So we
+ * fill in the SPRs with a well-known value, read the values back (since not
+ * all bits might be retained in the SPRs), then wait for a key or NMI (if the
+ * '-w' option has been specified) so that the user has a chance to migrate the
+ * VM. Alternatively, the test can also simply sleep a little bit with the
+ * H_CEDE hypercall, in the hope that we'll get scheduled to another host CPU
+ * and thus register contents might have changed, too (in case of bugs).
+ * Finally, we read back the values from the SPRs and compare them with the
+ * values before the migration. Mismatches are reported as test failures.
+ * Note that we do not test all SPRs since some of the registers change their
+ * content automatically, and some are only accessible with hypervisor privi-
+ * ledges or have bad side effects, so we have to omit those registers.
+ */
+#include <libcflat.h>
+#include <util.h>
+#include <alloc.h>
+#include <asm/handlers.h>
+#include <asm/hcall.h>
+#include <asm/processor.h>
+
+#define mfspr(nr) ({ \
+	uint64_t ret; \
+	asm volatile("mfspr %0,%1" : "=r"(ret) : "i"(nr)); \
+	ret; \
+})
+
+#define mtspr(nr, val) \
+	asm volatile("mtspr %0,%1" : : "i"(nr), "r"(val))
+
+uint64_t before[1024], after[1024];
+
+volatile int nmi_occurred;
+
+static void nmi_handler(struct pt_regs *regs __unused, void *opaque __unused)
+{
+	nmi_occurred = 1;
+}
+
+static int h_get_term_char(uint64_t termno)
+{
+	register uint64_t r3 asm("r3") = 0x54; /* H_GET_TERM_CHAR */
+	register uint64_t r4 asm("r4") = termno;
+	register uint64_t r5 asm("r5");
+
+	asm volatile (" sc 1 "	: "+r"(r3), "+r"(r4), "=r"(r5)
+				: "r"(r3),  "r"(r4));
+
+	return r3 == H_SUCCESS && r4 > 0 ? r5 >> 48 : 0;
+}
+
+/* Common SPRs for all PowerPC CPUs */
+static void set_sprs_common(uint64_t val)
+{
+	mtspr(9, val);		/* CTR */
+	// mtspr(273, val);	/* SPRG1 */  /* Used by our exception handler */
+	mtspr(274, val);	/* SPRG2 */
+	mtspr(275, val);	/* SPRG3 */
+}
+
+/* SPRs from PowerPC Operating Environment Architecture, Book III, Vers. 2.01 */
+static void set_sprs_book3s_201(uint64_t val)
+{
+	mtspr(18, val);		/* DSISR */
+	mtspr(19, val);		/* DAR */
+	mtspr(152, val);	/* CTRL */
+	mtspr(256, val);	/* VRSAVE */
+	mtspr(786, val);	/* MMCRA */
+	mtspr(795, val);	/* MMCR0 */
+	mtspr(798, val);	/* MMCR1 */
+}
+
+/* SPRs from PowerISA 2.07 Book III-S */
+static void set_sprs_book3s_207(uint64_t val)
+{
+	mtspr(3, val);		/* DSCR */
+	mtspr(13, val);		/* AMR */
+	mtspr(17, val);		/* DSCR */
+	mtspr(18, val);		/* DSISR */
+	mtspr(19, val);		/* DAR */
+	mtspr(29, val);		/* AMR */
+	mtspr(61, val);		/* IAMR */
+	// mtspr(152, val);	/* CTRL */  /* TODO: Needs a fix in KVM */
+	mtspr(153, val);	/* FSCR */
+	mtspr(157, val);	/* UAMOR */
+	mtspr(159, val);	/* PSPB */
+	mtspr(256, val);	/* VRSAVE */
+	// mtspr(272, val);	/* SPRG0 */ /* Used by our exception handler */
+	mtspr(769, val);	/* MMCR2 */
+	mtspr(770, val);	/* MMCRA */
+	mtspr(771, val);	/* PMC1 */
+	mtspr(772, val);	/* PMC2 */
+	mtspr(773, val);	/* PMC3 */
+	mtspr(774, val);	/* PMC4 */
+	mtspr(775, val);	/* PMC5 */
+	mtspr(776, val);	/* PMC6 */
+	mtspr(779, (val & 0xfffffffffbab3fffULL) | 0xfa0b2070);	/* MMCR0 */
+	mtspr(784, val);	/* SIER */
+	mtspr(785, val);	/* MMCR2 */
+	mtspr(786, val);	/* MMCRA */
+	mtspr(787, val);	/* PMC1 */
+	mtspr(788, val);	/* PMC2 */
+	mtspr(789, val);	/* PMC3 */
+	mtspr(790, val);	/* PMC4 */
+	mtspr(791, val);	/* PMC5 */
+	mtspr(792, val);	/* PMC6 */
+	mtspr(795, (val & 0xfffffffffbab3fffULL) | 0xfa0b2070);	/* MMCR0 */
+	mtspr(796, val);	/* SIAR */
+	mtspr(797, val);	/* SDAR */
+	mtspr(798, val);	/* MMCR1 */
+	mtspr(800, val);	/* BESCRS */
+	mtspr(801, val);	/* BESCCRSU */
+	mtspr(802, val);	/* BESCRR */
+	mtspr(803, val);	/* BESCRRU */
+	mtspr(804, val);	/* EBBHR */
+	mtspr(805, val);	/* EBBRR */
+	mtspr(806, val);	/* BESCR */
+	mtspr(815, val);	/* TAR */
+}
+
+static void set_sprs(uint64_t val)
+{
+	uint32_t pvr = mfspr(287);	/* Processor Version Register */
+
+	set_sprs_common(val);
+
+	switch (pvr >> 16) {
+	case 0x39:			/* PPC970 */
+	case 0x3C:			/* PPC970FX */
+	case 0x44:			/* PPC970MP */
+		set_sprs_book3s_201(val);
+		break;
+	case 0x4b:			/* POWER8E */
+	case 0x4c:			/* POWER8NVL */
+	case 0x4d:			/* POWER8 */
+		set_sprs_book3s_207(val);
+		break;
+	default:
+		puts("Warning: Unknown processor version!\n");
+	}
+}
+
+static void get_sprs_common(uint64_t *v)
+{
+	v[9] = mfspr(9);	/* CTR */
+	// v[273] = mfspr(273);	/* SPRG1 */ /* Used by our exception handler */
+	v[274] = mfspr(274);	/* SPRG2 */
+	v[275] = mfspr(275);	/* SPRG3 */
+}
+
+static void get_sprs_book3s_201(uint64_t *v)
+{
+	v[18] = mfspr(18);	/* DSISR */
+	v[19] = mfspr(19);	/* DAR */
+	v[136] = mfspr(136);	/* CTRL */
+	v[256] = mfspr(256);	/* VRSAVE */
+	v[786] = mfspr(786);	/* MMCRA */
+	v[795] = mfspr(795);	/* MMCR0 */
+	v[798] = mfspr(798);	/* MMCR1 */
+}
+
+static void get_sprs_book3s_207(uint64_t *v)
+{
+	v[3] = mfspr(3);	/* DSCR */
+	v[13] = mfspr(13);	/* AMR */
+	v[17] = mfspr(17);	/* DSCR */
+	v[18] = mfspr(18);	/* DSISR */
+	v[19] = mfspr(19);	/* DAR */
+	v[29] = mfspr(29);	/* AMR */
+	v[61] = mfspr(61);	/* IAMR */
+	//v[136] = mfspr(136);	/* CTRL */  /* TODO: Needs a fix in KVM */
+	v[153] = mfspr(153);	/* FSCR */
+	v[157] = mfspr(157);	/* UAMOR */
+	v[159] = mfspr(159);	/* PSPB */
+	v[256] = mfspr(256);	/* VRSAVE */
+	v[259] = mfspr(259);	/* SPRG3 (read only) */
+	// v[272] = mfspr(272);	/* SPRG0 */  /* Used by our exception handler */
+	v[769] = mfspr(769);	/* MMCR2 */
+	v[770] = mfspr(770);	/* MMCRA */
+	v[771] = mfspr(771);	/* PMC1 */
+	v[772] = mfspr(772);	/* PMC2 */
+	v[773] = mfspr(773);	/* PMC3 */
+	v[774] = mfspr(774);	/* PMC4 */
+	v[775] = mfspr(775);	/* PMC5 */
+	v[776] = mfspr(776);	/* PMC6 */
+	v[779] = mfspr(779);	/* MMCR0 */
+	v[780] = mfspr(780);	/* SIAR (read only) */
+	v[781] = mfspr(781);	/* SDAR (read only) */
+	v[782] = mfspr(782);	/* MMCR1 (read only) */
+	v[784] = mfspr(784);	/* SIER */
+	v[785] = mfspr(785);	/* MMCR2 */
+	v[786] = mfspr(786);	/* MMCRA */
+	v[787] = mfspr(787);	/* PMC1 */
+	v[788] = mfspr(788);	/* PMC2 */
+	v[789] = mfspr(789);	/* PMC3 */
+	v[790] = mfspr(790);	/* PMC4 */
+	v[791] = mfspr(791);	/* PMC5 */
+	v[792] = mfspr(792);	/* PMC6 */
+	v[795] = mfspr(795);	/* MMCR0 */
+	v[796] = mfspr(796);	/* SIAR */
+	v[797] = mfspr(797);	/* SDAR */
+	v[798] = mfspr(798);	/* MMCR1 */
+	v[800] = mfspr(800);	/* BESCRS */
+	v[801] = mfspr(801);	/* BESCCRSU */
+	v[802] = mfspr(802);	/* BESCRR */
+	v[803] = mfspr(803);	/* BESCRRU */
+	v[804] = mfspr(804);	/* EBBHR */
+	v[805] = mfspr(805);	/* EBBRR */
+	v[806] = mfspr(806);	/* BESCR */
+	v[815] = mfspr(815);	/* TAR */
+}
+
+static void get_sprs(uint64_t *v)
+{
+	uint32_t pvr = mfspr(287);	/* Processor Version Register */
+
+	get_sprs_common(v);
+
+	switch (pvr >> 16) {
+	case 0x39:			/* PPC970 */
+	case 0x3C:			/* PPC970FX */
+	case 0x44:			/* PPC970MP */
+		get_sprs_book3s_201(v);
+		break;
+	case 0x4b:			/* POWER8E */
+	case 0x4c:			/* POWER8NVL */
+	case 0x4d:			/* POWER8 */
+		get_sprs_book3s_207(v);
+		break;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int i;
+	bool waitkey = false;
+	uint64_t pat = 0xcafefacec0debabeULL;
+	const uint64_t patterns[] = {
+		0xcafefacec0debabeULL, ~0xcafefacec0debabeULL,
+		0xAAAA5555AAAA5555ULL, 0x5555AAAA5555AAAAULL,
+		0x1234567890ABCDEFULL, 0xFEDCBA0987654321ULL,
+		-1ULL,
+	};
+
+	for (i = 1; i < argc; i++) {
+		if (!strcmp(argv[i], "-w")) {
+			waitkey = true;
+		} else if (!strcmp(argv[i], "-p")) {
+			i += 1;
+			if (i >= argc || *argv[i] < '0'
+			    || *argv[i] >= '0' + ARRAY_SIZE(patterns))
+				report_abort("Error: bad value for -p");
+			pat ^= patterns[*argv[i] - '0'];
+		} else if (!strcmp(argv[i], "-t")) {
+			/* Randomize with timebase register */
+			asm volatile("mftb %0" : "=r"(i));
+			pat ^= i;
+			asm volatile("mftb %0" : "=r"(i));
+			pat ^= ~(uint64_t)i << 32;
+		} else {
+			report_abort("Warning: Unsupported argument: %s",
+			             argv[i]);
+		}
+	}
+
+	printf("Settings SPRs to 0x%lx...\n", pat);
+	set_sprs(pat);
+
+	memset(before, 0, sizeof(before));
+	memset(after, 0, sizeof(after));
+
+	get_sprs(before);
+
+	if (waitkey) {
+		handle_exception(0x100, &nmi_handler, NULL);
+		puts("Now migrate the VM, then press a key or send NMI...\n");
+		while (!nmi_occurred && h_get_term_char(0) == 0)
+			asm volatile(" nop " ::: "memory");
+	} else {
+		puts("Sleeping...\n");
+		handle_exception(0x900, &dec_except_handler, NULL);
+		asm volatile("mtdec %0" : : "r" (0x3FFFFFFF));
+		hcall(H_CEDE);
+	}
+
+	get_sprs(after);
+
+	puts("Checking SPRs...\n");
+	for (i = 0; i < 1024; i++) {
+		if (before[i] != 0 || after[i] != 0)
+			report("SPR %d:\t0x%016lx <==> 0x%016lx",
+				before[i] == after[i], i, before[i], after[i]);
+	}
+
+	return report_summary();
+}
diff --git a/powerpc/unittests.cfg b/powerpc/unittests.cfg
index 20dbde6..fb6b70e 100644
--- a/powerpc/unittests.cfg
+++ b/powerpc/unittests.cfg
@@ -59,3 +59,8 @@  file = tm.elf
 smp = 2,threads=2
 extra_params = -append "h_cede_tm"
 groups = nodefault,h_cede_tm
+
+[sprs]
+file = sprs.elf
+extra_params = -append '-w'
+groups = migration