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


Reply via email to