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@semihalf.com>
Obtained from:  Semihalf
Sponsored by:   Juniper Networks Inc.
Differential Revision: https://reviews.freebsd.org/D4338
This commit is contained in:
Zbigniew Bodek 2016-01-28 12:43:58 +00:00
parent 50102a6342
commit 60ba692c78
5 changed files with 165 additions and 18 deletions

View File

@ -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 and Restore implemented */
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 *conf)
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 *conf)
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 *conf)
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;
}
}

View File

@ -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:

View File

@ -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 */

View File

@ -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;

View File

@ -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