All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work()
@ 2026-07-13 14:49 ` Sebastian Andrzej Siewior
  0 siblings, 0 replies; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-07-13 14:49 UTC (permalink / raw)
  To: Luis Claudio R. Goncalves
  Cc: Waiman Long, Catalin Marinas, Will Deacon, Mark Rutland,
	Clark Williams, Steven Rostedt, linux-arm-kernel, linux-kernel,
	linux-rt-devel, Ada Couprie Diaz, Adrian Hunter,
	Alexander Shishkin, Arnaldo Carvalho de Melo, Huacai Chen,
	Ian Rogers, Ingo Molnar, James Clark, Jiri Olsa, Namhyung Kim,
	Oleg Nesterov, Peter Zijlstra, Russell King, WANG Xuerui,
	linux-perf-users, loongarch

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_arch_hwbp_notify(), a generic perf callback which delayes the
actual callback invocation to task_work_add() callback. This callback
invokes the architecture defines callback arch_hwbp_send_sig().
This requires struct callback_head and the functions require
ARCH_NEED_PERF_HW_NOTIF to be defined.

This was reported against ARM64. ARM and LongARCH follow the same
pattern are also converted.

Reported-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reported-by: Waiman Long <longman@redhat.com>
Closes: https://lore.kernel.org/all/aho0eqjMESuHxECr@redhat.com/
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---

This is approximately the fifth iteration at which point I think I can
share what I have.

Could this be tested, please? I tested it on arm64 for arm64 based on
Luis' test. ARM compiles my HW lacks support for this I think (gdb
aborts early). I hope the best for LoongARCH.

Are the perf bits okay or is it too gross?

 arch/arm/include/asm/hw_breakpoint.h       |  1 +
 arch/arm/kernel/ptrace.c                   |  6 ++----
 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 ++----
 include/linux/hw_breakpoint.h              |  3 +++
 include/linux/perf_event.h                 |  3 +++
 kernel/events/core.c                       | 24 ++++++++++++++++++++++
 9 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/arch/arm/include/asm/hw_breakpoint.h b/arch/arm/include/asm/hw_breakpoint.h
index e7f9961c53b2d..5b3a373348ff5 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_NOTIF
 
 struct arch_hw_breakpoint_ctrl {
 		u32 __reserved	: 9,
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
index 7951b2c06fec6..1650f12a04702 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 arch_hwbp_send_sig(struct perf_event *bp)
 {
 	struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
 	long num;
@@ -424,7 +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,
+	return register_user_hw_breakpoint(&attr, perf_arch_hwbp_notify, NULL,
 					   tsk);
 }
 
diff --git a/arch/arm64/include/asm/hw_breakpoint.h b/arch/arm64/include/asm/hw_breakpoint.h
index bd81cf17744af..58befb79c885c 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_NOTIF
 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 4d08598e2891d..df6122779ada6 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 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_arch_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..2b51922eb5492 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_NOTIF
 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..1d5e05a70b8d3 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 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_arch_hwbp_notify, NULL, tsk);
 	if (IS_ERR(bp))
 		return bp;
 
diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h
index db199d653dd1a..bb4c1064a103d 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 arch_hwbp_send_sig(struct perf_event *bp);
+extern void perf_arch_hwbp_notify(struct perf_event *bp, struct perf_sample_data *data,
+				  struct pt_regs *regs);
 
 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 48d851fbd8ea5..bc7fae88a2742 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -215,6 +215,9 @@ struct hw_perf_event {
 
 	/* Last sync'ed generation of filters */
 	unsigned long			addr_filters_gen;
+#ifdef ARCH_NEED_PERF_HW_NOTIF
+	struct callback_head		arch_hw_notif;
+#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 d7f3e2c2ecb1e..1f70cdb52f068 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -13336,6 +13336,26 @@ static void account_event(struct perf_event *event)
 	account_pmu_sb_event(event);
 }
 
+#ifdef ARCH_NEED_PERF_HW_NOTIF
+static void perf_arch_hwbp_send_sig(struct callback_head *head)
+{
+	struct perf_event *bp;
+
+	bp = container_of(head, struct perf_event, hw.arch_hw_notif);
+	arch_hwbp_send_sig(bp);
+	put_event(bp);
+}
+
+void perf_arch_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 (WARN_ON_ONCE(task_work_add(current, &bp->hw.arch_hw_notif, TWA_RESUME)))
+		put_event(bp);
+}
+#endif
+
 /*
  * Allocate and initialize an event structure
  */
@@ -13441,6 +13461,10 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
 	}
 
 	if (overflow_handler) {
+#ifdef ARCH_NEED_PERF_HW_NOTIF
+		if (overflow_handler == perf_arch_hwbp_notify)
+			init_task_work(&event->hw.arch_hw_notif, perf_arch_hwbp_send_sig);
+#endif
 		event->overflow_handler	= overflow_handler;
 		event->overflow_handler_context = context;
 	} else if (is_write_backward(event)){
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work()
@ 2026-07-13 14:49 ` Sebastian Andrzej Siewior
  0 siblings, 0 replies; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-07-13 14:49 UTC (permalink / raw)
  To: Luis Claudio R. Goncalves
  Cc: Mark Rutland, Ian Rogers, Alexander Shishkin, Catalin Marinas,
	linux-rt-devel, linux-kernel, WANG Xuerui, Will Deacon,
	Huacai Chen, Russell King, Peter Zijlstra, Ingo Molnar,
	Waiman Long, Clark Williams, James Clark, Steven Rostedt,
	Arnaldo Carvalho de Melo, loongarch, Namhyung Kim, Adrian Hunter,
	linux-arm-kernel, Oleg Nesterov, linux-perf-users, Jiri Olsa

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_arch_hwbp_notify(), a generic perf callback which delayes the
actual callback invocation to task_work_add() callback. This callback
invokes the architecture defines callback arch_hwbp_send_sig().
This requires struct callback_head and the functions require
ARCH_NEED_PERF_HW_NOTIF to be defined.

