diff mbox series

powerpc: Check DAWR before setting watchpoint

Message ID 20171117115230.8794-1-pedromfc@linux.vnet.ibm.com
State New
Headers show
Series powerpc: Check DAWR before setting watchpoint | expand

Commit Message

Pedro Franco de Carvalho Nov. 17, 2017, 11:52 a.m. UTC
Parse the PVR to check if DAWR was disabled by firmware
for POWER9 DD2.0 and if so return in error from ptrace.

Based on initial code provided by Michael Neuling <mikey@neuling.org>.

Suggested-by: Ananth N Mavinakayanahalli <ananth@linux.vnet.ibm.com>
Signed-off-by: Pedro Franco de Carvalho <pedromfc@linux.vnet.ibm.com>
---
 arch/powerpc/include/asm/debug.h    |  1 +
 arch/powerpc/kernel/hw_breakpoint.c |  2 ++
 arch/powerpc/kernel/process.c       | 24 ++++++++++++++++++++++++
 arch/powerpc/kernel/ptrace.c        | 18 ++++++++++++++++--
 4 files changed, 43 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/arch/powerpc/include/asm/debug.h b/arch/powerpc/include/asm/debug.h
index 14e71ff6579e..9b0381abeb0a 100644
--- a/arch/powerpc/include/asm/debug.h
+++ b/arch/powerpc/include/asm/debug.h
@@ -47,6 +47,7 @@  static inline int debugger_fault_handler(struct pt_regs *regs) { return 0; }
 
 void set_breakpoint(struct arch_hw_breakpoint *brk);
 void __set_breakpoint(struct arch_hw_breakpoint *brk);
+bool breakpoint_enabled(void);
 #ifdef CONFIG_PPC_ADV_DEBUG_REGS
 extern void do_send_trap(struct pt_regs *regs, unsigned long address,
 			 unsigned long error_code, int signal_code, int brkpt);
diff --git a/arch/powerpc/kernel/hw_breakpoint.c b/arch/powerpc/kernel/hw_breakpoint.c
index 53b9c1dfd7d9..42a6c692f22a 100644
--- a/arch/powerpc/kernel/hw_breakpoint.c
+++ b/arch/powerpc/kernel/hw_breakpoint.c
@@ -173,6 +173,8 @@  int arch_validate_hwbkpt_settings(struct perf_event *bp)
 	 */
 	length_max = 8; /* DABR */
 	if (cpu_has_feature(CPU_FTR_DAWR)) {
+		if(!breakpoint_enabled())
+			return -ENODEV;
 		length_max = 512 ; /* 64 doublewords */
 		/* DAWR region can't cross 512 boundary */
 		if ((bp->attr.bp_addr >> 10) != 
diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c
index a0c74bbf3454..73dd45dcc5b1 100644
--- a/arch/powerpc/kernel/process.c
+++ b/arch/powerpc/kernel/process.c
@@ -809,6 +809,30 @@  void set_breakpoint(struct arch_hw_breakpoint *brk)
 	preempt_enable();
 }
 
+/*
+ * Check if DAWR is enabled.
+ *
+ * Firmware for POWER9 DD2.0 has DAWR disabled.
+ * There is no way to determine this pragmatically or from
+ * firmware so unfortunately we need to do this based on the PVR.
+ */
+bool breakpoint_enabled(void)
+{
+	unsigned long pvr;
+	unsigned short maj;
+	unsigned short min;
+
+	pvr = mfspr(SPRN_PVR);
+
+	maj = (pvr >> 8) & 0x0F;
+	min = pvr & 0xFF;
+
+	if ((PVR_VER(pvr) == PVR_POWER9) && (maj == 2) && (min == 0))
+		return false;
+
+	return true;
+}
+
 #ifdef CONFIG_PPC64
 DEFINE_PER_CPU(struct cpu_usage, cpu_usage_array);
 #endif
diff --git a/arch/powerpc/kernel/ptrace.c b/arch/powerpc/kernel/ptrace.c
index f52ad5bb7109..632fc7b2ca90 100644
--- a/arch/powerpc/kernel/ptrace.c
+++ b/arch/powerpc/kernel/ptrace.c
@@ -35,6 +35,7 @@ 
 #include <linux/context_tracking.h>
 
 #include <linux/uaccess.h>
+#include <asm/debug.h>
 #include <asm/page.h>
 #include <asm/pgtable.h>
 #include <asm/switch_to.h>
@@ -2300,6 +2301,7 @@  static int ptrace_set_debugreg(struct task_struct *task, unsigned long addr,
 	struct perf_event_attr attr;
 #endif /* CONFIG_HAVE_HW_BREAKPOINT */
 #ifndef CONFIG_PPC_ADV_DEBUG_REGS
+	bool clear_bp = false;
 	struct arch_hw_breakpoint hw_brk;
 #endif
 
@@ -2333,9 +2335,10 @@  static int ptrace_set_debugreg(struct task_struct *task, unsigned long addr,
 	hw_brk.address = data & (~HW_BRK_TYPE_DABR);
 	hw_brk.type = (data & HW_BRK_TYPE_DABR) | HW_BRK_TYPE_PRIV_ALL;
 	hw_brk.len = 8;
+	clear_bp = (!data) || !(hw_brk.type & HW_BRK_TYPE_RDWR);
 #ifdef CONFIG_HAVE_HW_BREAKPOINT
 	bp = thread->ptrace_bps[0];
-	if ((!data) || !(hw_brk.type & HW_BRK_TYPE_RDWR)) {
+	if (clear_bp) {
 		if (bp) {
 			unregister_hw_breakpoint(bp);
 			thread->ptrace_bps[0] = NULL;
@@ -2372,7 +2375,15 @@  static int ptrace_set_debugreg(struct task_struct *task, unsigned long addr,
 		return PTR_ERR(bp);
 	}
 
-#endif /* CONFIG_HAVE_HW_BREAKPOINT */
+#else /* !CONFIG_HAVE_HW_BREAKPOINT */
+	/* Only check if DAWR is enabled if we are actually
+	 * setting a watchpoint that can be triggered (i.e. clear_bp is false).
+	 * This emulates the behavior when CONFIG_HAVE_HW_BREAKPOINT is set,
+	 * which checks DAWR when register_user_hw_breakpoint is called.
+	 */
+	if((!clear_bp) && (!breakpoint_enabled()))
+		return -ENODEV;
+#endif /* !CONFIG_HAVE_HW_BREAKPOINT */
 	task->thread.hw_brk = hw_brk;
 #else /* CONFIG_PPC_ADV_DEBUG_REGS */
 	/* As described above, it was assumed 3 bits were passed with the data
@@ -2826,6 +2837,9 @@  static long ppc_set_hwdebug(struct task_struct *child,
 	if (child->thread.hw_brk.address)
 		return -ENOSPC;
 
+	if (!breakpoint_enabled())
+		return -ENODEV;
+
 	child->thread.hw_brk = brk;
 
 	return 1;