powerpc: sstep: Add tests for compute type instructions
authorSandipan Das <sandipan@linux.ibm.com>
Wed, 20 Feb 2019 06:56:58 +0000 (12:26 +0530)
committerMichael Ellerman <mpe@ellerman.id.au>
Sat, 23 Feb 2019 10:04:31 +0000 (21:04 +1100)
This enhances the current selftest framework for validating
the in-kernel instruction emulation infrastructure by adding
support for compute type instructions i.e. integer ALU-based
instructions. Originally, this framework was limited to only
testing load and store instructions.

While most of the GPRs can be validated, support for SPRs is
limited to LR, CR and XER for now.

When writing the test cases, one must ensure that the Stack
Pointer (GPR1) or the Thread Pointer (GPR13) are not touched
by any means as these are vital non-volatile registers.

Signed-off-by: Sandipan Das <sandipan@linux.ibm.com>
[mpe: Use patch_site for the code patching]
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
arch/powerpc/lib/Makefile
arch/powerpc/lib/test_emulate_step.c
arch/powerpc/lib/test_emulate_step_exec_instr.S [new file with mode: 0644]

index 3bf9fc6fd36c30ebeae45997f69b50a250aec8aa..79396e184bcaaac71f120e0a88058bc7a3d2c182 100644 (file)
@@ -30,7 +30,8 @@ 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_KPROBES_SANITY_TEST)    += test_emulate_step.o \
+                                          test_emulate_step_exec_instr.o
 
 obj-y                  += checksum_$(BITS).o checksum_wrappers.o \
                           string_$(BITS).o memcmp_$(BITS).o
index 6c47daa616149f745bf96702157ed9a38405437a..e9f762ac825ff30a9d1ede61a8da9262496cee2f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Simple sanity test for emulate_step load/store instructions.
+ * Simple sanity tests for instruction emulation infrastructure.
  *
  * Copyright IBM Corp. 2016
  *
@@ -14,6 +14,7 @@
 #include <linux/ptrace.h>
 #include <asm/sstep.h>
 #include <asm/ppc-opcode.h>
+#include <asm/code-patching.h>
 
 #define IMM_L(i)               ((uintptr_t)(i) & 0xffff)
 
 #define TEST_LXVD2X(s, a, b)   (PPC_INST_LXVD2X | VSX_XX1((s), R##a, R##b))
 #define TEST_STXVD2X(s, a, b)  (PPC_INST_STXVD2X | VSX_XX1((s), R##a, R##b))
 
+#define MAX_SUBTESTS   16
+
+#define IGNORE_GPR(n)  (0x1UL << (n))
+#define IGNORE_XER     (0x1UL << 32)
+#define IGNORE_CCR     (0x1UL << 33)
 
 static void __init init_pt_regs(struct pt_regs *regs)
 {
@@ -72,9 +78,15 @@ static void __init init_pt_regs(struct pt_regs *regs)
        msr_cached = true;
 }
 
-static void __init show_result(char *ins, char *result)
+static void __init show_result(char *mnemonic, char *result)
 {
-       pr_info("%-14s : %s\n", ins, result);
+       pr_info("%-14s : %s\n", mnemonic, result);
+}
+
+static void __init show_result_with_descr(char *mnemonic, char *descr,
+                                         char *result)
+{
+       pr_info("%-14s : %-50s %s\n", mnemonic, descr, result);
 }
 
 static void __init test_ld(void)
@@ -426,7 +438,7 @@ static void __init test_lxvd2x_stxvd2x(void)
 }
 #endif /* CONFIG_VSX */
 