This was reported against ARM64. ARM and LongARCH follow the same
pattern are also converted.

Reported-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reported-by: Waiman Long <longman@redhat.com>
Closes: https://lore.kernel.org/all/aho0eqjMESuHxECr@redhat.com/
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---

This is approximately the fifth iteration at which point I think I can
share what I have.

Could this be tested, please? I tested it on arm64 for arm64 based on
Luis' test. ARM compiles my HW lacks support for this I think (gdb
aborts early). I hope the best for LoongARCH.

Are the perf bits okay or is it too gross?

 arch/arm/include/asm/hw_breakpoint.h       |  1 +
 arch/arm/kernel/ptrace.c                   |  6 ++----
 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 ++----
 include/linux/hw_breakpoint.h              |  3 +++
 include/linux/perf_event.h                 |  3 +++
 kernel/events/core.c                       | 24 ++++++++++++++++++++++
 9 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/arch/arm/include/asm/hw_breakpoint.h b/arch/arm/include/asm/hw_breakpoint.h
index e7f9961c53b2d..5b3a373348ff5 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_NOTIF
 
 struct arch_hw_breakpoint_ctrl {
 		u32 __reserved	: 9,
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
index 7951b2c06fec6..1650f12a04702 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 arch_hwbp_send_sig(struct perf_event *bp)
 {
 	struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
 	long num;
@@ -424,7 +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,
+	return register_user_hw_breakpoint(&attr, perf_arch_hwbp_notify, NULL,
 					   tsk);
 }
 
diff --git a/arch/arm64/include/asm/hw_breakpoint.h b/arch/arm64/include/asm/hw_breakpoint.h
index bd81cf17744af..58befb79c885c 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_NOTIF
 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 4d08598e2891d..df6122779ada6 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 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_arch_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..2b51922eb5492 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_NOTIF
 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..1d5e05a70b8d3 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 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_arch_hwbp_notify, NULL, tsk);
 	if (IS_ERR(bp))
 		return bp;
 
diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h
index db199d653dd1a..bb4c1064a103d 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 arch_hwbp_send_sig(struct perf_event *bp);
+extern void perf_arch_hwbp_notify(struct perf_event *bp, struct perf_sample_data *data,
+				  struct pt_regs *regs);
 
 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 48d851fbd8ea5..bc7fae88a2742 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -215,6 +215,9 @@ struct hw_perf_event {
 
 	/* Last sync'ed generation of filters */
 	unsigned long			addr_filters_gen;
+#ifdef ARCH_NEED_PERF_HW_NOTIF
+	struct callback_head		arch_hw_notif;
+#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 d7f3e2c2ecb1e..1f70cdb52f068 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -13336,6 +13336,26 @@ static void account_event(struct perf_event *event)
 	account_pmu_sb_event(event);
 }
 
+#ifdef ARCH_NEED_PERF_HW_NOTIF
+static void perf_arch_hwbp_send_sig(struct callback_head *head)
+{
+	struct perf_event *bp;
+
+	bp = container_of(head, struct perf_event, hw.arch_hw_notif);
+	arch_hwbp_send_sig(bp);
+	put_event(bp);
+}
+
+void perf_arch_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 (WARN_ON_ONCE(task_work_add(current, &bp->hw.arch_hw_notif, TWA_RESUME)))
+		put_event(bp);
+}
+#endif
+
 /*
  * Allocate and initialize an event structure
  */
