diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug
index f4961fbcb48d..d75c165538e9 100644
--- a/arch/powerpc/Kconfig.debug
+++ b/arch/powerpc/Kconfig.debug
@@ -56,6 +56,11 @@ config CODE_PATCHING_SELFTEST
 	bool "Run self-tests of the code-patching code"
 	depends on DEBUG_KERNEL
 
+config EMULATE_STEP_SELFTEST
+	bool "Run self-tests of the instruction emulation code"
+	depends on PPC64 && DEBUG_KERNEL
+	default n
+
 config JUMP_LABEL_FEATURE_CHECKS
 	bool "Enable use of jump label for cpu/mmu_has_feature()"
 	depends on JUMP_LABEL
diff --git a/arch/powerpc/lib/Makefile b/arch/powerpc/lib/Makefile
index 3bf9fc6fd36c..c4485e004838 100644
--- a/arch/powerpc/lib/Makefile
+++ b/arch/powerpc/lib/Makefile
@@ -31,6 +31,7 @@ obj64-y	+= copypage_64.o copyuser_64.o mem_64.o hweight_64.o \
 obj64-$(CONFIG_SMP)	+= locks.o
 obj64-$(CONFIG_ALTIVEC)	+= vmx-helper.o
 obj64-$(CONFIG_KPROBES_SANITY_TEST) += test_emulate_step.o
+obj64-$(CONFIG_EMULATE_STEP_SELFTEST)	+= exec_test_instr.o sstep_tests.o
 
 obj-y			+= checksum_$(BITS).o checksum_wrappers.o \
 			   string_$(BITS).o memcmp_$(BITS).o
