[PATCH] ppc64: Add ptrace data breakpoint support
authorAnton Blanchard <anton@samba.org>
Sat, 10 Sep 2005 06:01:11 +0000 (16:01 +1000)
committerPaul Mackerras <paulus@samba.org>
Mon, 12 Sep 2005 07:19:12 +0000 (17:19 +1000)
Add hardware data breakpoint support.

Signed-off-by: Anton Blanchard <anton@samba.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
14 files changed:
arch/ppc64/kernel/process.c
arch/ppc64/kernel/ptrace.c
arch/ppc64/kernel/ptrace32.c
arch/ppc64/kernel/ras.c
arch/ppc64/kernel/signal.c
arch/ppc64/kernel/signal32.c
arch/ppc64/mm/fault.c
arch/ppc64/xmon/privinst.h
arch/ppc64/xmon/xmon.c
include/asm-ppc64/hvcall.h
include/asm-ppc64/plpar_wrappers.h
include/asm-ppc64/processor.h
include/asm-ppc64/ptrace-common.h
include/asm-ppc64/system.h

index 7a7e027653ad922dac160da11e545401696148ed..887005358eb1faa40bdba25f77d75650f21d851a 100644 (file)
@@ -54,6 +54,7 @@
 #include <asm/sections.h>
 #include <asm/tlbflush.h>
 #include <asm/time.h>
+#include <asm/plpar_wrappers.h>
 
 #ifndef CONFIG_SMP
 struct task_struct *last_task_used_math = NULL;
@@ -163,7 +164,30 @@ int dump_task_altivec(struct pt_regs *regs, elf_vrregset_t *vrregs)
 
 #endif /* CONFIG_ALTIVEC */
 
+static void set_dabr_spr(unsigned long val)
+{
+       mtspr(SPRN_DABR, val);
+}
+
+int set_dabr(unsigned long dabr)
+{
+       int ret = 0;
+
+       if (firmware_has_feature(FW_FEATURE_XDABR)) {
+               /* We want to catch accesses from kernel and userspace */
+               unsigned long flags = H_DABRX_KERNEL|H_DABRX_USER;
+               ret = plpar_set_xdabr(dabr, flags);
+       } else if (firmware_has_feature(FW_FEATURE_DABR)) {
+               ret = plpar_set_dabr(dabr);
+       } else {
+               set_dabr_spr(dabr);
+       }
+
+       return ret;
+}
+
 DEFINE_PER_CPU(struct cpu_usage, cpu_usage_array);
+static DEFINE_PER_CPU(unsigned long, current_dabr);
 
 struct task_struct *__switch_to(struct task_struct *prev,
                                struct task_struct *new)
@@ -198,6 +222,11 @@ struct task_struct *__switch_to(struct task_struct *prev,
                new->thread.regs->msr |= MSR_VEC;
 #endif /* CONFIG_ALTIVEC */
 
+       if (unlikely(__get_cpu_var(current_dabr) != new->thread.dabr)) {
+               set_dabr(new->thread.dabr);
+               __get_cpu_var(current_dabr) = new->thread.dabr;
+       }
+
        flush_tlb_pending();
 
        new_thread = &new->thread;
@@ -334,6 +363,11 @@ void flush_thread(void)
                last_task_used_altivec = NULL;
 #endif /* CONFIG_ALTIVEC */
 #endif /* CONFIG_SMP */
+
+       if (current->thread.dabr) {
+               current->thread.dabr = 0;
+               set_dabr(0);
+       }
 }
 
 void
index bf7116d4c4c265d8e9078c38cdf2a4a5547d795c..85ed3188a91d0a7ef7c3799c8cf9a7992f411ac1 100644 (file)
@@ -207,6 +207,19 @@ int sys_ptrace(long request, long pid, long addr, long data)
                break;
        }
 
