From: Jinchao Wang <[email protected]> The new arch_reinstall_hw_breakpoint() function can be used in an atomic context, unlike the more expensive free and re-allocation path. This allows callers to efficiently re-establish an existing breakpoint.
Signed-off-by: Jinchao Wang <[email protected]> Reviewed-by: Masami Hiramatsu (Google) <[email protected]> --- Changes in v9: - Update commit message. - Temporarily disable the active slot in setup_hwbp() before updating the address register to avoid spurious debug exceptions. --- arch/x86/include/asm/hw_breakpoint.h | 2 ++ arch/x86/kernel/hw_breakpoint.c | 34 ++++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw_breakpoint.h index aa6adac6c3a2..c22cc4e87fc5 100644 --- a/arch/x86/include/asm/hw_breakpoint.h +++ b/arch/x86/include/asm/hw_breakpoint.h @@ -21,6 +21,7 @@ struct arch_hw_breakpoint { enum bp_slot_action { BP_SLOT_ACTION_INSTALL, + BP_SLOT_ACTION_REINSTALL, BP_SLOT_ACTION_UNINSTALL, }; @@ -65,6 +66,7 @@ extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused, int arch_install_hw_breakpoint(struct perf_event *bp); +int arch_reinstall_hw_breakpoint(struct perf_event *bp); void arch_uninstall_hw_breakpoint(struct perf_event *bp); void hw_breakpoint_pmu_read(struct perf_event *bp); void hw_breakpoint_pmu_unthrottle(struct perf_event *bp); diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoint.c index c323c2aab2af..0df3ff556f47 100644 --- a/arch/x86/kernel/hw_breakpoint.c +++ b/arch/x86/kernel/hw_breakpoint.c @@ -100,6 +100,10 @@ static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action) old_bp = NULL; new_bp = bp; break; + case BP_SLOT_ACTION_REINSTALL: + old_bp = bp; + new_bp = bp; + break; case BP_SLOT_ACTION_UNINSTALL: old_bp = bp; new_bp = NULL; @@ -129,23 +133,36 @@ static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action) static void setup_hwbp(struct arch_hw_breakpoint *info, int slot, bool enable) { unsigned long dr7; - - set_debugreg(info->address, slot); - __this_cpu_write(cpu_debugreg[slot], info->address); + bool enabled; dr7 = this_cpu_read(cpu_dr7); + enabled = dr7 & ((DR_LOCAL_ENABLE | DR_GLOBAL_ENABLE) << (slot * DR_ENABLE_SIZE)); dr7 &= ~(__encode_dr7(slot, 0xc, 0x3) | (DR_LOCAL_ENABLE << (slot * DR_ENABLE_SIZE))); - if (enable) - dr7 |= encode_dr7(slot, info->len, info->type); + + /* + * If the slot is currently enabled, disable it first before updating + * the address register to prevent spurious debug exceptions. + */ + if (enable && enabled) { + barrier(); + set_debugreg(dr7, 7); + barrier(); + this_cpu_write(cpu_dr7, dr7); + } + + set_debugreg(info->address, slot); + __this_cpu_write(cpu_debugreg[slot], info->address); /* * Enabling: * Ensure we first write cpu_dr7 before we set the DR7 register. * This ensures an NMI never see cpu_dr7 0 when DR7 is not. */ - if (enable) + if (enable) { + dr7 |= encode_dr7(slot, info->len, info->type); this_cpu_write(cpu_dr7, dr7); + } barrier(); @@ -189,6 +206,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp) return arch_manage_bp(bp, BP_SLOT_ACTION_INSTALL); } +int arch_reinstall_hw_breakpoint(struct perf_event *bp) +{ + return arch_manage_bp(bp, BP_SLOT_ACTION_REINSTALL); +} + void arch_uninstall_hw_breakpoint(struct perf_event *bp) { arch_manage_bp(bp, BP_SLOT_ACTION_UNINSTALL);
