[PATCH v4] ARM, ARM64, LONGARCH, XTENSA: Delay HW BP notification to task_work()
Huacai Chen
chenhuacai at kernel.org
Mon Oct 12 03:15:51 PDT 2026
Hi, Sebastian,
On Mon, Oct 12, 2026 at 5:04 PM Sebastian Andrzej Siewior
<bigeasy at linutronix.de> wrote:
>
> Waiman, Luis, Ada reported that HW breakpoints on ARM64 trigger
> "sleeping while atomic" warnings on PREEMPT_RT. The hardware event is
> delivered with disabled interrupts and perf intrastrucure expects
> disabled interrupts while the overflow callback is invoked.
>
> The callback then sends a SIGTRAP signal for which it acquires
> sighand_struct::siglock, a spinlock_t which becomes a sleeping lock and
> must not be acquired in atomic context.
>
> Delay the event callback until the return to userland.
> Add perf_hwbp_notify(), a generic perf callback which delayes the
> actual callback invocation to task_work_add() callback. This callback
> invokes the architecture defined callback perf_arch_hwbp_send_sig().
> This requires struct callback_head and the functions require
> ARCH_NEED_PERF_HW_NOTIFY to be defined.
>
> This was reported against ARM64. ARM, LongARCH and Xtensa follow the
> same pattern are also converted. Xtensa is the only not supporting
> PREEMPT_RT but now we have all architectures using the same pattern.
>
> Fixes: d8fccd9ca5f90 ("arm64: Allow to enable PREEMPT_RT.")
> Reported-by: Luis Claudio R. Goncalves <lgoncalv at redhat.com>
> Reported-by: Waiman Long <longman at redhat.com>
> Closes: https://lore.kernel.org/all/aho0eqjMESuHxECr@redhat.com/
> Reviewed-by: Ada Couprie Diaz <ada.coupriediaz at arm.com>
> Tested-by: Ada Couprie Diaz <ada.coupriediaz at arm.com> (arm64)
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy at linutronix.de>
> ---
> v3…v4: https://lore.kernel.org/all/20261001143516.Ew8C97WS@linutronix.de/
> - Add fixes tag
> - Rename the functions as suggested by Ada to use the pattern that perf
> uses in other places:
> - perf_arch_hwbp_notify() -> perf_hwbp_notify() (generic)
> - arch_hwbp_send_sig() -> perf_arch_hwbp_send_sig() (arch specific)
> - perf_arch_hwbp_send_sig() -> perf_hwbp_send_sig() (generic)
> - Rename ARCH_NEED_PERF_HW_NOTIF -> ARCH_NEED_PERF_HW_NOTIFY
>
> v2…v3: https://lore.kernel.org/all/20260814085118.OPEA_Ssn@linutronix.de/
> - Add Xtensa for completion
> - sashiko complains and wants TWA_SIGNAL instead TWA_RESUME. His
> argument is that a syscall will trap via get_user() and loop forever
> instead making progress. This is wrong IMHO. ARM64 will single step
> over the watchpoint and continue execution. The only downside is that
> userland will get notified after the syscall completed. So my theory.
> Using TWA_SIGNAL is worse: Assume we have a watchpoint on UADDR and
> are in a futex() syscall. The get_user() invocation will trigger the
> exception, the debug handler will step over and queue a signal. The
> futex code will notice this and return ERESTARTNOINTR. A signal will
> be sent, the syscall restarts, traps onto UADDR again, the loop
> continues. But this should be case now, too…
> Now that I look into arch_build_bp_info() and do actual testing I must
> say arm64 does not support mixed breakpoints. This means there is no
> breakpoint in kernel on a userland address. \o/
>
> v1…v2: https://lore.kernel.org/all/20260713144939.FuCj9yvZ@linutronix.de/
> - sashiko complained that a memory breakpoint might trigger several
> times before a signal is sent if the syscall touches the memory (via
> get_user()) more than once before returning back. This would lead to
> list corruption in task_work_add(). To handle this, there is now a
> variable which is set via xchg before task_work_add() and cleared
> after the signal has been sent.
>
> arch/arm/include/asm/hw_breakpoint.h | 1 +
> arch/arm/kernel/ptrace.c | 7 ++----
> arch/arm64/include/asm/hw_breakpoint.h | 1 +
> arch/arm64/kernel/ptrace.c | 6 ++---
> arch/loongarch/include/asm/hw_breakpoint.h | 1 +
> arch/loongarch/kernel/ptrace.c | 6 ++---
> arch/xtensa/include/asm/hw_breakpoint.h | 1 +
> arch/xtensa/kernel/ptrace.c | 7 ++----
> include/linux/hw_breakpoint.h | 3 +++
> include/linux/perf_event.h | 4 ++++
> kernel/events/core.c | 26 ++++++++++++++++++++++
> 11 files changed, 45 insertions(+), 18 deletions(-)
>
> diff --git a/arch/arm/include/asm/hw_breakpoint.h b/arch/arm/include/asm/hw_breakpoint.h
> index e7f9961c53b2d..fa7fb2b2a392a 100644
> --- a/arch/arm/include/asm/hw_breakpoint.h
> +++ b/arch/arm/include/asm/hw_breakpoint.h
> @@ -7,6 +7,7 @@
> struct task_struct;
>
> #ifdef CONFIG_HAVE_HW_BREAKPOINT
> +#define ARCH_NEED_PERF_HW_NOTIFY
>
> struct arch_hw_breakpoint_ctrl {
> u32 __reserved : 9,
> diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
> index ed7a2a87a6707..db4b28c6e15a4 100644
> --- a/arch/arm/kernel/ptrace.c
> +++ b/arch/arm/kernel/ptrace.c
> @@ -347,9 +347,7 @@ static long ptrace_hbp_idx_to_num(int idx)
> /*
> * Handle hitting a HW-breakpoint.
> */
> -static void ptrace_hbptriggered(struct perf_event *bp,
> - struct perf_sample_data *data,
> - struct pt_regs *regs)
> +void perf_arch_hwbp_send_sig(struct perf_event *bp)
> {
> struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
> long num;
> @@ -424,8 +422,7 @@ static struct perf_event *ptrace_hbp_create(struct task_struct *tsk, int type)
> attr.bp_type = type;
> attr.disabled = 1;
>
> - return register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL,
> - tsk);
> + return register_user_hw_breakpoint(&attr, perf_hwbp_notify, NULL, tsk);
> }
>
> static int ptrace_gethbpregs(struct task_struct *tsk, long num,
> diff --git a/arch/arm64/include/asm/hw_breakpoint.h b/arch/arm64/include/asm/hw_breakpoint.h
> index bd81cf17744af..0bc75f42b0da6 100644
> --- a/arch/arm64/include/asm/hw_breakpoint.h
> +++ b/arch/arm64/include/asm/hw_breakpoint.h
> @@ -124,6 +124,7 @@ extern void hw_breakpoint_pmu_read(struct perf_event *bp);
> extern int hw_breakpoint_slots(int type);
>
> #ifdef CONFIG_HAVE_HW_BREAKPOINT
> +#define ARCH_NEED_PERF_HW_NOTIFY
> extern void hw_breakpoint_thread_switch(struct task_struct *next);
> extern void ptrace_hw_copy_thread(struct task_struct *task);
> #else
> diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
> index f743cbec1c3ad..dc3793c2a7c3c 100644
> --- a/arch/arm64/kernel/ptrace.c
> +++ b/arch/arm64/kernel/ptrace.c
> @@ -168,9 +168,7 @@ void ptrace_disable(struct task_struct *child)
> /*
> * Handle hitting a HW-breakpoint.
> */
> -static void ptrace_hbptriggered(struct perf_event *bp,
> - struct perf_sample_data *data,
> - struct pt_regs *regs)
> +void perf_arch_hwbp_send_sig(struct perf_event *bp)
> {
> struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
> const char *desc = "Hardware breakpoint trap (ptrace)";
> @@ -312,7 +310,7 @@ static struct perf_event *ptrace_hbp_create(unsigned int note_type,
> attr.bp_type = type;
> attr.disabled = 1;
>
> - bp = register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL, tsk);
> + bp = register_user_hw_breakpoint(&attr, perf_hwbp_notify, NULL, tsk);
> if (IS_ERR(bp))
> return bp;
>
> diff --git a/arch/loongarch/include/asm/hw_breakpoint.h b/arch/loongarch/include/asm/hw_breakpoint.h
> index 5faa97a87a9e2..c2d061ff3763a 100644
> --- a/arch/loongarch/include/asm/hw_breakpoint.h
> +++ b/arch/loongarch/include/asm/hw_breakpoint.h
> @@ -120,6 +120,7 @@ void breakpoint_handler(struct pt_regs *regs);
> void watchpoint_handler(struct pt_regs *regs);
>
> #ifdef CONFIG_HAVE_HW_BREAKPOINT
> +#define ARCH_NEED_PERF_HW_NOTIFY
> extern void ptrace_hw_copy_thread(struct task_struct *task);
> extern void hw_breakpoint_thread_switch(struct task_struct *next);
> #else
> diff --git a/arch/loongarch/kernel/ptrace.c b/arch/loongarch/kernel/ptrace.c
> index be38430f7e280..27e7e64591592 100644
> --- a/arch/loongarch/kernel/ptrace.c
> +++ b/arch/loongarch/kernel/ptrace.c
> @@ -384,9 +384,7 @@ static int lbt_set(struct task_struct *target,
> /*
> * Handle hitting a HW-breakpoint.
> */
> -static void ptrace_hbptriggered(struct perf_event *bp,
> - struct perf_sample_data *data,
> - struct pt_regs *regs)
> +void perf_arch_hwbp_send_sig(struct perf_event *bp)
> {
> int i;
> struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
> @@ -479,7 +477,7 @@ static struct perf_event *ptrace_hbp_create(unsigned int note_type,
> attr.bp_type = type;
> attr.disabled = 1;
>
> - bp = register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL, tsk);
> + bp = register_user_hw_breakpoint(&attr, perf_hwbp_notify, NULL, tsk);
> if (IS_ERR(bp))
> return bp;
>
> diff --git a/arch/xtensa/include/asm/hw_breakpoint.h b/arch/xtensa/include/asm/hw_breakpoint.h
> index 9ec86f440a48e..44ea340c88742 100644
> --- a/arch/xtensa/include/asm/hw_breakpoint.h
> +++ b/arch/xtensa/include/asm/hw_breakpoint.h
> @@ -12,6 +12,7 @@
> #define __ASM_XTENSA_HW_BREAKPOINT_H
>
> #ifdef CONFIG_HAVE_HW_BREAKPOINT
> +#define ARCH_NEED_PERF_HW_NOTIFY
>
> #include <linux/kdebug.h>
> #include <linux/types.h>
> diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c
> index 364e4fdabb00d..c9f583d650de2 100644
> --- a/arch/xtensa/kernel/ptrace.c
> +++ b/arch/xtensa/kernel/ptrace.c
> @@ -361,9 +361,7 @@ static int ptrace_pokeusr(struct task_struct *child, long regno, long val)
> }
>
> #ifdef CONFIG_HAVE_HW_BREAKPOINT
> -static void ptrace_hbptriggered(struct perf_event *bp,
> - struct perf_sample_data *data,
> - struct pt_regs *regs)
> +void perf_arch_hwbp_send_sig(struct perf_event *bp)
> {
> int i;
> struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
> @@ -395,8 +393,7 @@ static struct perf_event *ptrace_hbp_create(struct task_struct *tsk, int type)
> attr.bp_type = type;
> attr.disabled = 1;
>
> - return register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL,
> - tsk);
> + return register_user_hw_breakpoint(&attr, perf_hwbp_notify, NULL, tsk);
> }
>
> /*
> diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h
> index db199d653dd1a..91e8d80220c54 100644
> --- a/include/linux/hw_breakpoint.h
> +++ b/include/linux/hw_breakpoint.h
> @@ -85,6 +85,9 @@ extern int register_perf_hw_breakpoint(struct perf_event *bp);
> extern void unregister_hw_breakpoint(struct perf_event *bp);
> extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events);
> extern bool hw_breakpoint_is_used(void);
> +extern void perf_arch_hwbp_send_sig(struct perf_event *bp);
> +extern void perf_hwbp_notify(struct perf_event *bp, struct perf_sample_data *data,
> + struct pt_regs *regs);
Maybe this declaration can be put in one line because there is no 80
characters limit now.
Huacai
>
> extern int dbg_reserve_bp_slot(struct perf_event *bp);
> extern int dbg_release_bp_slot(struct perf_event *bp);
> diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
> index 2d5e7687875b0..07f2c858d50d1 100644
> --- a/include/linux/perf_event.h
> +++ b/include/linux/perf_event.h
> @@ -215,6 +215,10 @@ struct hw_perf_event {
>
> /* Last sync'ed generation of filters */
> unsigned long addr_filters_gen;
> +#ifdef ARCH_NEED_PERF_HW_NOTIFY
> + struct callback_head arch_hw_notif;
> + int arch_hw_notif_busy;
> +#endif
>
> /*
> * hw_perf_event::state flags; used to track the PERF_EF_* state.
> diff --git a/kernel/events/core.c b/kernel/events/core.c
> index 7846d70be57f6..619eab68d1dfd 100644
> --- a/kernel/events/core.c
> +++ b/kernel/events/core.c
> @@ -13334,6 +13334,28 @@ static void account_event(struct perf_event *event)
> account_pmu_sb_event(event);
> }
>
> +#ifdef ARCH_NEED_PERF_HW_NOTIFY
> +static void perf_hwbp_send_sig(struct callback_head *head)
> +{
> + struct perf_event *bp;
> +
> + bp = container_of(head, struct perf_event, hw.arch_hw_notif);
> + perf_arch_hwbp_send_sig(bp);
> + xchg_relaxed(&bp->hw.arch_hw_notif_busy, 0);
> + put_event(bp);
> +}
> +
> +void perf_hwbp_notify(struct perf_event *bp, struct perf_sample_data *data,
> + struct pt_regs *regs)
> +{
> + if (WARN_ON_ONCE(!atomic_long_inc_not_zero(&bp->refcount)))
> + return;
> + if (xchg_relaxed(&bp->hw.arch_hw_notif_busy, 1) ||
> + WARN_ON_ONCE(task_work_add(current, &bp->hw.arch_hw_notif, TWA_RESUME)))
> + put_event(bp);
> +}
> +#endif
> +
> /*
> * Allocate and initialize an event structure
> */
> @@ -13439,6 +13461,10 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
> }
>
> if (overflow_handler) {
> +#ifdef ARCH_NEED_PERF_HW_NOTIFY
> + if (overflow_handler == perf_hwbp_notify)
> + init_task_work(&event->hw.arch_hw_notif, perf_hwbp_send_sig);
> +#endif
> event->overflow_handler = overflow_handler;
> event->overflow_handler_context = context;
> } else if (is_write_backward(event)){
> --
> 2.55.0
>
More information about the linux-arm-kernel
mailing list