Merge branches 'sh/xstate', 'sh/hw-breakpoints' and 'sh/stable-updates'
[firefly-linux-kernel-4.4.55.git] / arch / sh / kernel / ptrace_32.c
index be9b5dcb4021a42dadb9437fdf0f4a851d1eaa13..c625cdab76dd4143d6001f8b3913699c26e351df 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,33 +64,64 @@ static inline int put_stack_long(struct task_struct *task, int offset,
        return 0;
 }
 
-void user_enable_single_step(struct task_struct *child)
+void ptrace_triggered(struct perf_event *bp, int nmi,
+                     struct perf_sample_data *data, struct pt_regs *regs)
 {
-       /* Next scheduling will set up UBC */
-       if (child->thread.ubc_pc == 0)
-               ubc_usercnt += 1;
+       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;
+}
 
-       child->thread.ubc_pc = get_stack_long(child,
-                               offsetof(struct pt_regs, pc));
+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)
 {
        clear_tsk_thread_flag(child, TIF_SINGLESTEP);
-
-       /*
-        * Ensure the UBC is not programmed at the next context switch.
-        *
-        * Normally this is not needed but there are sequences such as
-        * singlestep, signal delivery, and continue that leave the
-        * ubc_pc non-zero leading to spurious SIGTRAPs.
-        */
-       if (child->thread.ubc_pc != 0) {
-               ubc_usercnt -= 1;
-               child->thread.ubc_pc = 0;
-       }
 }
 
 /*