MIPS: Ptrace support for HARDWARE_WATCHPOINTS
authorDavid Daney <ddaney@avtrex.com>
Tue, 23 Sep 2008 07:11:26 +0000 (00:11 -0700)
committerRalf Baechle <ralf@linux-mips.org>
Sat, 11 Oct 2008 15:18:57 +0000 (16:18 +0100)
This is the final part of the watch register patch.  Here we hook up
ptrace so that the user space debugger (gdb), can set and read the
registers.

Signed-off-by: David Daney <ddaney@avtrex.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/include/asm/ptrace.h
arch/mips/kernel/ptrace.c
arch/mips/kernel/ptrace32.c

index c00cca24dae0427544520a156c186e57f9877a1f..cd5e8a47812baaf3b9dcd434d1ef28a667d13852 100644 (file)
@@ -74,11 +74,57 @@ struct pt_regs {
 #define PTRACE_POKEDATA_3264   0xc3
 #define PTRACE_GET_THREAD_AREA_3264    0xc4
 
+/* Read and write watchpoint registers.  */
+enum pt_watch_style {
+       pt_watch_style_mips32,
+       pt_watch_style_mips64
+};
+struct mips32_watch_regs {
+       uint32_t watchlo[8];
+       /* Lower 16 bits of watchhi. */
+       uint16_t watchhi[8];
+       /* Valid mask and I R W bits.
+        * bit 0 -- 1 if W bit is usable.
+        * bit 1 -- 1 if R bit is usable.
+        * bit 2 -- 1 if I bit is usable.
+        * bits 3 - 11 -- Valid watchhi mask bits.
+        */
+       uint16_t watch_masks[8];
+       /* The number of valid watch register pairs.  */
+       uint32_t num_valid;
+} __attribute__((aligned(8)));
+
+struct mips64_watch_regs {
+       uint64_t watchlo[8];
+       uint16_t watchhi[8];
+       uint16_t watch_masks[8];
+       uint32_t num_valid;
+} __attribute__((aligned(8)));
+
+struct pt_watch_regs {
+       enum pt_watch_style style;
+       union {
+               struct mips32_watch_regs mips32;
+               struct mips32_watch_regs mips64;
+       };
+};
+
+#define PTRACE_GET_WATCH_REGS  0xd0
+#define PTRACE_SET_WATCH_REGS  0xd1
+
 #ifdef __KERNEL__
 
+#include <linux/compiler.h>
 #include <linux/linkage.h>
 #include <asm/isadep.h>
 
+struct task_struct;
+
+extern int ptrace_get_watch_regs(struct task_struct *child,
+       struct pt_watch_regs __user *addr);
+extern int ptrace_set_watch_regs(struct task_struct *child,
+       struct pt_watch_regs __user *addr);
+
 /*
  * Does the process account for user or for system time?
  */
index 96ffc9c6d194d6ba5d2a8be948481652adbb34a2..054861ccb4ddac36306b70871d83aed6a3c50fbf 100644 (file)
@@ -46,7 +46,8 @@
  */
 void ptrace_disable(struct task_struct *child)
 {
-       /* Nothing to do.. */
+       /* Don't load the watchpoint registers for the ex-child. */
+       clear_tsk_thread_flag(child, TIF_LOAD_WATCH);
 }
 
 /*
@@ -167,6 +168,93 @@ int ptrace_setfpregs(struct task_struct *child, __u32 __user *data)
        return 0;
 }
 
+int ptrace_get_watch_regs(struct task_struct *child,
+                         struct pt_watch_regs __user *addr)
+{
+       enum pt_watch_style style;
+       int i;
+
+       if (!cpu_has_watch || current_cpu_data.watch_reg_use_cnt == 0)
+               return -EIO;
+       if (!access_ok(VERIFY_WRITE, addr, sizeof(struct pt_watch_regs)))
+               return -EIO;
+
+#ifdef CONFIG_32BIT
+       style = pt_watch_style_mips32;
+#define WATCH_STYLE mips32
+#else
+       style = pt_watch_style_mips64;
+#define WATCH_STYLE mips64
+#endif
+
+       __put_user(style, &addr->style);
+       __put_user(current_cpu_data.watch_reg_use_cnt,
+                  &addr->WATCH_STYLE.num_valid);
+       for (i = 0; i < current_cpu_data.watch_reg_use_cnt; i++) {
+               __put_user(child->thread.watch.mips3264.watchlo[i],
+                          &addr->WATCH_STYLE.watchlo[i]);
+               __put_user(child->thread.watch.mips3264.watchhi[i] & 0xfff,
+                          &addr->WATCH_STYLE.watchhi[i]);
+               __put_user(current_cpu_data.watch_reg_masks[i],
+                          &addr->WATCH_STYLE.watch_masks[i]);
+       }
+       for (; i < 8; i++) {
+               __put_user(0, &addr->WATCH_STYLE.watchlo[i]);
+               __put_user(0, &addr->WATCH_STYLE.watchhi[i]);
+               __put_user(0, &addr->WATCH_STYLE.watch_masks[i]);
+       }
+
+       return 0;
+}
+
+int ptrace_set_watch_regs(struct task_struct *child,
+                         struct pt_watch_regs __user *addr)
+{
+       int i;
+       int watch_active = 0;
+       unsigned long lt[NUM_WATCH_REGS];
+       u16 ht[NUM_WATCH_REGS];
+
+       if (!cpu_has_watch || current_cpu_data.watch_reg_use_cnt == 0)
+               return -EIO;
+       if (!access_ok(VERIFY_READ, addr, sizeof(struct pt_watch_regs)))
+               return -EIO;
+       /* Check the values. */
+       for (i = 0; i < current_cpu_data.watch_reg_use_cnt; i++) {
+               __get_user(lt[i], &addr->WATCH_STYLE.watchlo[i]);
+#ifdef CONFIG_32BIT
+               if (lt[i] & __UA_LIMIT)
+                       return -EINVAL;
+#else
+               if (test_tsk_thread_flag(child, TIF_32BIT_ADDR)) {
+                       if (lt[i] & 0xffffffff80000000UL)
+                               return -EINVAL;
+               } else {
+                       if (lt[i] & __UA_LIMIT)
+                               return -EINVAL;
+               }
+#endif
+               __get_user(ht[i], &addr->WATCH_STYLE.watchhi[i]);
+               if (ht[i] & ~0xff8)
+                       return -EINVAL;
+       }
+       /* Install them. */
+       for (i = 0; i < current_cpu_data.watch_reg_use_cnt; i++) {
+               if (lt[i] & 7)
+                       watch_active = 1;
+               child->thread.watch.mips3264.watchlo[i] = lt[i];
+               /* Set the G bit. */
+               child->thread.watch.mips3264.watchhi[i] = ht[i];
+       }
+
+       if (watch_active)
+               set_tsk_thread_flag(child, TIF_LOAD_WATCH);
+       else
+               clear_tsk_thread_flag(child, TIF_LOAD_WATCH);
+
+       return 0;
+}
+
 long arch_ptrace(struct task_struct *child, long request, long addr, long data)
 {
        int ret;
@@ -440,6 +528,16 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
                                (unsigned long __user *) data);
                break;
 
+       case PTRACE_GET_WATCH_REGS:
+               ret = ptrace_get_watch_regs(child,
+                                       (struct pt_watch_regs __user *) addr);
+               break;
+
+       case PTRACE_SET_WATCH_REGS:
+               ret = ptrace_set_watch_regs(child,
+                                       (struct pt_watch_regs __user *) addr);
+               break;
+
        default:
                ret = ptrace_request(child, request, addr, data);
                break;
index cac56a8c8679a28183a3a4be42f86891116dd9b6..e45105e3ef0072f12c8fafc613918b40759b2a15 100644 (file)
@@ -387,6 +387,16 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
                                (unsigned long __user *) (unsigned long) data);
                break;
 
+       case PTRACE_GET_WATCH_REGS:
+               ret = ptrace_get_watch_regs(child,
+                       (struct pt_watch_regs __user *) (unsigned long) addr);
+               break;
+
+       case PTRACE_SET_WATCH_REGS:
+               ret = ptrace_set_watch_regs(child,
+                       (struct pt_watch_regs __user *) (unsigned long) addr);
+               break;
+
        default:
                ret = ptrace_request(child, request, addr, data);
                break;