svn commit: r294987 - in head/sys/arm: arm include

Zbigniew Bodek zbb at FreeBSD.org
Thu Jan 28 12:44:00 UTC 2016


Author: zbb
Date: Thu Jan 28 12:43:58 2016
New Revision: 294987
URL: https://svnweb.freebsd.org/changeset/base/294987

Log:
  SMP support for ARMv6/v7 HW watchpoints
  
  Use per-CPU structure to store HW watchpoints registers state
  for each CPU present in the system. Those registers will be restored
  upon wake up from the STOP state if requested by the debug_monitor
  code. The method is similar to the one introduced to AMD64.
  
  We store all possible 16 registers for HW watchpoints
  (maximum allowed by the architecture).
  HW breakpoints are not maintained since they are used for single
  stepping only.
  
  Pointed out by: kib
  Reviewed by:    wma
  No strong objections from: kib
  Submitted by:   Zbigniew Bodek <zbb at semihalf.com>
  Obtained from:  Semihalf
  Sponsored by:   Juniper Networks Inc.
  Differential Revision: https://reviews.freebsd.org/D4338

Modified:
  head/sys/arm/arm/debug_monitor.c
  head/sys/arm/arm/mp_machdep.c
  head/sys/arm/include/debug_monitor.h
  head/sys/arm/include/pcpu.h
  head/sys/arm/include/reg.h

Modified: head/sys/arm/arm/debug_monitor.c
==============================================================================
--- head/sys/arm/arm/debug_monitor.c	Thu Jan 28 12:25:27 2016	(r294986)
+++ head/sys/arm/arm/debug_monitor.c	Thu Jan 28 12:43:58 2016	(r294987)
@@ -35,14 +35,17 @@ __FBSDID("$FreeBSD$");
 #include <sys/types.h>
 #include <sys/kdb.h>
 #include <sys/pcpu.h>
+#include <sys/smp.h>
 #include <sys/systm.h>
 
+#include <machine/atomic.h>
 #include <machine/armreg.h>
 #include <machine/cpu.h>
 #include <machine/debug_monitor.h>
 #include <machine/kdb.h>
 #include <machine/param.h>
 #include <machine/pcb.h>
+#include <machine/reg.h>
 
 #include <ddb/ddb.h>
 #include <ddb/db_access.h>
@@ -80,7 +83,7 @@ static boolean_t dbg_ossr;	/* OS Save an
 static uint32_t dbg_watchpoint_num;
 static uint32_t dbg_breakpoint_num;
 
-static int dbg_ref_count_mme[MAXCPU]; /* Times monitor mode was enabled */
+static int dbg_ref_count_mme; /* Times monitor mode was enabled */
 
 /* ID_DFR0 - Debug Feature Register 0 */
 #define	ID_DFR0_CP_DEBUG_M_SHIFT	0
@@ -542,11 +545,9 @@ dbg_enable_monitor(void)
 {
 	uint32_t dbg_dscr;
 
-	/* Already enabled? Just increment reference counter and return */
-	if (dbg_monitor_is_enabled()) {
-		dbg_ref_count_mme[PCPU_GET(cpuid)]++;
+	/* Already enabled? Just return */
+	if (dbg_monitor_is_enabled())
 		return (0);
-	}
 
 	dbg_dscr = cp14_dbgdscrint_get();
 
@@ -565,10 +566,8 @@ dbg_enable_monitor(void)
 	isb();
 
 	/* Verify that Monitor mode is set */
-	if (dbg_monitor_is_enabled()) {
-		dbg_ref_count_mme[PCPU_GET(cpuid)]++;
+	if (dbg_monitor_is_enabled())
 		return (0);
-	}
 
 	return (ENXIO);
 }
@@ -581,9 +580,6 @@ dbg_disable_monitor(void)
 	if (!dbg_monitor_is_enabled())
 		return (0);
 