diff --git a/arch/powerpc/lib/exec_test_instr.S b/arch/powerpc/lib/exec_test_instr.S
new file mode 100644
index 000000000000..217e83415eaf
--- /dev/null
+++ b/arch/powerpc/lib/exec_test_instr.S
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Non-emulated single-stepping support limited to basic integer ops for
+ * validating the instruction emulation infrastructure.
+ *
+ * Copyright (C) 2019 IBM Corporation
+ */
+
+#include <asm/asm-offsets.h>
+#include <asm/ppc_asm.h>
+#include <linux/errno.h>
+
+/* int exec_instr(struct pt_regs *regs) */
+_GLOBAL(exec_instr)
+
+	/*
+	 * Stack frame layout (INT_FRAME_SIZE bytes)
+	 *   In-memory pt_regs	(SP + STACK_FRAME_OVERHEAD)
+	 *   Scratch space	(SP + 8)
+	 *   Back chain		(SP + 0)
+	 */
+
+	/*
+	 * Allocate a new stack frame with enough space to hold the register
+	 * states in an in-memory pt_regs and also create the back chain to
+	 * the caller's stack frame.
+	 */
+	stdu	r1, -INT_FRAME_SIZE(r1)
+
+	/*
+	 * Save non-volatile GPRs on stack. This includes TOC pointer (GPR2)
+	 * and local variables (GPR14 to GPR31). The register for the pt_regs
+	 * parameter (GPR3) is saved additionally to ensure that the resulting
+	 * register state can still be saved even if GPR3 gets overwritten
+	 * when loading the initial register state for the test instruction.
+	 * The stack pointer (GPR1) and the thread pointer (GPR13) are not
+	 * saved as these should not be modified anyway.
+	 */
+	SAVE_2GPRS(2, r1)
+	SAVE_NVGPRS(r1)
+
+	/*
+	 * Save LR on stack to ensure that the return address is available
+	 * even if it gets overwritten by the test instruction.
+	 */
+	mflr	r0
+	std	r0, _LINK(r1)
+
+	/*
+	 * Save CR on stack. For simplicity, the entire register is saved
+	 * even though only fields 2 to 4 are non-volatile.
+	 */
+	mfcr	r0
+	std	r0, _CCR(r1)
+
+	/*
+	 * Load register state for the test instruction without touching the
+	 * critical non-volatile registers. The register state is passed as a
+	 * pointer to a pt_regs instance.
+	 */
+	subi	r31, r3, GPR0
+
+	/* Load LR from pt_regs */
+	ld	r0, _LINK(r31)
+	mtlr	r0
+
+	/* Load CR from pt_regs */
+	ld	r0, _CCR(r31)
+	mtcr	r0
+
+	/* Load XER from pt_regs */
+	ld	r0, _XER(r31)
+	mtxer	r0
+
+	/* Load GPRs from pt_regs */
+	REST_GPR(0, r31)
+	REST_10GPRS(2, r31)
+	REST_GPR(12, r31)
+	REST_NVGPRS(r31)
+
+	.global	exec_instr_execute
+exec_instr_execute:
+	/* Placeholder for the test instruction */
+1:	nop
+
+	/*
+	 * Since GPR3 is overwritten, temporarily restore it back to its
+	 * original state, i.e. the pointer to pt_regs, to ensure that the
+	 * resulting register state can be saved. Before doing this, a copy
+	 * of it is created in the scratch space which is used later on to
+	 * save it to pt_regs.
+	 */
+	std	r3, 8(r1)
+	REST_GPR(3, r1)
+
+	/* Save resulting GPR state to pt_regs */
+	subi	r3, r3, GPR0
+	SAVE_GPR(0, r3)
+	SAVE_GPR(2, r3)
+	SAVE_8GPRS(4, r3)
+	SAVE_GPR(12, r3)
+	SAVE_NVGPRS(r3)
+
+	/* Save resulting LR to pt_regs */
+	mflr	r0
+	std	r0, _LINK(r3)
+
+	/* Save resulting CR to pt_regs */
+	mfcr	r0
+	std	r0, _CCR(r3)
+
+	/* Save resulting XER to pt_regs */
+	mfxer	r0
+	std	r0, _XER(r3)
+
+	/* Restore resulting GPR3 from scratch space and save it to pt_regs */
+	ld	r0, 8(r1)
+	std	r0, GPR3(r3)
+
+	/* Set return value to denote execution success */
+	li	r3, 0
+
+	/* Continue */
+	b	3f
+
+	/* Set return value to denote execution failure */
+2:	li	r3, -EFAULT
+
+	/* Restore the non-volatile GPRs from stack */
+3:	REST_GPR(2, r1)
+	REST_NVGPRS(r1)
+
+	/* Restore LR from stack to be able to return */
+	ld	r0, _LINK(r1)
+	mtlr	r0
+
+	/* Restore CR from stack */
+	ld	r0, _CCR(r1)
+	mtcr	r0
+
+	/* Tear down stack frame */
+	addi	r1, r1, INT_FRAME_SIZE
+
+	/* Return */
+	blr
+
+	/* Setup exception table */
+	EX_TABLE(1b, 2b)
+
+_ASM_NOKPROBE_SYMBOL(exec_instr)
diff --git a/arch/powerpc/lib/sstep_tests.c b/arch/powerpc/lib/sstep_tests.c
new file mode 100644
index 000000000000..a610c778044d
--- /dev/null
+++ b/arch/powerpc/lib/sstep_tests.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Selftests for the instruction emulation infrastructure.
+ *
+ * Copyright (C) 2019 IBM Corporation
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ptrace.h>
+
+#include <asm/code-patching.h>
+#include <asm/ppc-opcode.h>
+#include <asm/sstep.h>
+
+#define MAX_SUBTESTS	16
+#define MAX_INSNS	32
+
+#define PASS	1
+#define FAIL	0
+
+#define IGNORE_GPR(n)	(0x1UL << (n))
+#define IGNORE_XER	(0x1UL << 32)
+#define IGNORE_CCR	(0x1UL << 33)
+
+struct sstep_test {
+	const char *mnemonic;
+	struct {
+		const char *descr;
+		unsigned long flags;
+		unsigned int instr;
+		struct pt_regs regs;
+	} subtests[MAX_SUBTESTS + 1];
+};
+
+static struct sstep_test tests[] = {
+	{
+		.mnemonic = "nop",
+		.subtests =
+		{
+			{
+				.descr = "R0 = LONG_MAX",
+				.instr = PPC_INST_NOP | ___PPC_RA(0) | ___PPC_RS(0) | __PPC_UI(0x0),
+				.regs =
+				{
+					.gpr[0] = LONG_MAX,
+				}
+			}
+		}
+	},
+};
+
+int emulate_instr(struct pt_regs *regs, unsigned int instr)
+{
+	struct instruction_op op;
+
+	if (!regs || !instr)
+		return -EINVAL;
+
+	if (analyse_instr(&op, regs, instr) != 1 || GETTYPE(op.type) != COMPUTE) {
+		pr_info("emulation failed or not supported, opcode = 0x%08x\n", instr);
+		return -EFAULT;
+	}
+
+	emulate_update_regs(regs, &op);
+	return 0;
+}
+
+int execute_instr(struct pt_regs *regs, unsigned int instr)
+{
+	extern unsigned int exec_instr_execute[];
+	extern int exec_instr(struct pt_regs *regs);
+
+	if (!regs || !instr)
+		return -EINVAL;
+
+	/* Patch the NOP with the actual instruction */
+	patch_instruction(&exec_instr_execute[0], instr);
+	if (exec_instr(regs)) {
+		pr_info("execution failed, opcode = 0x%08x\n", instr);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int __init run_sstep_tests(void)
+{
+	bool ignore_gpr;
+	bool ignore_xer;
+	bool ignore_ccr;
+	unsigned int instr;
+	unsigned long flags;
+	struct pt_regs a, b;
+	const char *mnemonic, *descr;
+	unsigned int i, j, k, result;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		mnemonic = tests[i].mnemonic;
+
+		for (j = 0; j < MAX_SUBTESTS && tests[i].subtests[j].descr; j++) {
+			descr = tests[i].subtests[j].descr;
+			instr = tests[i].subtests[j].instr;
+			flags = tests[i].subtests[j].flags;
+			ignore_xer = flags & IGNORE_XER;
+			ignore_ccr = flags & IGNORE_CCR;
+			result = PASS;
+
+			memcpy(&a, &tests[i].subtests[j].regs, sizeof(struct pt_regs));
+			memcpy(&b, &tests[i].subtests[j].regs, sizeof(struct pt_regs));
+
+			/*
+			 * Set a compatible MSR value explicitly to ensure
+			 * that XER and CR bits are updated appropriately
+			 */
+			a.msr = b.msr = MSR_KERNEL;
+
+			if (emulate_instr(&a, instr) || execute_instr(&b, instr)) {
+				result = FAIL;
+				goto print;
+			}
+
+			/* Verify GPR values */
+			for (k = 0; k < 32; k++) {
+				ignore_gpr = flags & IGNORE_GPR(k);
+				if (!ignore_gpr && a.gpr[k] != b.gpr[k]) {
+					result = FAIL;
+					pr_info("GPR%u got = 0x%016lx, exp = 0x%016lx\n", k, a.gpr[k], b.gpr[k]);
+				}
+			}
+
+			/* Verify LR value */
+			if (a.link != b.link) {
+				result = FAIL;
+				pr_info("LR got = 0x%016lx, exp = 0x%016lx\n", a.link, b.link);
+			}
+
+			/* Verify XER value */
+			if (!ignore_xer && a.xer != b.xer) {
+				result = FAIL;
+				pr_info("XER got = 0x%016lx, exp = 0x%016lx\n", a.xer, b.xer);
+			}
+
+			/* Verify CR value */
+			if (!ignore_ccr && a.ccr != b.ccr) {
+				result = FAIL;
+				pr_info("CR got = 0x%08lx, exp = 0x%08lx\n", a.ccr, b.ccr);
+			}
+
+print:
+			pr_info("%-8s: %-50s [%s]", mnemonic, descr, result ? "PASS" : "FAIL");
+		}
+	}
+
+	return 0;
+}
+late_initcall(run_sstep_tests);
