Skip to content

Commit ec7dca6

Browse files
committed
arm64: debug: split hardware watchpoint exception entry
JIRA: https://issues.redhat.com/browse/RHEL-65658 commit 413f0bb Author: Ada Couprie Diaz <ada.coupriediaz@arm.com> Date: Mon Jul 7 12:41:06 2025 +0100 arm64: debug: split hardware watchpoint exception entry Currently all debug exceptions share common entry code and are routed to `do_debug_exception()`, which calls dynamically-registered handlers for each specific debug exception. This is unfortunate as different debug exceptions have different entry handling requirements, and it would be better to handle these distinct requirements earlier. Hardware watchpoints are the only debug exceptions that will write FAR_EL1, so we need to preserve it and pass it down. However, they cannot be used to maliciously train branch predictors, so we can omit calling `arm64_bp_hardening()`, nor do they need to handle the Cortex-A76 erratum #1463225, as it only applies to single stepping exceptions. As the hardware watchpoint handler only returns 0 and never triggers the call to `arm64_notify_die()`, we can call it directly from `entry-common.c`. Split the hardware watchpoint exception entry and adjust the behaviour to match the lack of needed mitigations. Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com> Reviewed-by: Will Deacon <will@kernel.org> Acked-by: Mark Rutland <mark.rutland@arm.com> Link: https://lore.kernel.org/r/20250707114109.35672-11-ada.coupriediaz@arm.com Signed-off-by: Will Deacon <will@kernel.org> Signed-off-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
1 parent b06bed7 commit ec7dca6

File tree

3 files changed

+40
-12
lines changed

3 files changed

+40
-12
lines changed

arch/arm64/include/asm/exception.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,12 @@ void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
6161
struct pt_regs *regs);
6262
#ifdef CONFIG_HAVE_HW_BREAKPOINT
6363
void do_breakpoint(unsigned long esr, struct pt_regs *regs);
64+
void do_watchpoint(unsigned long addr, unsigned long esr,
65+
struct pt_regs *regs);
6466
#else
6567
static inline void do_breakpoint(unsigned long esr, struct pt_regs *regs) {}
68+
static inline void do_watchpoint(unsigned long addr, unsigned long esr,
69+
struct pt_regs *regs) {}
6670
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
6771
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
6872
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);

arch/arm64/kernel/entry-common.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,18 @@ static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
512512
arm64_exit_el1_dbg(regs);
513513
}
514514

515+
static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
516+
{
517+
/* Watchpoints are the only debug exception to write FAR_EL1 */
518+
unsigned long far = read_sysreg(far_el1);
519+
520+
arm64_enter_el1_dbg(regs);
521+
debug_exception_enter(regs);
522+
do_watchpoint(far, esr, regs);
523+
debug_exception_exit(regs);
524+
arm64_exit_el1_dbg(regs);
525+
}
526+
515527
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
516528
{
517529
unsigned long far = read_sysreg(far_el1);
@@ -561,6 +573,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
561573
el1_softstp(regs, esr);
562574
break;
563575
case ESR_ELx_EC_WATCHPT_CUR:
576+
el1_watchpt(regs, esr);
577+
break;
564578
case ESR_ELx_EC_BRK64:
565579
el1_dbg(regs, esr);
566580
break;
@@ -777,6 +791,19 @@ static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
777791
exit_to_user_mode(regs);
778792
}
779793

794+
static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
795+
{
796+
/* Watchpoints are the only debug exception to write FAR_EL1 */
797+
unsigned long far = read_sysreg(far_el1);
798+
799+
enter_from_user_mode(regs);
800+
debug_exception_enter(regs);
801+
do_watchpoint(far, esr, regs);
802+
debug_exception_exit(regs);
803+
local_daif_restore(DAIF_PROCCTX);
804+
exit_to_user_mode(regs);
805+
}
806+
780807
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
781808
{
782809
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
@@ -858,6 +885,8 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
858885
el0_softstp(regs, esr);
859886
break;
860887
case ESR_ELx_EC_WATCHPT_LOW:
888+
el0_watchpt(regs, esr);
889+
break;
861890
case ESR_ELx_EC_BRK64:
862891
el0_dbg(regs, esr);
863892
break;
@@ -982,6 +1011,8 @@ asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
9821011
el0_softstp(regs, esr);
9831012
break;
9841013
case ESR_ELx_EC_WATCHPT_LOW:
1014+
el0_watchpt(regs, esr);
1015+
break;
9851016
case ESR_ELx_EC_BKPT32:
9861017
el0_dbg(regs, esr);
9871018
break;

arch/arm64/kernel/hw_breakpoint.c

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -750,8 +750,7 @@ static int watchpoint_report(struct perf_event *wp, unsigned long addr,
750750
return step;
751751
}
752752

753-
static int watchpoint_handler(unsigned long addr, unsigned long esr,
754-
struct pt_regs *regs)
753+
void do_watchpoint(unsigned long addr, unsigned long esr, struct pt_regs *regs)
755754
{
756755
int i, step = 0, *kernel_step, access, closest_match = 0;
757756
u64 min_dist = -1, dist;
@@ -806,7 +805,7 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
806805
rcu_read_unlock();
807806

808807
if (!step)
809-
return 0;
808+
return;
810809

811810
/*
812811
* We always disable EL0 watchpoints because the kernel can
@@ -819,7 +818,7 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
819818

820819
/* If we're already stepping a breakpoint, just return. */
821820
if (debug_info->bps_disabled)
822-
return 0;
821+
return;
823822

824823
if (test_thread_flag(TIF_SINGLESTEP))
825824
debug_info->suspended_step = 1;
@@ -830,7 +829,7 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
830829
kernel_step = this_cpu_ptr(&stepping_kernel_bp);
831830

832831
if (*kernel_step != ARM_KERNEL_STEP_NONE)
833-
return 0;
832+
return;
834833

835834
if (kernel_active_single_step()) {
836835
*kernel_step = ARM_KERNEL_STEP_SUSPEND;
@@ -839,10 +838,8 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
839838
kernel_enable_single_step(regs);
840839
}
841840
}
842-
843-
return 0;
844841
}
845-
NOKPROBE_SYMBOL(watchpoint_handler);
842+
NOKPROBE_SYMBOL(do_watchpoint);
846843

847844
/*
848845
* Handle single-step exception.
@@ -984,10 +981,6 @@ static int __init arch_hw_breakpoint_init(void)
984981
pr_info("found %d breakpoint and %d watchpoint registers.\n",
985982
core_num_brps, core_num_wrps);
986983

987-
/* Register debug fault handlers. */
988-
hook_debug_fault_code(DBG_ESR_EVT_HWWP, watchpoint_handler, SIGTRAP,
989-
TRAP_HWBKPT, "hw-watchpoint handler");
990-
991984
/*
992985
* Reset the breakpoint resources. We assume that a halting
993986
* debugger will leave the world in a nice state for us.

0 commit comments

Comments
 (0)