]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/commitdiff
s390/test_unwind: add program check context tests
authorVasily Gorbik <gor@linux.ibm.com>
Mon, 25 Nov 2019 13:07:40 +0000 (14:07 +0100)
committerVasily Gorbik <gor@linux.ibm.com>
Sat, 30 Nov 2019 09:52:48 +0000 (10:52 +0100)
Add unwinding from program check handler tests. Unwinder should be able
to unwind through pt_regs stored by program check handler on task stack.

Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
arch/s390/lib/test_unwind.c

index 72fa745281f093eb3b9dc726e6e5bf7177d2563b..bda7ac0ddd29710d62bb23911408cc4e64adc329 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/kthread.h>
 #include <linux/module.h>
 #include <linux/string.h>
+#include <linux/kprobes.h>
 #include <linux/wait.h>
 #include <asm/irq.h>
 #include <asm/delay.h>
@@ -119,6 +120,7 @@ static struct unwindme *unwindme;
 #define UWM_CALLER             0x8     /* Unwind starting from caller. */
 #define UWM_SWITCH_STACK       0x10    /* Use CALL_ON_STACK. */
 #define UWM_IRQ                        0x20    /* Unwind from irq context. */
+#define UWM_PGM                        0x40    /* Unwind from program check handler. */
 
 static __always_inline unsigned long get_psw_addr(void)
 {
@@ -130,6 +132,17 @@ static __always_inline unsigned long get_psw_addr(void)
        return psw_addr;
 }
 
+#ifdef CONFIG_KPROBES
+static int pgm_pre_handler(struct kprobe *p, struct pt_regs *regs)
+{
+       struct unwindme *u = unwindme;
+
+       u->ret = test_unwind(NULL, (u->flags & UWM_REGS) ? regs : NULL,
+                            (u->flags & UWM_SP) ? u->sp : 0);
+       return 0;
+}
+#endif
+
 /* This function may or may not appear in the backtrace. */
 static noinline int unwindme_func4(struct unwindme *u)
 {
@@ -140,6 +153,34 @@ static noinline int unwindme_func4(struct unwindme *u)
                wait_event(u->task_wq, kthread_should_park());
                kthread_parkme();
                return 0;
+#ifdef CONFIG_KPROBES
+       } else if (u->flags & UWM_PGM) {
+               struct kprobe kp;
+               int ret;
+
+               unwindme = u;
+               memset(&kp, 0, sizeof(kp));
+               kp.symbol_name = "do_report_trap";
+               kp.pre_handler = pgm_pre_handler;
+               ret = register_kprobe(&kp);
+               if (ret < 0) {
+                       pr_err("register_kprobe failed %d\n", ret);
+                       return -EINVAL;
+               }
+
+               /*
+                * trigger specification exception
+                */
+               asm volatile(
+                       "       mvcl    %%r1,%%r1\n"
+                       "0:     nopr    %%r7\n"
+                       EX_TABLE(0b, 0b)
+                       :);
+
+               unregister_kprobe(&kp);
+               unwindme = NULL;
+               return u->ret;
+#endif
        } else {
                struct pt_regs regs;
 
@@ -286,6 +327,12 @@ do {                                                                       \
        TEST(UWM_IRQ | UWM_CALLER | UWM_SP);
        TEST(UWM_IRQ | UWM_CALLER | UWM_SP | UWM_REGS);
        TEST(UWM_IRQ | UWM_CALLER | UWM_SP | UWM_REGS | UWM_SWITCH_STACK);
+#ifdef CONFIG_KPROBES
+       TEST(UWM_PGM);
+       TEST(UWM_PGM | UWM_SP);
+       TEST(UWM_PGM | UWM_REGS);
+       TEST(UWM_PGM | UWM_SP | UWM_REGS);
+#endif
 #undef TEST
 
        return ret;