[PATCH] arm64/debug: mask debug exceptions when called from common code

Ada Couprie Diaz ada.coupriediaz at arm.com
Mon Sep 7 09:31:01 PDT 2026


We can currently take debug exceptions while manipulating the hardware
breakpoint registers and perf slots when called from generic kernel code.
This can lead to errors and unsound behaviours in some pathological
(and self-inflicted) edge cases[0].

Save and restore the interrupt mask around those manipulations.

This is to avoid a superfluous save/restore when calling
`toggle_bp_registers()` and `hw_breakpoint_control()` from our arch code,
as debug exceptions will already be masked in all cases.
It should also allow some clean ups by batching ISBs when we update
multiple registers, rather than doing one for each register write.

Clarify some comments and specify a few functions which should be called
with debug exceptions masked (even though that is true for most of them).

[0]: https://lore.kernel.org/linux-arm-kernel/adeE4MD0RgapI8aL@J2N7QTR9R3/

Signed-off-by: Ada Couprie Diaz <ada.coupriediaz at arm.com>
----
This is a follow-up of [1], which blanketly masks debug exceptions rather
than fiddling with specific edge cases one by one as discussed with Will.

I'm not sure about specifying that `toggle_bp_registers()` and
`hw_breakpoint_control()` must be called with debug exceptions masked,
as most other functions here do too, but those are the only one that need it
which are called via common code, rather than arch entry or debug code.

Thanks,
Ada

[1]: https://lore.kernel.org/r/20260527161553.97676-1-ada.coupriediaz@arm.com
---
 arch/arm64/kernel/hw_breakpoint.c | 32 ++++++++++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/kernel/hw_breakpoint.c b/arch/arm64/kernel/hw_breakpoint.c
index 73cce8ac8368..32b38967433e 100644
--- a/arch/arm64/kernel/hw_breakpoint.c
+++ b/arch/arm64/kernel/hw_breakpoint.c
@@ -20,6 +20,7 @@
 #include <linux/uaccess.h>
 
 #include <asm/current.h>
+#include <asm/daifflags.h>
 #include <asm/debug-monitors.h>
 #include <asm/esr.h>
 #include <asm/exception.h>
@@ -221,6 +222,12 @@ static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots,
 	return -ENOSPC;
 }
 
+/*
+ * Configure hardware breakpoint registers from a perf event,
+ * according to operation.
+ *
+ * Must be called with debug exceptions masked.
+ */
 static int hw_breakpoint_control(struct perf_event *bp,
 				 enum hw_breakpoint_ops ops)
 {
@@ -286,15 +293,24 @@ static int hw_breakpoint_control(struct perf_event *bp,
 
 /*
  * Install a perf counter breakpoint.
+ *
+ * Perf can and will call this with debug exceptions unmasked.
+ * Mask them to avoid triggering one while manipulating their registers.
  */
 int arch_install_hw_breakpoint(struct perf_event *bp)
 {
-	return hw_breakpoint_control(bp, HW_BREAKPOINT_INSTALL);
+	unsigned long daif = local_daif_save();
+	int ret = hw_breakpoint_control(bp, HW_BREAKPOINT_INSTALL);
+
+	local_daif_restore(daif);
+	return ret;
 }
 
 void arch_uninstall_hw_breakpoint(struct perf_event *bp)
 {
+	unsigned long daif = local_daif_save();
 	hw_breakpoint_control(bp, HW_BREAKPOINT_UNINSTALL);
+	local_daif_restore(daif);
 }
 
 static int get_hbp_len(u8 hbp_len)
@@ -586,7 +602,10 @@ int hw_breakpoint_arch_parse(struct perf_event *bp,
 /*
  * Enable/disable all of the breakpoints active at the specified
  * exception level at the register level.
- * This is used when single-stepping after a breakpoint exception.
+ * This is used when single-stepping after a breakpoint exception or switching
+ * threads.
+ *
+ * Must be called with debug exceptions masked.
  */
 static void toggle_bp_registers(int reg, enum dbg_active_el el, int enable)
 {
@@ -908,7 +927,11 @@ bool try_step_suspended_breakpoints(struct pt_regs *regs)
 NOKPROBE_SYMBOL(try_step_suspended_breakpoints);
 
 /*
- * Context-switcher for restoring suspended breakpoints.
+ * Context-switcher for restoring suspended CPU-bound breakpoints.
+ *
+ * Breakpoints of the previous task are uninstalled before
+ * this function is called, in perf_event_task_sched_out(), and those
+ * of the next task are installed after, in perf_event_task_sched_in().
  */
 void hw_breakpoint_thread_switch(struct task_struct *next)
 {
@@ -922,6 +945,7 @@ void hw_breakpoint_thread_switch(struct task_struct *next)
 	 */
 
 	struct debug_info *current_debug_info, *next_debug_info;
+	unsigned long daif = local_daif_save();
 
 	current_debug_info = &current->thread.debug;
 	next_debug_info = &next->thread.debug;
@@ -937,6 +961,8 @@ void hw_breakpoint_thread_switch(struct task_struct *next)
 		toggle_bp_registers(AARCH64_DBG_REG_WCR,
 				    DBG_ACTIVE_EL0,
 				    !next_debug_info->wps_disabled);
+
+	local_daif_restore(daif);
 }
 
 /*

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
-- 
2.43.0




More information about the linux-arm-kernel mailing list