diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
index caa807f2..697f5735 100644
--- a/powerpc/Makefile.common
+++ b/powerpc/Makefile.common
@@ -10,6 +10,7 @@ tests-common = \
 	$(TEST_DIR)/spapr_hcall.elf \
 	$(TEST_DIR)/rtas.elf \
 	$(TEST_DIR)/emulator.elf \
+	$(TEST_DIR)/atomics.elf \
 	$(TEST_DIR)/tm.elf \
 	$(TEST_DIR)/smp.elf \
 	$(TEST_DIR)/sprs.elf \
diff --git a/powerpc/atomics.c b/powerpc/atomics.c
new file mode 100644
index 00000000..f2e7a3e3
--- /dev/null
+++ b/powerpc/atomics.c
@@ -0,0 +1,190 @@
+/*
+ * Test some powerpc instructions
+ */
+#include <stdint.h>
+#include <libcflat.h>
+#include <migrate.h>
+#include <asm/processor.h>
+
+static int verbose;
+
+#define RSV_SIZE 128
+
+static uint8_t granule[RSV_SIZE] __attribute((__aligned__(RSV_SIZE)));
+
+static void test_lwarx_stwcx(void)
+{
+	unsigned int *var = (unsigned int *)granule;
+	unsigned int old;
+	unsigned int result;
+
+	report_prefix_push("lwarx/stwcx.");
+
+	*var = 0;
+	asm volatile ("1:"
+		      "lwarx	%0,0,%2;"
+		      "stwcx.	%1,0,%2;"
+		      "bne-	1b;"
+		      : "=&r"(old) : "r"(1), "r"(var) : "cr0", "memory");
+	report(old == 0 && *var == 1, "simple update");
+
+	*var = 0;
+	asm volatile ("li	%0,0;"
+		      "stwcx.	%1,0,%2;"
+		      "stwcx.	%1,0,%2;"
+		      "bne-	1f;"
+		      "li	%0,1;"
+		      "1:"
+		      : "=&r"(result)
+		      : "r"(1), "r"(var) : "cr0", "memory");
+	report(result == 0 && *var == 0, "failed stwcx. (no reservation)");
+
+	*var = 0;
+	asm volatile ("li	%0,0;"
+		      "lwarx	%1,0,%4;"
+		      "stw	%3,0(%4);"
+		      "stwcx.	%2,0,%4;"
+		      "bne-	1f;"
+		      "li	%0,1;"
+		      "1:"
+		      : "=&r"(result), "=&r"(old)
+		      : "r"(1), "r"(2), "r"(var) : "cr0", "memory");
+	report(result == 0 && *var == 2, "failed stwcx. (intervening store)");
+
+	report_prefix_pop();
+}
+
+static void test_lqarx_stqcx(void)
+{
+	union {
+		__int128_t var;
+		struct {
+#if  __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+			unsigned long var1;
+			unsigned long var2;
+#else
+			unsigned long var2;
+			unsigned long var1;
+#endif
+		};
+	} var __attribute__((aligned(16)));
+	register unsigned long new1 asm("r8");
+	register unsigned long new2 asm("r9");
+	register unsigned long old1 asm("r10");
+	register unsigned long old2 asm("r11");
+	unsigned int result;
+
+	var.var1 = 1;
+	var.var2 = 2;
+
+	(void)new2;
+	(void)old2;
+
+	report_prefix_push("lqarx/stqcx.");
+
+	old1 = 0;
+	old2 = 0;
+	new1 = 3;
+	new2 = 4;
+	asm volatile ("1:"
+		      "lqarx	%0,0,%4;"
+		      "stqcx.	%2,0,%4;"
+		      "bne-	1b;"
+		      : "=&r"(old1), "=&r"(old2)
+		      : "r"(new1), "r"(new2), "r"(&var)
+		      : "cr0", "memory");
+
+	report(old1 == 2 && old2 == 1 && var.var1 == 4 && var.var2 == 3,
+			"simple update");
+
+	var.var1 = 1;
+	var.var2 = 2;
+	new1 = 3;
+	new2 = 4;
+	asm volatile ("li	%0,0;"
+		      "stqcx.	%1,0,%3;"
+		      "stqcx.	%1,0,%3;"
+		      "bne-	1f;"
+		      "li	%0,1;"
+		      "1:"
+		      : "=&r"(result)
+		      : "r"(new1), "r"(new2), "r"(&var)
+		      : "cr0", "memory");
+	report(result == 0 && var.var1 == 1 && var.var2 == 2,
+			"failed stqcx. (no reservation)");
+
+	var.var1 = 1;
+	var.var2 = 2;
+	new1 = 3;
+	new2 = 4;
+	asm volatile ("li	%0,0;"
+		      "lqarx	%1,0,%6;"
+		      "std	%5,0(%6);"
+		      "stqcx.	%3,0,%6;"
+		      "bne-	1f;"
+		      "li	%0,1;"
+		      "1:"
+		      : "=&r"(result), "=&r"(old1), "=&r"(old2)
+		      : "r"(new1), "r"(new2), "r"(0), "r"(&var)
+		      : "cr0", "memory");
+	report(result == 0 && (var.var1 == 0 || var.var2 == 0),
+			"failed stqcx. (intervening store)");
+
+	report_prefix_pop();
+}
+
+static void test_migrate_reserve(void)
+{
+	unsigned int *var = (unsigned int *)granule;
+	unsigned int old;
+
+	/* ensure incorrect value does not succeed */
+	report_prefix_push("migrate reservation");
+
+	*var = 0x12345;
+	asm volatile ("lwarx	%0,0,%1" : "=&r"(old) : "r"(var) : "memory");
+	migrate();
+	*var = 0;
+	asm volatile ("stwcx.	%0,0,%1" : : "r"(0xbad), "r"(var) : "cr0", "memory");
+	report(*var == 0, "migrate reserve update fails with concurrently modified value");
+
+#if 0
+XXX this will not work with KVM and for QEMU it only works with record/replay -
+something the harness is not equipped to test.
+
+	/* ensure reservation succeds */
+	report_prefix_push("migrate reservation");
+
+	*var = 0x12345;
+	asm volatile ("lwarx	%0,0,%1" : "=&r"(old) : "r"(var) : "memory");
+	migrate();
+	asm volatile ("stwcx.	%0,0,%1" : : "r"(0xf070), "r"(var) : "cr0", "memory");
+	report(*var == 0xf070, "migrate reserve update succeeds with unmodified value");
+#endif
+}
+
+int main(int argc, char **argv)
+{
+	int i;
+	bool migrate = false;
+
+	for (i = 1; i < argc; i++) {
+		if (strcmp(argv[i], "-v") == 0) {
+			verbose = 1;
+		}
+		if (strcmp(argv[i], "-m") == 0) {
+			migrate = true;
+		}
+	}
+
+	report_prefix_push("atomics");
+
+	test_lwarx_stwcx();
+	test_lqarx_stqcx();
+	if (migrate)
+		test_migrate_reserve();
+
+	report_prefix_pop();
+
+	return report_summary();
+}
diff --git a/powerpc/unittests.cfg b/powerpc/unittests.cfg
index 727712bb..9f71ea93 100644
--- a/powerpc/unittests.cfg
+++ b/powerpc/unittests.cfg
@@ -80,6 +80,15 @@ smp = 2
 file = smp.elf
 smp = 8,threads=4
 
+[atomics]
+file = atomics.elf
+
+[atomics-migration]
+file = atomics.elf
+machine = pseries
+extra_params = -append '-m'
+groups = migration
+
 [h_cede_tm]
 file = tm.elf
 machine = pseries