+       case PTRACE_GET_DEBUGREG: {
+               ret = -EINVAL;
+               /* We only support one DABR and no IABRS at the moment */
+               if (addr > 0)
+                       break;
+               ret = put_user(child->thread.dabr,
+                              (unsigned long __user *)data);
+               break;
+       }
+
+       case PTRACE_SET_DEBUGREG:
+               ret = ptrace_set_debugreg(child, addr, data);
+
        case PTRACE_DETACH:
                ret = ptrace_detach(child, data);
                break;
index cbb1e0fb8813ac27cece1b29019543b4baad23a2..fb8c22d6084a7c280a0c74d9f9d3f1ebb523471d 100644 (file)
@@ -338,6 +338,19 @@ int sys32_ptrace(long request, long pid, unsigned long addr, unsigned long data)
                break;
        }
 
+       case PTRACE_GET_DEBUGREG: {
+               ret = -EINVAL;
+               /* We only support one DABR and no IABRS at the moment */
+               if (addr > 0)
+                       break;
+               ret = put_user(child->thread.dabr, (u32 __user *)data);
+               break;
+       }
+
+       case PTRACE_SET_DEBUGREG:
+               ret = ptrace_set_debugreg(child, addr, data);
+               break;
+
        case PTRACE_DETACH:
                ret = ptrace_detach(child, data);
                break;
index 3c00f7bfc1b5736d43ab2011e3ac37aafe6da9cf..41b97dc9cc0a0a9735675bdee038bf7b7008007e 100644 (file)
@@ -59,8 +59,6 @@ char mce_data_buf[RTAS_ERROR_LOG_MAX]
 /* This is true if we are using the firmware NMI handler (typically LPAR) */
 extern int fwnmi_active;
 
-extern void _exception(int signr, struct pt_regs *regs, int code, unsigned long addr);
-
 static int ras_get_sensor_state_token;
 static int ras_check_exception_token;
 
index 49a79a55c32de7d5ab73191dbe78047a6dcf848e..347112cca3c096ac660a3eed66d7aa4b9c0e0b26 100644 (file)
@@ -550,6 +550,15 @@ int do_signal(sigset_t *oldset, struct pt_regs *regs)
                /* Whee!  Actually deliver the signal.  */
                if (TRAP(regs) == 0x0C00)
                        syscall_restart(regs, &ka);
+
+               /*
+                * Reenable the DABR before delivering the signal to
+                * user space. The DABR will have been cleared if it
+                * triggered inside the kernel.
+                */
+               if (current->thread.dabr)
+                       set_dabr(current->thread.dabr);
+
                return handle_signal(signr, &ka, &info, oldset, regs);
        }
 
index 46f4d6cc7fc9591ce7240887a05edadcd711c6f6..a8b7a5a56bb445b90f7116c3f418fd14b41a657b 100644 (file)
@@ -970,6 +970,14 @@ int do_signal32(sigset_t *oldset, struct pt_regs *regs)
                newsp = regs->gpr[1];
        newsp &= ~0xfUL;
 
+       /*
+        * Reenable the DABR before delivering the signal to
+        * user space. The DABR will have been cleared if it
+        * triggered inside the kernel.
+        */
+       if (current->thread.dabr)
+               set_dabr(current->thread.dabr);
+
        /* Whee!  Actually deliver the signal.  */
        if (ka.sa.sa_flags & SA_SIGINFO)
                ret = handle_rt_signal32(signr, &ka, &info, oldset, regs, newsp);
index 772f0714a5b7d4be9231b362650c920c1c70cb9c..7fbc68bbb7391cef88ee614d2ec54f291f54c7dc 100644 (file)
@@ -77,6 +77,28 @@ static int store_updates_sp(struct pt_regs *regs)
        return 0;
 }
 