-	if (--dbg_ref_count_mme[PCPU_GET(cpuid)] > 0)
-		return (0);
-
 	dbg_dscr = cp14_dbgdscrint_get();
 	switch (dbg_model) {
 	case ID_DFR0_CP_DEBUG_M_V6:
@@ -607,11 +603,13 @@ dbg_disable_monitor(void)
 static int
 dbg_setup_xpoint(struct dbg_wb_conf *conf)
 {
+	struct pcpu *pcpu;
+	struct dbreg *d;
 	const char *typestr;
 	uint32_t cr_size, cr_priv, cr_access;
 	uint32_t reg_ctrl, reg_addr, ctrl, addr;
 	boolean_t is_bkpt;
-	u_int cpuid;
+	u_int cpuid, cpu;
 	u_int i;
 	int err;
 
@@ -705,20 +703,52 @@ dbg_setup_xpoint(struct dbg_wb_conf *con
 	dbg_wb_write_reg(reg_addr, i, addr);
 	dbg_wb_write_reg(reg_ctrl, i, ctrl);
 
-	return (dbg_enable_monitor());
+	err = dbg_enable_monitor();
+	if (err != 0)
+		return (err);
+
+	/* Increment monitor enable counter */
+	dbg_ref_count_mme++;
+
+	/*
+	 * Save watchpoint settings for all CPUs.
+	 * We don't need to do the same with breakpoints since HW breakpoints
+	 * are only used to perform single stepping.
+	 */
+	if (!is_bkpt) {
+		CPU_FOREACH(cpu) {
+			pcpu = pcpu_find(cpu);
+			/* Fill out the settings for watchpoint */
+			d = (struct dbreg *)pcpu->pc_dbreg;
+			d->dbg_wvr[i] = addr;
+			d->dbg_wcr[i] = ctrl;
+			/* Skip update command for the current CPU */
+			if (cpu != cpuid)
+				pcpu->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
+		}
+	}
+	/* Ensure all data is written before waking other CPUs */
+	atomic_thread_fence_rel();
+
+	return (0);
 }
 
 static int
 dbg_remove_xpoint(struct dbg_wb_conf *conf)
 {
+	struct pcpu *pcpu;
+	struct dbreg *d;
 	uint32_t reg_ctrl, reg_addr, addr;
-	u_int cpuid;
+	boolean_t is_bkpt;
+	u_int cpuid, cpu;
 	u_int i;
 	int err;
 
 	if (!dbg_capable)
 		return (ENXIO);
 
+	is_bkpt = (conf->type == DBG_TYPE_BREAKPOINT);
+
 	cpuid = PCPU_GET(cpuid);
 	if (!dbg_ready[cpuid]) {
 		err = dbg_reset_state();
@@ -729,7 +759,7 @@ dbg_remove_xpoint(struct dbg_wb_conf *co
 
 	addr = conf->address;
 
-	if (conf->type == DBG_TYPE_BREAKPOINT) {
+	if (is_bkpt) {
 		i = conf->slot;
 		reg_ctrl = DBG_REG_BASE_BCR;
 		reg_addr = DBG_REG_BASE_BVR;
@@ -746,7 +776,40 @@ dbg_remove_xpoint(struct dbg_wb_conf *co
 	dbg_wb_write_reg(reg_ctrl, i, 0);
 	dbg_wb_write_reg(reg_addr, i, 0);
 
-	return (dbg_disable_monitor());
+	/* Decrement monitor enable counter */
+	dbg_ref_count_mme--;
+	if (dbg_ref_count_mme < 0)
+		dbg_ref_count_mme = 0;
+
+	atomic_thread_fence_rel();
+
+	if (dbg_ref_count_mme == 0) {
+		err = dbg_disable_monitor();
+		if (err != 0)
+			return (err);
+	}
+
+	/*
+	 * Save watchpoint settings for all CPUs.
+	 * We don't need to do the same with breakpoints since HW breakpoints
+	 * are only used to perform single stepping.
+	 */
+	if (!is_bkpt) {
+		CPU_FOREACH(cpu) {
+			pcpu = pcpu_find(cpu);
+			/* Fill out the settings for watchpoint */
+			d = (struct dbreg *)pcpu->pc_dbreg;
+			d->dbg_wvr[i] = 0;
+			d->dbg_wcr[i] = 0;
+			/* Skip update command for the current CPU */
+			if (cpu != cpuid)
+				pcpu->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
+		}
+		/* Ensure all data is written before waking other CPUs */
+		atomic_thread_fence_rel();
+	}
+
+	return (0);
 }
 
 static __inline uint32_t
@@ -941,3 +1004,67 @@ dbg_monitor_init(void)
 	db_printf("HW Breakpoints/Watchpoints not enabled on CPU%d\n",
 	    PCPU_GET(cpuid));
 }
+
+CTASSERT(sizeof(struct dbreg) == sizeof(((struct pcpu *)NULL)->pc_dbreg));
+
+void
+dbg_resume_dbreg(void)
+{
+	struct dbreg *d;
+	u_int cpuid;
+	u_int i;
+	int err;
+
+	/*
+	 * This flag is set on the primary CPU
+	 * and its meaning is valid for other CPUs too.
+	 */
+	if (!dbg_capable)
+		return;
+
+	atomic_thread_fence_acq();
+
+	switch (PCPU_GET(dbreg_cmd)) {
+	case PC_DBREG_CMD_LOAD:
+		d = (struct dbreg *)PCPU_PTR(dbreg);
+		cpuid = PCPU_GET(cpuid);
+
+		/* Reset Debug Architecture State if not done earlier */
+		if (!dbg_ready[cpuid]) {
+			err = dbg_reset_state();
+			if (err != 0) {
+				/*
+				 * Something is very wrong.
+				 * WPs/BPs will not work correctly in this CPU.
+				 */
+				panic("%s: Failed to reset Debug Architecture "
+				    "state on CPU%d", __func__, cpuid);
+			}
+			dbg_ready[cpuid] = TRUE;
+		}
+
+		/* Restore watchpoints */
+		for (i = 0; i < dbg_watchpoint_num; i++) {
+			dbg_wb_write_reg(DBG_REG_BASE_WVR, i, d->dbg_wvr[i]);
+			dbg_wb_write_reg(DBG_REG_BASE_WCR, i, d->dbg_wcr[i]);
+		}
+
+		if ((dbg_ref_count_mme > 0) && !dbg_monitor_is_enabled()) {
+			err = dbg_enable_monitor();
+			if (err != 0) {
+				panic("%s: Failed to enable Debug Monitor "
+				    "on CPU%d", __func__, cpuid);
+			}
+		}
+		if ((dbg_ref_count_mme == 0) && dbg_monitor_is_enabled()) {
+			err = dbg_disable_monitor();
+			if (err != 0) {
+				panic("%s: Failed to disable Debug Monitor "
+				    "on CPU%d", __func__, cpuid);
+			}
+		}
+
+		PCPU_SET(dbreg_cmd, PC_DBREG_CMD_NONE);
+		break;
+	}
+}

Modified: head/sys/arm/arm/mp_machdep.c
==============================================================================
--- head/sys/arm/arm/mp_machdep.c	Thu Jan 28 12:25:27 2016	(r294986)
+++ head/sys/arm/arm/mp_machdep.c	Thu Jan 28 12:43:58 2016	(r294987)
@@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$");
 #include <machine/armreg.h>
 #include <machine/cpu.h>
 #include <machine/cpufunc.h>
+#include <machine/debug_monitor.h>
 #include <machine/smp.h>
 #include <machine/pcb.h>
 #include <machine/pmap.h>
@@ -303,6 +304,9 @@ ipi_stop(void *dummy __unused)
 
 	CPU_CLR_ATOMIC(cpu, &started_cpus);
 	CPU_CLR_ATOMIC(cpu, &stopped_cpus);
+#ifdef DDB
+	dbg_resume_dbreg();
+#endif
 	CTR0(KTR_SMP, "IPI_STOP (restart)");
 }
 
@@ -405,6 +409,9 @@ ipi_handler(void *arg)
 
 			CPU_CLR_ATOMIC(cpu, &started_cpus);
 			CPU_CLR_ATOMIC(cpu, &stopped_cpus);
+#ifdef DDB
+			dbg_resume_dbreg();
+#endif
 			CTR0(KTR_SMP, "IPI_STOP (restart)");
 			break;
 		case IPI_PREEMPT:

Modified: head/sys/arm/include/debug_monitor.h
==============================================================================
--- head/sys/arm/include/debug_monitor.h	Thu Jan 28 12:25:27 2016	(r294986)
+++ head/sys/arm/include/debug_monitor.h	Thu Jan 28 12:43:58 2016	(r294987)
@@ -48,6 +48,7 @@ void dbg_monitor_init(void);
 void dbg_show_watchpoint(void);
 int dbg_setup_watchpoint(db_expr_t, db_expr_t, enum dbg_access_t);
 int dbg_remove_watchpoint(db_expr_t, db_expr_t);
+void dbg_resume_dbreg(void);
 #else /* __ARM_ARCH >= 6 */
 static __inline void
 dbg_show_watchpoint(void)
@@ -68,6 +69,11 @@ static __inline void
 dbg_monitor_init(void)
 {
 }
+
+static __inline void
+dbg_resume_dbreg(void)
+{
+}
 #endif /* __ARM_ARCH < 6 */
 
 #else /* DDB */

Modified: head/sys/arm/include/pcpu.h
==============================================================================
--- head/sys/arm/include/pcpu.h	Thu Jan 28 12:25:27 2016	(r294986)
+++ head/sys/arm/include/pcpu.h	Thu Jan 28 12:43:58 2016	(r294987)
@@ -49,7 +49,9 @@ struct vmspace;
 	struct pmap *pc_curpmap;					\
 	vm_offset_t pc_qmap_addr;					\
 	void *pc_qmap_pte;						\
-	char __pad[133]
+	unsigned int pc_dbreg[32];					\
+	int pc_dbreg_cmd;						\
+	char __pad[1]
 #else
 #define PCPU_MD_FIELDS							\
 	vm_offset_t qmap_addr;						\
@@ -59,6 +61,9 @@ struct vmspace;
 
 #ifdef _KERNEL
 
+#define	PC_DBREG_CMD_NONE	0
+#define	PC_DBREG_CMD_LOAD	1
+
 struct pcb;
 struct pcpu;
 

Modified: head/sys/arm/include/reg.h
==============================================================================
--- head/sys/arm/include/reg.h	Thu Jan 28 12:25:27 2016	(r294986)
+++ head/sys/arm/include/reg.h	Thu Jan 28 12:43:58 2016	(r294987)
@@ -19,7 +19,9 @@ struct fpreg {
 };
 
 struct dbreg {
-	        unsigned int  dr[8];    /* debug registers */
+#define	ARM_WR_MAX	16 /* Maximum number of watchpoint registers */
+	unsigned int dbg_wcr[ARM_WR_MAX]; /* Watchpoint Control Registers */
+	unsigned int dbg_wvr[ARM_WR_MAX]; /* Watchpoint Value Registers */
 };
 
 #ifdef _KERNEL


More information about the svn-src-head mailing list