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:
parent
50102a6342
commit
60ba692c78
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user