+static void do_dabr(struct pt_regs *regs, unsigned long error_code)
+{
+       siginfo_t info;
+
+       if (notify_die(DIE_DABR_MATCH, "dabr_match", regs, error_code,
+                       11, SIGSEGV) == NOTIFY_STOP)
+               return;
+
+       if (debugger_dabr_match(regs))
+               return;
+
+       /* Clear the DABR */
+       set_dabr(0);
+
+       /* Deliver the signal to userspace */
+       info.si_signo = SIGTRAP;
+       info.si_errno = 0;
+       info.si_code = TRAP_HWBKPT;
+       info.si_addr = (void __user *)regs->nip;
+       force_sig_info(SIGTRAP, &info, current);
+}
+
 /*
  * The error_code parameter is
  *  - DSISR for a non-SLB data access fault,
@@ -111,12 +133,9 @@ int __kprobes do_page_fault(struct pt_regs *regs, unsigned long address,
        if (!user_mode(regs) && (address >= TASK_SIZE))
                return SIGSEGV;
 
-       if (error_code & DSISR_DABRMATCH) {
-               if (notify_die(DIE_DABR_MATCH, "dabr_match", regs, error_code,
-                                       11, SIGSEGV) == NOTIFY_STOP)
-                       return 0;
-               if (debugger_dabr_match(regs))
-                       return 0;
+       if (error_code & DSISR_DABRMATCH) {
+               do_dabr(regs, error_code);
+               return 0;
        }
 
        if (in_atomic() || mm == NULL) {
index 183c3e40025881daab3b80ec360a47f2d380af1e..02eb40dac0b304fbcab0b3cf0d136bc312a9eb79 100644 (file)
@@ -46,7 +46,6 @@ GSETSPR(287, pvr)
 GSETSPR(1008, hid0)
 GSETSPR(1009, hid1)
 GSETSPR(1010, iabr)
-GSETSPR(1013, dabr)
 GSETSPR(1023, pir)
 
 static inline void store_inst(void *p)
index 45908b10acd345970da83c6d5369fc850e6b072a..74e63a886a69ae4b9769cacd3b6f2df44bcec48d 100644 (file)
@@ -586,6 +586,8 @@ int xmon_dabr_match(struct pt_regs *regs)
 {
        if ((regs->msr & (MSR_IR|MSR_PR|MSR_SF)) != (MSR_IR|MSR_SF))
                return 0;
+       if (dabr.enabled == 0)
+               return 0;
        xmon_core(regs, 0);
        return 1;
 }
@@ -628,20 +630,6 @@ int xmon_fault_handler(struct pt_regs *regs)
        return 0;
 }
 
-/* On systems with a hypervisor, we can't set the DABR
-   (data address breakpoint register) directly. */
-static void set_controlled_dabr(unsigned long val)
-{
-#ifdef CONFIG_PPC_PSERIES
-       if (systemcfg->platform == PLATFORM_PSERIES_LPAR) {
-               int rc = plpar_hcall_norets(H_SET_DABR, val);
-               if (rc != H_Success)
-                       xmon_printf("Warning: setting DABR failed (%d)\n", rc);
-       } else
-#endif
-               set_dabr(val);
-}
-
 static struct bpt *at_breakpoint(unsigned long pc)
 {
        int i;
@@ -728,7 +716,7 @@ static void insert_bpts(void)
 static void insert_cpu_bpts(void)
 {
        if (dabr.enabled)
-               set_controlled_dabr(dabr.address | (dabr.enabled & 7));
+               set_dabr(dabr.address | (dabr.enabled & 7));
        if (iabr && cpu_has_feature(CPU_FTR_IABR))
                set_iabr(iabr->address
                         | (iabr->enabled & (BP_IABR|BP_IABR_TE)));
@@ -756,7 +744,7 @@ static void remove_bpts(void)
 
 static void remove_cpu_bpts(void)
 {
-       set_controlled_dabr(0);
+       set_dabr(0);
        if (cpu_has_feature(CPU_FTR_IABR))
                set_iabr(0);
 }
index 4f668a4baff01b13427e59be038280075c7e835c..ab7c3cf24888cd6c6fb2f3663cd835f95d088f7b 100644 (file)
 #define H_PP1                  (1UL<<(63-62))
 #define H_PP2                  (1UL<<(63-63))
 
+/* DABRX flags */
+#define H_DABRX_HYPERVISOR     (1UL<<(63-61))
+#define H_DABRX_KERNEL         (1UL<<(63-62))
+#define H_DABRX_USER           (1UL<<(63-63))
+
 /* pSeries hypervisor opcodes */
 #define H_REMOVE               0x04
 #define H_ENTER                        0x08
 #define H_VIO_SIGNAL           0x104
 #define H_SEND_CRQ             0x108
 #define H_COPY_RDMA             0x110
+#define H_SET_XDABR            0x134
 #define H_STUFF_TCE            0x138
 #define H_PUT_TCE_INDIRECT     0x13C
 #define H_VTERM_PARTNER_INFO   0x150
index f4a5fb7d67c7ccd24a0aa82c06672e8f54461565..72dd2449ee76224f6f9f1748e5ef69c81b4843bd 100644 (file)
@@ -107,5 +107,14 @@ static inline long plpar_put_term_char(unsigned long termno,
                                  lbuf[1]);
 }
 
+static inline long plpar_set_xdabr(unsigned long address, unsigned long flags)
+{
+       return plpar_hcall_norets(H_SET_XDABR, address, flags);
+}
+
+static inline long plpar_set_dabr(unsigned long val)
+{
+       return plpar_hcall_norets(H_SET_DABR, val);
+}
 
 #endif /* _PPC64_PLPAR_WRAPPERS_H */
index 8bd7aa959385d01d33c530526609e7302cf97921..4146189006e32cac1d8d11d618e8f02498bfe1ce 100644 (file)
@@ -433,6 +433,7 @@ struct thread_struct {
        unsigned long   start_tb;       /* Start purr when proc switched in */
        unsigned long   accum_tb;       /* Total accumilated purr for process */
        unsigned long   vdso_base;      /* base of the vDSO library */
+       unsigned long   dabr;           /* Data address breakpoint register */
 #ifdef CONFIG_ALTIVEC
        /* Complete AltiVec register set */
        vector128       vr[32] __attribute((aligned(16)));
index bd0f84c27bd0b8e5bfba420a9ee674af9f65d608..b1babb7296733632baeaa2ea60a21e0399bc60e0 100644 (file)
@@ -13,6 +13,7 @@
 #define _PPC64_PTRACE_COMMON_H
 
 #include <linux/config.h>
+#include <asm/system.h>
 
 /*
  * Set of msr bits that gdb can change on behalf of a process.
@@ -141,4 +142,23 @@ static inline int set_vrregs(struct task_struct *task,
 }
 #endif
 
+static inline int ptrace_set_debugreg(struct task_struct *task,
+                                     unsigned long addr, unsigned long data)
+{
+       /* We only support one DABR and no IABRS at the moment */
+       if (addr > 0)
+               return -EINVAL;
+
+       /* The bottom 3 bits are flags */
+       if ((data & ~0x7UL) >= TASK_SIZE)
+               return -EIO;
+
+       /* Ensure translation is on */
+       if (data && !(data & DABR_TRANSLATION))
+               return -EIO;
+
+       task->thread.dabr = data;
+       return 0;
+}
+
 #endif /* _PPC64_PTRACE_COMMON_H */
index c0396428cc3c0c908bb723c111adff2a1d44ebac..375015c62f2071083b0114040404719fccc58818 100644 (file)
@@ -101,6 +101,9 @@ static inline int debugger_dabr_match(struct pt_regs *regs) { return 0; }
 static inline int debugger_fault_handler(struct pt_regs *regs) { return 0; }
 #endif
 
+extern int set_dabr(unsigned long dabr);
+extern void _exception(int signr, struct pt_regs *regs, int code,
+                      unsigned long addr);
 extern int fix_alignment(struct pt_regs *regs);
 extern void bad_page_fault(struct pt_regs *regs, unsigned long address,
                           int sig);