sh: Convert ptrace to hw_breakpoint API.
[pandora-kernel.git] / arch / sh / kernel / ptrace_32.c
index bdb1044..8e094c4 100644 (file)
@@ -2,7 +2,7 @@
  * SuperH process tracing
  *
  * Copyright (C) 1999, 2000  Kaz Kojima & Niibe Yutaka
- * Copyright (C) 2002 - 2008  Paul Mundt
+ * Copyright (C) 2002 - 2009  Paul Mundt
  *
  * Audit support by Yuichi Nakamura <ynakam@hitachisoft.jp>
  *
@@ -26,6 +26,7 @@
 #include <linux/tracehook.h>
 #include <linux/elf.h>
 #include <linux/regset.h>
+#include <linux/hw_breakpoint.h>
 #include <asm/uaccess.h>
 #include <asm/pgtable.h>
 #include <asm/system.h>
@@ -63,9 +64,59 @@ static inline int put_stack_long(struct task_struct *task, int offset,
        return 0;
 }
 
+void ptrace_triggered(struct perf_event *bp, int nmi,
+                     struct perf_sample_data *data, struct pt_regs *regs)
+{
+       struct perf_event_attr attr;
+
+       /*
+        * Disable the breakpoint request here since ptrace has defined a
+        * one-shot behaviour for breakpoint exceptions.
+        */
+       attr = bp->attr;
+       attr.disabled = true;
+       modify_user_hw_breakpoint(bp, &attr);
+}
+
+static int set_single_step(struct task_struct *tsk, unsigned long addr)
+{
+       struct thread_struct *thread = &tsk->thread;
+       struct perf_event *bp;
+       struct perf_event_attr attr;
+
+       bp = thread->ptrace_bps[0];
+       if (!bp) {
+               hw_breakpoint_init(&attr);
+
+               attr.bp_addr = addr;
+               attr.bp_len = HW_BREAKPOINT_LEN_2;
+               attr.bp_type = HW_BREAKPOINT_R;
+
+               bp = register_user_hw_breakpoint(&attr, ptrace_triggered, tsk);
+               if (IS_ERR(bp))
+                       return PTR_ERR(bp);
+
+               thread->ptrace_bps[0] = bp;
+       } else {
+               int err;
+
+               attr = bp->attr;
+               attr.bp_addr = addr;
+               err = modify_user_hw_breakpoint(bp, &attr);
+               if (unlikely(err))
+                       return err;
+       }
+
+       return 0;
+}
+
 void user_enable_single_step(struct task_struct *child)
 {
+       unsigned long pc = get_stack_long(child, offsetof(struct pt_regs, pc));
+
        set_tsk_thread_flag(child, TIF_SINGLESTEP);
+
+       set_single_step(child, pc);
 }
 
 void user_disable_single_step(struct task_struct *child)