-static int __init test_emulate_step(void)
+static void __init run_tests_load_store(void)
 {
        test_ld();
        test_lwz();
@@ -437,6 +449,153 @@ static int __init test_emulate_step(void)
        test_lfdx_stfdx();
        test_lvx_stvx();
        test_lxvd2x_stxvd2x();
+}
+
+struct compute_test {
+       char *mnemonic;
+       struct {
+               char *descr;
+               unsigned long flags;
+               unsigned int instr;
+               struct pt_regs regs;
+       } subtests[MAX_SUBTESTS + 1];
+};
+
+static struct compute_test compute_tests[] = {
+       {
+               .mnemonic = "nop",
+               .subtests = {
+                       {
+                               .descr = "R0 = LONG_MAX",
+                               .instr = PPC_INST_NOP,
+                               .regs = {
+                                       .gpr[0] = LONG_MAX,
+                               }
+                       }
+               }
+       }
+};
+
+static int __init emulate_compute_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, instruction = 0x%08x\n", instr);
+               return -EFAULT;
+       }
+
+       emulate_update_regs(regs, &op);
+       return 0;
+}
+
+static int __init execute_compute_instr(struct pt_regs *regs,
+                                       unsigned int instr)
+{
+       extern int exec_instr(struct pt_regs *regs);
+       extern s32 patch__exec_instr;
+
+       if (!regs || !instr)
+               return -EINVAL;
+
+       /* Patch the NOP with the actual instruction */
+       patch_instruction_site(&patch__exec_instr, instr);
+       if (exec_instr(regs)) {
+               pr_info("execution failed, instruction = 0x%08x\n", instr);
+               return -EFAULT;
+       }
+
+       return 0;
+}
+
+#define gpr_mismatch(gprn, exp, got)   \
+       pr_info("GPR%u mismatch, exp = 0x%016lx, got = 0x%016lx\n",     \
+               gprn, exp, got)
+
+#define reg_mismatch(name, exp, got)   \
+       pr_info("%s mismatch, exp = 0x%016lx, got = 0x%016lx\n",        \
+               name, exp, got)
+
+static void __init run_tests_compute(void)
+{
+       unsigned long flags;
+       struct compute_test *test;
+       struct pt_regs *regs, exp, got;
+       unsigned int i, j, k, instr;
+       bool ignore_gpr, ignore_xer, ignore_ccr, passed;
+
+       for (i = 0; i < ARRAY_SIZE(compute_tests); i++) {
+               test = &compute_tests[i];
+
+               for (j = 0; j < MAX_SUBTESTS && test->subtests[j].descr; j++) {
+                       instr = test->subtests[j].instr;
+                       flags = test->subtests[j].flags;
+                       regs = &test->subtests[j].regs;
+                       ignore_xer = flags & IGNORE_XER;
+                       ignore_ccr = flags & IGNORE_CCR;
+                       passed = true;
+
+                       memcpy(&exp, regs, sizeof(struct pt_regs));
+                       memcpy(&got, regs, sizeof(struct pt_regs));
+
+                       /*
+                        * Set a compatible MSR value explicitly to ensure
+                        * that XER and CR bits are updated appropriately
+                        */
+                       exp.msr = MSR_KERNEL;
+                       got.msr = MSR_KERNEL;
+
+                       if (emulate_compute_instr(&got, instr) ||
+                           execute_compute_instr(&exp, instr)) {
+                               passed = false;
+                               goto print;
+                       }
+
+                       /* Verify GPR values */
+                       for (k = 0; k < 32; k++) {
+                               ignore_gpr = flags & IGNORE_GPR(k);
+                               if (!ignore_gpr && exp.gpr[k] != got.gpr[k]) {
+                                       passed = false;
+                                       gpr_mismatch(k, exp.gpr[k], got.gpr[k]);
+                               }
+                       }
+
+                       /* Verify LR value */
+                       if (exp.link != got.link) {
+                               passed = false;
+                               reg_mismatch("LR", exp.link, got.link);
+                       }
+
+                       /* Verify XER value */
+                       if (!ignore_xer && exp.xer != got.xer) {
+                               passed = false;
+                               reg_mismatch("XER", exp.xer, got.xer);
+                       }
+
+                       /* Verify CR value */
+                       if (!ignore_ccr && exp.ccr != got.ccr) {
+                               passed = false;
+                               reg_mismatch("CR", exp.ccr, got.ccr);
+                       }
+
+print:
+                       show_result_with_descr(test->mnemonic,
+                                              test->subtests[j].descr,
+                                              passed ? "PASS" : "FAIL");
+               }
+       }
+}
+
+static int __init test_emulate_step(void)
+{
+       printk(KERN_INFO "Running instruction emulation self-tests ...\n");
+       run_tests_load_store();
+       run_tests_compute();
 
        return 0;
 }
diff --git a/arch/powerpc/lib/test_emulate_step_exec_instr.S b/arch/powerpc/lib/test_emulate_step_exec_instr.S
new file mode 100644 (file)
index 0000000..1580f34
--- /dev/null
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Non-emulated single-stepping support (currently limited to basic integer
+ * computations) used to validate the instruction emulation infrastructure.
+ *
+ * Copyright (C) 2019 IBM Corporation
+ */
+
+#include <asm/asm-offsets.h>
+#include <asm/ppc_asm.h>
+#include <asm/code-patching-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)
+
+       /* Placeholder for the test instruction */
+1:     nop
+       patch_site 1b patch__exec_instr
+
+       /*
+        * 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)