in kernel/hw_breakpoint.c [754:845]
static int watchpoint_handler(unsigned long addr, unsigned int esr,
struct pt_regs *regs)
{
int i, step = 0, *kernel_step, access, closest_match = 0;
u64 min_dist = -1, dist;
u32 ctrl_reg;
u64 val;
struct perf_event *wp, **slots;
struct debug_info *debug_info;
struct arch_hw_breakpoint_ctrl ctrl;
slots = this_cpu_ptr(wp_on_reg);
debug_info = ¤t->thread.debug;
/*
* Find all watchpoints that match the reported address. If no exact
* match is found. Attribute the hit to the closest watchpoint.
*/
rcu_read_lock();
for (i = 0; i < core_num_wrps; ++i) {
wp = slots[i];
if (wp == NULL)
continue;
/*
* Check that the access type matches.
* 0 => load, otherwise => store
*/
access = (esr & AARCH64_ESR_ACCESS_MASK) ? HW_BREAKPOINT_W :
HW_BREAKPOINT_R;
if (!(access & hw_breakpoint_type(wp)))
continue;
/* Check if the watchpoint value and byte select match. */
val = read_wb_reg(AARCH64_DBG_REG_WVR, i);
ctrl_reg = read_wb_reg(AARCH64_DBG_REG_WCR, i);
decode_ctrl_reg(ctrl_reg, &ctrl);
dist = get_distance_from_watchpoint(addr, val, &ctrl);
if (dist < min_dist) {
min_dist = dist;
closest_match = i;
}
/* Is this an exact match? */
if (dist != 0)
continue;
step = watchpoint_report(wp, addr, regs);
}
/* No exact match found? */
if (min_dist > 0 && min_dist != -1)
step = watchpoint_report(slots[closest_match], addr, regs);
rcu_read_unlock();
if (!step)
return 0;
/*
* We always disable EL0 watchpoints because the kernel can
* cause these to fire via an unprivileged access.
*/
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL0, 0);
if (user_mode(regs)) {
debug_info->wps_disabled = 1;
/* If we're already stepping a breakpoint, just return. */
if (debug_info->bps_disabled)
return 0;
if (test_thread_flag(TIF_SINGLESTEP))
debug_info->suspended_step = 1;
else
user_enable_single_step(current);
} else {
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL1, 0);
kernel_step = this_cpu_ptr(&stepping_kernel_bp);
if (*kernel_step != ARM_KERNEL_STEP_NONE)
return 0;
if (kernel_active_single_step()) {
*kernel_step = ARM_KERNEL_STEP_SUSPEND;
} else {
*kernel_step = ARM_KERNEL_STEP_ACTIVE;
kernel_enable_single_step(regs);
}
}
return 0;
}