@@ -13441,6 +13461,10 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
 	}
 
 	if (overflow_handler) {
+#ifdef ARCH_NEED_PERF_HW_NOTIF
+		if (overflow_handler == perf_arch_hwbp_notify)
+			init_task_work(&event->hw.arch_hw_notif, perf_arch_hwbp_send_sig);
+#endif
 		event->overflow_handler	= overflow_handler;
 		event->overflow_handler_context = context;
 	} else if (is_write_backward(event)){
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work()
  2026-07-13 14:49 ` Sebastian Andrzej Siewior
  (?)
@ 2026-07-13 15:05 ` sashiko-bot
  -1 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-13 15:05 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior; +Cc: linux-perf-users, linux-rt-devel, linux-kernel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Queueing the same hardware breakpoint callback_head multiple times corrupts the task_works list, leading to an infinite loop and Use-After-Free.
- [High] The use of TWA_RESUME fails to interrupt blocking system calls when a watchpoint triggers, potentially hanging the tracee.
--

--- Patch [1]: [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work() ---
commit edeb5ce1fcde3854d06fcd1845bb9a1a8e7ef02e
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>

ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work()

This patch delays the delivery of hardware breakpoint events to task_work()
to avoid sleeping while atomic warnings on PREEMPT_RT. It introduces a
generic callback perf_arch_hwbp_notify() that defers the architecture
specific signal generation to a return to user space.

Link: https://lore.kernel.org/all/aho0eqjMESuHxECr@redhat.com/

> diff --git a/kernel/events/core.c b/kernel/events/core.c
> index 6d1f8bad7e1c5..9674cd59703fb 100644
> --- a/kernel/events/core.c
> +++ b/kernel/events/core.c

[ ... ]

> @@ -13259,6 +13259,26 @@ static void account_event(struct perf_event *event)
>  	account_pmu_sb_event(event);
>  }
>  
> +#ifdef ARCH_NEED_PERF_HW_NOTIF
> +static void perf_arch_hwbp_send_sig(struct callback_head *head)
> +{
> +	struct perf_event *bp;
> +
> +	bp = container_of(head, struct perf_event, hw.arch_hw_notif);
> +	arch_hwbp_send_sig(bp);
> +	put_event(bp);
> +}
> +
> +void perf_arch_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 (WARN_ON_ONCE(task_work_add(current, &bp->hw.arch_hw_notif, TWA_RESUME)))

[Severity: Critical]
If a user-space process sets a hardware watchpoint on a buffer and passes it
to a syscall that accesses the buffer multiple times (like copy_to_user),
can this queue the same callback_head multiple times before returning to
user space?

Because arch_hw_notif is a statically embedded callback_head, calling
task_work_add() a second time for the same event without checking if it is
already pending will overwrite its next pointer to point to itself. When
task_work_run() executes, the subsequent execution loop will loop infinitely
because next and work remain the same. 

The callback perf_arch_hwbp_send_sig() also calls put_event(bp), dropping
the refcount to zero and freeing the event. The next iteration reads
work->next and calls work->func(work) from freed memory, resulting in a
use-after-free and CPU lockup.

[Severity: High]
Is TWA_RESUME the correct mode here, or should it use TWA_SIGNAL?

Prior to this patch, hardware breakpoint handlers directly called
force_sig_fault(), which set TIF_SIGPENDING and properly interrupted
blocking system calls (like select or futex) to deliver the SIGTRAP.

TWA_RESUME only sets TIF_NOTIFY_RESUME and leaves signal_pending() false,
meaning the task could potentially block indefinitely without returning to
user space to process the task work, potentially deadlocking the tracee.

> +		put_event(bp);
> +}
> +#endif

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713144939.FuCj9yvZ@linutronix.de?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work()
  2026-07-13 14:49 ` Sebastian Andrzej Siewior
@ 2026-07-30  1:14   ` Luis Claudio R. Goncalves
  -1 siblings, 0 replies; 5+ messages in thread
From: Luis Claudio R. Goncalves @ 2026-07-30  1:14 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: Waiman Long, Catalin Marinas, Will Deacon, Mark Rutland,
	Clark Williams, Steven Rostedt, linux-arm-kernel, linux-kernel,
	linux-rt-devel, Ada Couprie Diaz, Adrian Hunter,
	Alexander Shishkin, Arnaldo Carvalho de Melo, Huacai Chen,
	Ian Rogers, Ingo Molnar, James Clark, Jiri Olsa, Namhyung Kim,
	Oleg Nesterov, Peter Zijlstra, Russell King, WANG Xuerui,
	linux-perf-users, loongarch

