From fd9648dff6f9797ecc509bcd181706a274dc074d Mon Sep 17 00:00:00 2001
From: Anton Blanchard <anton@samba.org>
Date: Sat, 10 Sep 2005 16:01:11 +1000
Subject: [PATCH] ppc64: Add ptrace data breakpoint support

Add hardware data breakpoint support.

Signed-off-by: Anton Blanchard <anton@samba.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
---
 arch/ppc64/kernel/process.c  | 34 ++++++++++++++++++++++++++++++++++
 arch/ppc64/kernel/ptrace.c   | 13 +++++++++++++
 arch/ppc64/kernel/ptrace32.c | 13 +++++++++++++
 arch/ppc64/kernel/ras.c      |  2 --
 arch/ppc64/kernel/signal.c   |  9 +++++++++
 arch/ppc64/kernel/signal32.c |  8 ++++++++
 arch/ppc64/mm/fault.c        | 31 +++++++++++++++++++++++++------
 arch/ppc64/xmon/privinst.h   |  1 -
 arch/ppc64/xmon/xmon.c       | 20 ++++----------------
 9 files changed, 106 insertions(+), 25 deletions(-)

(limited to 'arch')

diff --git a/arch/ppc64/kernel/process.c b/arch/ppc64/kernel/process.c
index 7a7e027653..887005358e 100644
--- a/arch/ppc64/kernel/process.c
+++ b/arch/ppc64/kernel/process.c
@@ -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
diff --git a/arch/ppc64/kernel/ptrace.c b/arch/ppc64/kernel/ptrace.c
index bf7116d4c4..85ed3188a9 100644
--- a/arch/ppc64/kernel/ptrace.c
+++ b/arch/ppc64/kernel/ptrace.c
@@ -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;
diff --git a/arch/ppc64/kernel/ptrace32.c b/arch/ppc64/kernel/ptrace32.c
index cbb1e0fb88..fb8c22d608 100644
--- a/arch/ppc64/kernel/ptrace32.c
+++ b/arch/ppc64/kernel/ptrace32.c
@@ -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;
diff --git a/arch/ppc64/kernel/ras.c b/arch/ppc64/kernel/ras.c
index 3c00f7bfc1..41b97dc9cc 100644
--- a/arch/ppc64/kernel/ras.c
+++ b/arch/ppc64/kernel/ras.c
@@ -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;
 
diff --git a/arch/ppc64/kernel/signal.c b/arch/ppc64/kernel/signal.c
index 49a79a55c3..347112cca3 100644
--- a/arch/ppc64/kernel/signal.c
+++ b/arch/ppc64/kernel/signal.c
@@ -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);
 	}
 
diff --git a/arch/ppc64/kernel/signal32.c b/arch/ppc64/kernel/signal32.c
index 46f4d6cc7f..a8b7a5a56b 100644
--- a/arch/ppc64/kernel/signal32.c
+++ b/arch/ppc64/kernel/signal32.c
@@ -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);
diff --git a/arch/ppc64/mm/fault.c b/arch/ppc64/mm/fault.c
index 772f0714a5..7fbc68bbb7 100644
--- a/arch/ppc64/mm/fault.c
+++ b/arch/ppc64/mm/fault.c
@@ -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) {
diff --git a/arch/ppc64/xmon/privinst.h b/arch/ppc64/xmon/privinst.h
index 183c3e4002..02eb40dac0 100644
--- a/arch/ppc64/xmon/privinst.h
+++ b/arch/ppc64/xmon/privinst.h
@@ -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)
diff --git a/arch/ppc64/xmon/xmon.c b/arch/ppc64/xmon/xmon.c
index 45908b10ac..74e63a886a 100644
--- a/arch/ppc64/xmon/xmon.c
+++ b/arch/ppc64/xmon/xmon.c
@@ -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);
 }
-- 
cgit v1.2.2