ARM: hw_breakpoint: add support for multiple watchpoints
authorWill Deacon <will.deacon@arm.com>
Tue, 2 Aug 2011 15:16:57 +0000 (16:16 +0100)
committerWill Deacon <will.deacon@arm.com>
Wed, 31 Aug 2011 09:42:48 +0000 (10:42 +0100)
ARM debug architecture 7.1 mandates that the DFAR is updated on a
watchpoint debug exception to contain the faulting virtual address
of the memory access. This allows us to determine which watchpoints
have fired and therefore report useful information to userspace.

This patch adds support for using the DFAR in the watchpoint handler,
which allows us to support multiple watchpoints on CPUs implementing
the new debug architecture.

Signed-off-by: Will Deacon <will.deacon@arm.com>
arch/arm/include/asm/hw_breakpoint.h
arch/arm/kernel/hw_breakpoint.c

index 0ac141a87616e12a0b7d9a294faeb64134e9903a..c190bc992f0e6e917d2071c920312c7391beb5e3 100644 (file)
@@ -58,6 +58,7 @@ static inline void decode_ctrl_reg(u32 reg,
 /* Watchpoints */
 #define ARM_BREAKPOINT_LOAD    1
 #define ARM_BREAKPOINT_STORE   2
+#define ARM_FSR_ACCESS_MASK    (1 << 11)
 
 /* Privilege Levels */
 #define ARM_BREAKPOINT_PRIV    1
index 156b8af13571a975f844c965364f14506e2e220d..448143e44b65e66b096ea686208ee68d90b52c6e 100644 (file)
@@ -339,24 +339,10 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
                val_base = ARM_BASE_BVR;
                slots = (struct perf_event **)__get_cpu_var(bp_on_reg);
                max_slots = core_num_brps;
-               if (info->step_ctrl.enabled) {
-                       /* Override the breakpoint data with the step data. */
-                       addr = info->trigger & ~0x3;
-                       ctrl = encode_ctrl_reg(info->step_ctrl);
-               }
        } else {
                /* Watchpoint */
-               if (info->step_ctrl.enabled) {
-                       /* Install into the reserved breakpoint region. */
-                       ctrl_base = ARM_BASE_BCR + core_num_brps;
-                       val_base = ARM_BASE_BVR + core_num_brps;
-                       /* Override the watchpoint data with the step data. */
-                       addr = info->trigger & ~0x3;
-                       ctrl = encode_ctrl_reg(info->step_ctrl);
-               } else {
-                       ctrl_base = ARM_BASE_WCR;
-                       val_base = ARM_BASE_WVR;
-               }
+               ctrl_base = ARM_BASE_WCR;
+               val_base = ARM_BASE_WVR;
                slots = (struct perf_event **)__get_cpu_var(wp_on_reg);
                max_slots = core_num_wrps;
        }
@@ -375,6 +361,17 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
                goto out;
        }
 
+       /* Override the breakpoint data with the step data. */
+       if (info->step_ctrl.enabled) {
+               addr = info->trigger & ~0x3;
+               ctrl = encode_ctrl_reg(info->step_ctrl);
+               if (info->ctrl.type != ARM_BREAKPOINT_EXECUTE) {
+                       i = 0;
+                       ctrl_base = ARM_BASE_BCR + core_num_brps;
+                       val_base = ARM_BASE_BVR + core_num_brps;
+               }
+       }
+
        /* Setup the address register. */
        write_wb_reg(val_base + i, addr);
 
@@ -398,10 +395,7 @@ void arch_uninstall_hw_breakpoint(struct perf_event *bp)
                max_slots = core_num_brps;
        } else {
                /* Watchpoint */
-               if (info->step_ctrl.enabled)
-                       base = ARM_BASE_BCR + core_num_brps;
-               else
-                       base = ARM_BASE_WCR;
+               base = ARM_BASE_WCR;
                slots = (struct perf_event **)__get_cpu_var(wp_on_reg);
                max_slots = core_num_wrps;
        }
@@ -419,6 +413,13 @@ void arch_uninstall_hw_breakpoint(struct perf_event *bp)
        if (WARN_ONCE(i == max_slots, "Can't find any breakpoint slot\n"))
                return;
 