On Mon, Jul 13, 2026 at 04:49:39PM +0200, Sebastian Andrzej Siewior 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_arch_hwbp_notify(), a generic perf callback which delayes the
> actual callback invocation to task_work_add() callback. This callback
> invokes the architecture defines callback arch_hwbp_send_sig().
> This requires struct callback_head and the functions require
> ARCH_NEED_PERF_HW_NOTIF to be defined.
> 
> This was reported against ARM64. ARM and LongARCH follow the same
> pattern are also converted.
> 
> Reported-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reported-by: Waiman Long <longman@redhat.com>
> Closes: https://lore.kernel.org/all/aho0eqjMESuHxECr@redhat.com/
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> ---
> 
> This is approximately the fifth iteration at which point I think I can
> share what I have.
> 
> Could this be tested, please? I tested it on arm64 for arm64 based on
> Luis' test. ARM compiles my HW lacks support for this I think (gdb
> aborts early). I hope the best for LoongARCH.

I tested your patch on baremetal (3 different aarch64 machines) and 3
different Virtual Machines. Apart from the one VM that didn't provide the
HW breakpoints the test used, I was able to reproduce the problem with
v7.2-rc5-rt3 and earlier kernels in all the other setups. Applying your
patch eliminated the backtraces. I ran several batches of 2000 and 10000
executions in a row of the gdb test from the original thread without a
glitch.

Having DEBUG_ATOMIC_SLEEP enabled in the kernel (along with PREEMPT_RT)
was all I needed to reproduce the problem with the unpatched kernel. After
I applied the patch I run tests with and without lockdep. No backtrace,
no complaints.

I started working on trying to reproduce the (possible) problem Sashiko
highlighted, but so far, no luck in reproducing that specific scenario.

Best regards,
Luis


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work()
@ 2026-07-30  1:14   ` Luis Claudio R. Goncalves
  0 siblings, 0 replies; 5+ messages in thread
From: Luis Claudio R. Goncalves @ 2026-07-30  1:14 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: Mark Rutland, Ian Rogers, Alexander Shishkin, Catalin Marinas,
	linux-rt-devel, linux-kernel, WANG Xuerui, Will Deacon,
	Huacai Chen, Russell King, Peter Zijlstra, Ingo Molnar,
	Waiman Long, Clark Williams, James Clark, Steven Rostedt,
	Arnaldo Carvalho de Melo, loongarch, Namhyung Kim, Adrian Hunter,
	linux-arm-kernel, Oleg Nesterov, linux-perf-users, Jiri Olsa

On Mon, Jul 13, 2026 at 04:49:39PM +0200, Sebastian Andrzej Siewior 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_arch_hwbp_notify(), a generic perf callback which delayes the
> actual callback invocation to task_work_add() callback. This callback
> invokes the architecture defines callback arch_hwbp_send_sig().
> This requires struct callback_head and the functions require
> ARCH_NEED_PERF_HW_NOTIF to be defined.
> 
> This was reported against ARM64. ARM and LongARCH follow the same
> pattern are also converted.
> 
> Reported-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reported-by: Waiman Long <longman@redhat.com>
> Closes: https://lore.kernel.org/all/aho0eqjMESuHxECr@redhat.com/
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> ---
> 
> This is approximately the fifth iteration at which point I think I can
> share what I have.
> 
> Could this be tested, please? I tested it on arm64 for arm64 based on
> Luis' test. ARM compiles my HW lacks support for this I think (gdb
> aborts early). I hope the best for LoongARCH.

I tested your patch on baremetal (3 different aarch64 machines) and 3
different Virtual Machines. Apart from the one VM that didn't provide the
HW breakpoints the test used, I was able to reproduce the problem with
v7.2-rc5-rt3 and earlier kernels in all the other setups. Applying your
patch eliminated the backtraces. I ran several batches of 2000 and 10000
executions in a row of the gdb test from the original thread without a
glitch.

Having DEBUG_ATOMIC_SLEEP enabled in the kernel (along with PREEMPT_RT)
was all I needed to reproduce the problem with the unpatched kernel. After
I applied the patch I run tests with and without lockdep. No backtrace,
no complaints.

I started working on trying to reproduce the (possible) problem Sashiko
highlighted, but so far, no luck in reproducing that specific scenario.

Best regards,
Luis



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-30  1:16 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13 14:49 [RFC PATCH] ARM, ARM64, LOONGARCH: Delay HW BP notification to task_work() Sebastian Andrzej Siewior
2026-07-13 14:49 ` Sebastian Andrzej Siewior
2026-07-13 15:05 ` sashiko-bot
2026-07-30  1:14 ` Luis Claudio R. Goncalves
2026-07-30  1:14   ` Luis Claudio R. Goncalves

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.