+       /* Ensure that we disable the mismatch breakpoint. */
+       if (info->ctrl.type != ARM_BREAKPOINT_EXECUTE &&
+           info->step_ctrl.enabled) {
+               i = 0;
+               base = ARM_BASE_BCR + core_num_brps;
+       }
+
        /* Reset the control register. */
        write_wb_reg(base + i, 0);
 }
@@ -659,34 +660,62 @@ static void disable_single_step(struct perf_event *bp)
        arch_install_hw_breakpoint(bp);
 }
 
-static void watchpoint_handler(unsigned long unknown, struct pt_regs *regs)
+static void watchpoint_handler(unsigned long addr, unsigned int fsr,
+                              struct pt_regs *regs)
 {
-       int i;
+       int i, access;
+       u32 val, ctrl_reg, alignment_mask;
        struct perf_event *wp, **slots;
        struct arch_hw_breakpoint *info;
+       struct arch_hw_breakpoint_ctrl ctrl;
 
        slots = (struct perf_event **)__get_cpu_var(wp_on_reg);
 
-       /* Without a disassembler, we can only handle 1 watchpoint. */
-       BUG_ON(core_num_wrps > 1);
-
        for (i = 0; i < core_num_wrps; ++i) {
                rcu_read_lock();
 
                wp = slots[i];
 
-               if (wp == NULL) {
-                       rcu_read_unlock();
-                       continue;
-               }
+               if (wp == NULL)
+                       goto unlock;
 
+               info = counter_arch_bp(wp);
                /*
-                * The DFAR is an unknown value. Since we only allow a
-                * single watchpoint, we can set the trigger to the lowest
-                * possible faulting address.
+                * The DFAR is an unknown value on debug architectures prior
+                * to 7.1. Since we only allow a single watchpoint on these
+                * older CPUs, we can set the trigger to the lowest possible
+                * faulting address.
                 */
-               info = counter_arch_bp(wp);
-               info->trigger = wp->attr.bp_addr;
+               if (debug_arch < ARM_DEBUG_ARCH_V7_1) {
+                       BUG_ON(i > 0);
+                       info->trigger = wp->attr.bp_addr;
+               } else {
+                       if (info->ctrl.len == ARM_BREAKPOINT_LEN_8)
+                               alignment_mask = 0x7;
+                       else
+                               alignment_mask = 0x3;
+
+                       /* Check if the watchpoint value matches. */
+                       val = read_wb_reg(ARM_BASE_WVR + i);
+                       if (val != (addr & ~alignment_mask))
+                               goto unlock;
+
+                       /* Possible match, check the byte address select. */
+                       ctrl_reg = read_wb_reg(ARM_BASE_WCR + i);
+                       decode_ctrl_reg(ctrl_reg, &ctrl);
+                       if (!((1 << (addr & alignment_mask)) & ctrl.len))
+                               goto unlock;
+
+                       /* Check that the access type matches. */
+                       access = (fsr & ARM_FSR_ACCESS_MASK) ? HW_BREAKPOINT_W :
+                                HW_BREAKPOINT_R;
+                       if (!(access & hw_breakpoint_type(wp)))
+                               goto unlock;
+
+                       /* We have a winner. */
+                       info->trigger = addr;
+               }
+
                pr_debug("watchpoint fired: address = 0x%x\n", info->trigger);
                perf_bp_event(wp, regs);
 
@@ -698,6 +727,7 @@ static void watchpoint_handler(unsigned long unknown, struct pt_regs *regs)
                if (!wp->overflow_handler)
                        enable_single_step(wp, instruction_pointer(regs));
 
+unlock:
                rcu_read_unlock();
        }
 }
@@ -813,7 +843,7 @@ static int hw_breakpoint_pending(unsigned long addr, unsigned int fsr,
        case ARM_ENTRY_ASYNC_WATCHPOINT:
                WARN(1, "Asynchronous watchpoint exception taken. Debugging results may be unreliable\n");
        case ARM_ENTRY_SYNC_WATCHPOINT:
-               watchpoint_handler(addr, regs);
+               watchpoint_handler(addr, fsr, regs);
                break;
        default:
                ret = 1; /* Unhandled fault. */