* [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state
@ 2026-09-11 14:08 Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count Josef Bacik
` (14 more replies)
0 siblings, 15 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
v1: https://lore.kernel.org/all/20260910-b4-rcu-tasks-preempt-qs-v1-0-d4469f4cc101@toxicpanda.com/
v1->v2:
- Only walk the kprobe hash while the optimizer is actually waiting (Sashiko).
- Re-check the kprobe jump window at every QS decision instead of once at
preemption time (AI review).
- Updated Documentation/RCU for the new rule (AI review).
- Added 14/15 and 15/15 to address Paul's comments.
- Added a comment in trace_recursion.h per Steve.
- No change for the arm64 ftrace_static_tramp_end report, the Kconfig
dependency already covers it (Sashiko).
- Re-ran the x86-64 QEMU tests, still 0.2-0.3s and clean.
--- Original email ---
Tasks RCU only treats a voluntary context switch, usermode or idle as a
quiescent state, because a preempted task may be sitting in a trampoline
that is about to be freed. That was a fine trade when PREEMPT_NONE
servers compiled Tasks RCU away and PREEMPT desktops rarely ran
long-lived in-kernel loops. PREEMPT_LAZY changes both halves at once:
Tasks RCU is now real on server configs, and cond_resched() is a no-op,
so a CPU-bound kthread or kworker only ever loses the CPU by being
preempted, which is exactly the event Tasks RCU refuses to count.
The way this showed up for us was a cgroup writeback worker draining a
very large cgwb for around eleven minutes on an arm64 box. Nothing wrong
with that on its own, but a BPF program detach on another CPU went
bpf_trampoline_update() -> ftrace_shutdown() -> synchronize_rcu_tasks()
while holding trampoline_mutex, forty-odd tasks piled up behind the
mutex, and the hung task detector panicked the machine. The kprobe jump
optimizer is worse in principle: it does synchronize_rcu_tasks() under
kprobe_mutex, text_mutex and cpus_read_lock(), so one long-running
kthread can stall static key updates and CPU hotplug for its whole run.
The current answer is to find each such loop and add
cond_resched_tasks_rcu_qs() to it, which is the kind of annotation
PREEMPT_LAZY was supposed to let us stop writing.
This series tries the other direction: have the trampolines say when a
task is inside them, so that a preemption anywhere else can be a
quiescent state.
- task_struct grows an int, rcu_tramp_nesting. Every trampoline whose
lifetime Tasks RCU guards increments it before calling out and
decrements it before returning: ftrace_caller and its dynamic copies,
the BPF trampoline (which drops it again around the call to the
original function, since im->pcref covers that), the x86 optprobe
template, and out-of-line register_ftrace_direct() trampolines. Only
current writes it and nested users are balanced, so it is a plain
non-atomic inc/dec, one load of current plus one RMW per entry/exit.
- The inc/dec are inside the trampoline, so there is a window of a few
instructions on each side where the count is zero but the task is in
(or on its way into) trampoline text. Nothing there can be preempted
synchronously, only from an interrupt, so the irq-exit preemption path
looks at regs->ip and holds the count across preempt_schedule_irq()
when the IP is somewhere the counter cannot cover: outside core and
module text (all the dynamically allocated trampolines and slots), in
the static ftrace stubs or the x86 return thunks that still hold a
direct-call target, in a module that hosts its own direct trampoline,
or inside the bytes after a kprobe that the jump optimizer may be
about to rewrite (the one synchronize_rcu_tasks() user that is not
about trampolines at all).
- With those in place, rcu_tasks_classic_qs() also clears the holdout
flag on a preemption when the count is zero, on architectures that
opt in. x86-64 and arm64 do so here. Everyone else keeps the
voluntary-only rule and is untouched apart from the (unused) field.
A running holdout already gets poked via rcu_request_urgent_qs_task(),
which makes the next tick set NEED_RESCHED, so with this the resulting
preemption retires it and a Tasks RCU grace period is bounded by roughly
a tick plus the longest preempt-off section rather than by the longest
stretch without a voluntary schedule().
Patches 1-12 are scaffolding and change no behaviour on their own; patch
13 flips the rule and selects the option for the two architectures.
Testing so far is QEMU only: x86-64, PREEMPT_LAZY with PREEMPT_RCU=n,
PROVE_RCU and lockdep, with and without PREEMPT_DYNAMIC. A kthread
spinning in-kernel for 30s with the function tracer, an ftrace kprobe,
an optimized kprobe and fentry/fexit programs attached:
synchronize_rcu_tasks() goes from 29.7s to 0.1-0.3s, tearing down a
DYNAMIC ftrace_ops (tracefs instance function -> nop) from 27s to
0.2-0.8s, and the ftrace-direct sample modules load, fire and unload in
about 2.5s each while the spinner runs, with no warnings and the new
return-to-user assertion quiet. arm64 is build-tested only at this
point; real hardware numbers for both are the obvious next step and I
did not want to sit on the idea waiting for them.
Things I would particularly like opinions on:
- Whether hooking rcu_tasks_classic_qs() is the right place, or whether
Paul would rather see this expressed differently inside Tasks RCU.
- return_to_handler and the rethook/kretprobe trampolines are not
instrumented. Their C callees take the ftrace recursion lock before
touching any ops and the trampolines themselves are static text, so I
believe they do not need it, but I would like Steven and Masami to
confirm.
- The register_ftrace_direct() contract change: out-of-line direct
trampolines now have to maintain the count themselves (the samples
are converted). I do not know of out-of-tree users beyond BPF, but
this is the one place an existing user could be silently weakened.
- Whether arm64 folks are comfortable with the ldr/add/str in
ftrace_caller and the BPF trampoline, and with treating all of
ftrace_caller as trampoline text for the IP check.
- If this holds up, cond_resched_tasks_rcu_qs() and
rcu_softirq_qs_periodic() become unnecessary on the opted-in
architectures; I have not touched them here.
Based on v7.3-rc2+ (893e11787f78).
---
Josef Bacik (15):
rcu-tasks: Add per-task trampoline nesting count
entry: Pass pt_regs to irqentry_exit_cond_resched()
rcu-tasks: Hold trampoline nesting across irq-exit preemption in trampoline text
kprobes: Let Tasks RCU recognise tasks preempted in an optprobe jump window
ftrace: Mark modules hosting direct-call trampolines for Tasks RCU
x86/ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller
x86/kprobes: Maintain Tasks RCU trampoline nesting in the optprobe template
bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
arm64: ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller
bpf, arm64: Maintain Tasks RCU trampoline nesting in the BPF trampoline
samples: ftrace: Maintain Tasks RCU trampoline nesting in direct-call trampolines
rcutorture: Bracket Tasks RCU readers with trampoline nesting
rcu-tasks: Treat preemption outside trampolines as a quiescent state
rcu-tasks: Retire switched-out tasks with no trampoline nesting at scan time
rcu-tasks: Kick running holdouts through the scheduler
.../RCU/Design/Requirements/Requirements.rst | 28 +++-
Documentation/RCU/checklist.rst | 8 +-
arch/arm64/Kconfig | 1 +
arch/arm64/kernel/asm-offsets.c | 3 +
arch/arm64/kernel/entry-ftrace.S | 35 +++++
arch/arm64/kernel/ftrace.c | 16 +++
arch/arm64/net/bpf_jit_comp.c | 46 +++++++
arch/x86/Kconfig | 1 +
arch/x86/kernel/asm-offsets.c | 3 +
arch/x86/kernel/ftrace.c | 37 ++++++
arch/x86/kernel/ftrace_64.S | 43 +++++++
arch/x86/kernel/kprobes/opt.c | 20 +++
arch/x86/kernel/vmlinux.lds.S | 4 +
arch/x86/net/bpf_jit_comp.c | 43 +++++++
arch/x86/xen/enlighten_pv.c | 2 +-
include/linux/irq-entry-common.h | 14 +-
include/linux/kprobes.h | 8 +-
include/linux/module.h | 7 +
include/linux/rcupdate.h | 86 ++++++++++++-
include/linux/sched.h | 2 +
include/linux/trace_recursion.h | 11 ++
kernel/entry/common.c | 38 +++++-
kernel/fork.c | 2 +
kernel/kprobes.c | 46 +++++++
kernel/rcu/Kconfig | 17 ++-
kernel/rcu/rcutorture.c | 6 +
kernel/rcu/tasks.h | 141 ++++++++++++++++++++-
kernel/rcu/update.c | 2 +
kernel/trace/ftrace.c | 39 ++++++
samples/ftrace/ftrace-direct-modify.c | 9 ++
samples/ftrace/ftrace-direct-multi-modify.c | 9 ++
samples/ftrace/ftrace-direct-multi.c | 5 +
samples/ftrace/ftrace-direct-too.c | 5 +
samples/ftrace/ftrace-direct.c | 5 +
samples/ftrace/ftrace-direct.h | 64 ++++++++++
35 files changed, 776 insertions(+), 30 deletions(-)
---
base-commit: 893e11787f78e43b534e252249ac3fff4d1333f8
change-id: 20260910-b4-rcu-tasks-preempt-qs-401ff45465c7
Best regards,
--
Josef Bacik <josef@toxicpanda.com>
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 17:23 ` Paul E. McKenney
2026-09-11 14:08 ` [PATCH RFC v2 02/15] entry: Pass pt_regs to irqentry_exit_cond_resched() Josef Bacik
` (13 subsequent siblings)
14 siblings, 1 reply; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Tasks RCU exists so that ftrace, BPF and kprobes can free trampoline
text once no task can still be executing in it. Today the only way a
task tells Tasks RCU "I am not in a trampoline" is a voluntary context
switch, so a preempted task is always assumed to be inside one.
Add task_struct::rcu_tramp_nesting so that trampolines can say so
directly: a trampoline increments it before calling out and decrements
it before returning, and while it is non-zero the task must not be
treated as Tasks-RCU quiescent. Provide rcu_tasks_trampoline_enter()
and rcu_tasks_trampoline_exit() for C users, report the count in the
Tasks RCU stall output, and, under CONFIG_PROVE_RCU, assert that it is
zero on every return to userspace since no task can legitimately reach
userspace with a trampoline on its stack.
Only current ever writes the count and every nested user (interrupts
running their own trampolines) is balanced, so plain accesses suffice.
The callbacks reached from static trampolines (return_to_handler, the
rethook and kretprobe trampolines) are covered by the preempt_disable()
in the ftrace recursion protection rather than by the count; note that
dependency in trace_recursion.h so it is not lost if the
preempt_disable() is ever removed from there.
Nothing increments the count and nothing consults it for quiescent-state
decisions yet; both come in later patches.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/irq-entry-common.h | 2 ++
include/linux/rcupdate.h | 37 +++++++++++++++++++++++++++++++++++++
include/linux/sched.h | 1 +
include/linux/trace_recursion.h | 11 +++++++++++
kernel/fork.c | 1 +
kernel/rcu/tasks.h | 3 ++-
6 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index 0bb6c03481fa..8da571622000 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -5,6 +5,7 @@
#include <linux/context_tracking.h>
#include <linux/hrtimer_rearm.h>
#include <linux/kmsan.h>
+#include <linux/rcupdate.h>
#include <linux/rseq_entry.h>
#include <linux/static_call_types.h>
#include <linux/syscalls.h>
@@ -214,6 +215,7 @@ static __always_inline void __exit_to_user_mode_validate(void)
{
/* Ensure that kernel state is sane for a return to userspace */
kmap_assert_nomap();
+ rcu_tasks_trampoline_assert_none();
lockdep_assert_irqs_disabled();
lockdep_sys_exit();
}
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 44c07a66edff..b5c666c82479 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -180,6 +180,37 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
#ifdef CONFIG_TASKS_RCU_GENERIC
# ifdef CONFIG_TASKS_RCU
+
+/*
+ * Trampoline nesting: dynamically allocated text (ftrace trampolines, BPF
+ * trampoline images, kprobe optinsn slots) that relies on Tasks RCU for its
+ * lifetime brackets itself with an increment/decrement of
+ * current->rcu_tramp_nesting. While the count is non-zero the task is inside,
+ * or was called from, such text and an involuntary context switch must not be
+ * treated as a Tasks RCU quiescent state.
+ *
+ * Only current writes the count and only current (or an interrupt on the same
+ * CPU) reads it, so plain accesses suffice.
+ */
+static __always_inline void rcu_tasks_trampoline_enter(void)
+{
+ current->rcu_tramp_nesting++;
+ barrier();
+}
+
+static __always_inline void rcu_tasks_trampoline_exit(void)
+{
+ barrier();
+ current->rcu_tramp_nesting--;
+}
+
+/* A task must never reach userspace with a trampoline on its stack. */
+static __always_inline void rcu_tasks_trampoline_assert_none(void)
+{
+ if (IS_ENABLED(CONFIG_PROVE_RCU))
+ WARN_ON_ONCE(current->rcu_tramp_nesting);
+}
+
# define rcu_tasks_classic_qs(t, preempt) \
do { \
if (!(preempt) && READ_ONCE((t)->rcu_tasks_holdout)) \
@@ -192,6 +223,9 @@ void rcu_tasks_torture_stats_print(char *tt, char *tf);
# define rcu_tasks_classic_qs(t, preempt) do { } while (0)
# define call_rcu_tasks call_rcu
# define synchronize_rcu_tasks synchronize_rcu
+static inline void rcu_tasks_trampoline_enter(void) { }
+static inline void rcu_tasks_trampoline_exit(void) { }
+static inline void rcu_tasks_trampoline_assert_none(void) { }
# endif
#define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))
@@ -208,6 +242,9 @@ void exit_tasks_rcu_finish(void);
#define rcu_tasks_classic_qs(t, preempt) do { } while (0)
#define rcu_tasks_qs(t, preempt) do { } while (0)
#define rcu_note_voluntary_context_switch(t) do { } while (0)
+static inline void rcu_tasks_trampoline_enter(void) { }
+static inline void rcu_tasks_trampoline_exit(void) { }
+static inline void rcu_tasks_trampoline_assert_none(void) { }
#define call_rcu_tasks call_rcu
#define synchronize_rcu_tasks synchronize_rcu
static inline void exit_tasks_rcu_start(void) { }
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..d2e7b1b3c9d2 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -956,6 +956,7 @@ struct task_struct {
unsigned long rcu_tasks_nvcsw;
u8 rcu_tasks_holdout;
u8 rcu_tasks_idx;
+ int rcu_tramp_nesting;
int rcu_tasks_idle_cpu;
struct list_head rcu_tasks_holdout_list;
int rcu_tasks_exit_cpu;
diff --git a/include/linux/trace_recursion.h b/include/linux/trace_recursion.h
index e6ca052b2a85..2da23a52ca4a 100644
--- a/include/linux/trace_recursion.h
+++ b/include/linux/trace_recursion.h
@@ -153,6 +153,17 @@ static __always_inline int trace_test_and_set_recursion(unsigned long ip, unsign
current->trace_recursion = val;
barrier();
+ /*
+ * Callbacks reached from static trampoline text (return_to_handler,
+ * the rethook and kretprobe trampolines) do not maintain
+ * current->rcu_tramp_nesting themselves; they rely on this
+ * preempt_disable() to keep the task from being preempted, and thus
+ * from reporting a Tasks RCU quiescent state, while an ftrace_ops or
+ * its data is in use. If the preempt_disable() is ever removed from
+ * the recursion protection, this must rcu_tasks_trampoline_enter()
+ * here and rcu_tasks_trampoline_exit() in trace_clear_recursion()
+ * instead. See CONFIG_RCU_TASKS_PREEMPT_QS.
+ */
preempt_disable_notrace();
return bit;
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d4..cfe3a8e53fbd 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1869,6 +1869,7 @@ static inline void rcu_copy_process(struct task_struct *p)
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TASKS_RCU
p->rcu_tasks_holdout = false;
+ p->rcu_tramp_nesting = 0;
INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
p->rcu_tasks_idle_cpu = -1;
INIT_LIST_HEAD(&p->rcu_tasks_exit_list);
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 627295396cd9..1662ba18bf34 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -1113,10 +1113,11 @@ static void check_holdout_task(struct task_struct *t,
*firstreport = false;
}
cpu = task_cpu(t);
- pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d idle_cpu: %d/%d\n",
+ pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d tramp_nesting: %d idle_cpu: %d/%d\n",
t, ".I"[is_idle_task(t)],
"N."[cpu < 0 || !tick_nohz_full_cpu(cpu)],
t->rcu_tasks_nvcsw, t->nvcsw, t->rcu_tasks_holdout,
+ data_race(t->rcu_tramp_nesting),
data_race(t->rcu_tasks_idle_cpu), cpu);
sched_show_task(t);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 02/15] entry: Pass pt_regs to irqentry_exit_cond_resched()
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 03/15] rcu-tasks: Hold trampoline nesting across irq-exit preemption in trampoline text Josef Bacik
` (12 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
The irq-exit preemption path is about to need the interrupted context's
registers to decide whether the preemption may be reported to Tasks RCU
as a quiescent state. irqentry_exit_to_kernel_mode_preempt() already
has them; hand them down through irqentry_exit_cond_resched(), its
PREEMPT_DYNAMIC static-call and static-key variants, and
raw_irqentry_exit_cond_resched(). The only caller outside the generic
entry code is Xen PV's upcall handler, which has regs as well.
No functional change.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
arch/x86/xen/enlighten_pv.c | 2 +-
include/linux/irq-entry-common.h | 12 ++++++------
kernel/entry/common.c | 6 +++---
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c
index 2c64b388f616..3d85035f5624 100644
--- a/arch/x86/xen/enlighten_pv.c
+++ b/arch/x86/xen/enlighten_pv.c
@@ -739,7 +739,7 @@ __visible noinstr void xen_pv_evtchn_do_upcall(struct pt_regs *regs)
inhcall = get_and_clear_inhcall();
if (inhcall && !WARN_ON_ONCE(state.exit_rcu)) {
- irqentry_exit_cond_resched();
+ irqentry_exit_cond_resched(regs);
instrumentation_end();
restore_inhcall(inhcall);
} else {
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index 8da571622000..fc04725ae46b 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -348,21 +348,21 @@ typedef struct irqentry_state {
*
* Conditional reschedule with additional sanity checks.
*/
-void raw_irqentry_exit_cond_resched(void);
+void raw_irqentry_exit_cond_resched(struct pt_regs *regs);
#ifdef CONFIG_PREEMPT_DYNAMIC
#if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
#define irqentry_exit_cond_resched_dynamic_enabled raw_irqentry_exit_cond_resched
#define irqentry_exit_cond_resched_dynamic_disabled NULL
DECLARE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
-#define irqentry_exit_cond_resched() static_call(irqentry_exit_cond_resched)()
+#define irqentry_exit_cond_resched(regs) static_call(irqentry_exit_cond_resched)(regs)
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
-void dynamic_irqentry_exit_cond_resched(void);
-#define irqentry_exit_cond_resched() dynamic_irqentry_exit_cond_resched()
+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs);
+#define irqentry_exit_cond_resched(regs) dynamic_irqentry_exit_cond_resched(regs)
#endif
#else /* CONFIG_PREEMPT_DYNAMIC */
-#define irqentry_exit_cond_resched() raw_irqentry_exit_cond_resched()
+#define irqentry_exit_cond_resched(regs) raw_irqentry_exit_cond_resched(regs)
#endif /* CONFIG_PREEMPT_DYNAMIC */
/**
@@ -467,7 +467,7 @@ static inline void irqentry_exit_to_kernel_mode_preempt(struct pt_regs *regs,
return;
if (IS_ENABLED(CONFIG_PREEMPTION))
- irqentry_exit_cond_resched();
+ irqentry_exit_cond_resched(regs);
}
/**
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e3d381fd3d25..e4acd50bd81a 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -134,7 +134,7 @@ static inline bool arch_irqentry_exit_need_resched(void);
static inline bool arch_irqentry_exit_need_resched(void) { return true; }
#endif
-void raw_irqentry_exit_cond_resched(void)
+void raw_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!preempt_count()) {
/* Sanity check RCU and thread stack */
@@ -150,11 +150,11 @@ void raw_irqentry_exit_cond_resched(void)
DEFINE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DEFINE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
-void dynamic_irqentry_exit_cond_resched(void)
+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched))
return;
- raw_irqentry_exit_cond_resched();
+ raw_irqentry_exit_cond_resched(regs);
}
#endif
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 03/15] rcu-tasks: Hold trampoline nesting across irq-exit preemption in trampoline text
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 02/15] entry: Pass pt_regs to irqentry_exit_cond_resched() Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 04/15] kprobes: Let Tasks RCU recognise tasks preempted in an optprobe jump window Josef Bacik
` (11 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
A trampoline's own rcu_tramp_nesting increment and decrement live inside
the trampoline, so there is a window of a few instructions on entry and
exit where the count is zero while the CPU is executing trampoline text
(or text on the way into one, such as a static ftrace stub holding a
direct-call target). In that window the task has not called out, so it
can only be preempted from an interrupt, and the interrupted instruction
pointer identifies where it is.
Add rcu_tasks_ip_in_trampoline(), which treats any IP outside core
kernel and module text as potentially Tasks-RCU-protected (ftrace
trampolines, BPF images and programs, kprobe slots are all dynamically
allocated text; is_ftrace_trampoline() and friends are deliberately not
used because text being torn down may already be unregistered from them
while a task still stands on it), plus a __weak
arch_rcu_tasks_ip_in_trampoline() for core text an architecture needs
to flag. On irq-exit preemption, if the IP matches, hold the count
elevated across preempt_schedule_irq().
Introduce ARCH_HAS_RCU_TASKS_PREEMPT_QS / RCU_TASKS_PREEMPT_QS to gate
this; no architecture selects it yet, so the check compiles away and
there is no functional change.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/rcupdate.h | 17 +++++++++++++++++
kernel/entry/common.c | 23 ++++++++++++++++++++++-
kernel/rcu/Kconfig | 10 ++++++++++
kernel/rcu/tasks.h | 38 ++++++++++++++++++++++++++++++++++++++
kernel/rcu/update.c | 2 ++
5 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index b5c666c82479..0a408e36ea15 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -173,6 +173,9 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
#endif /* #else #ifdef CONFIG_RCU_NOCB_CPU */
+/* Arch hook for rcu_tasks_ip_in_trampoline(); see kernel/rcu/tasks.h. */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip);
+
/*
* Note a quasi-voluntary context switch for RCU-tasks's benefit.
* This is a macro rather than an inline function to avoid #include hell.
@@ -189,6 +192,16 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
* or was called from, such text and an involuntary context switch must not be
* treated as a Tasks RCU quiescent state.
*
+ * The increment and decrement themselves live inside the trampoline, so there
+ * is a window of a few instructions at entry (before the increment) and exit
+ * (after the decrement) where the count is zero but the CPU is executing
+ * trampoline text, or text on the way into one (a static ftrace stub or a
+ * return thunk holding the trampoline's address). In that window the task
+ * cannot be preempted synchronously, only from an interrupt, so the irq-exit
+ * preemption path covers it by checking regs->ip with
+ * rcu_tasks_ip_in_trampoline() and holding the count elevated across
+ * preempt_schedule_irq() when it matches.
+ *
* Only current writes the count and only current (or an interrupt on the same
* CPU) reads it, so plain accesses suffice.
*/
@@ -211,6 +224,8 @@ static __always_inline void rcu_tasks_trampoline_assert_none(void)
WARN_ON_ONCE(current->rcu_tramp_nesting);
}
+bool rcu_tasks_ip_in_trampoline(unsigned long ip);
+
# define rcu_tasks_classic_qs(t, preempt) \
do { \
if (!(preempt) && READ_ONCE((t)->rcu_tasks_holdout)) \
@@ -226,6 +241,7 @@ void rcu_tasks_torture_stats_print(char *tt, char *tf);
static inline void rcu_tasks_trampoline_enter(void) { }
static inline void rcu_tasks_trampoline_exit(void) { }
static inline void rcu_tasks_trampoline_assert_none(void) { }
+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
# endif
#define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))
@@ -245,6 +261,7 @@ void exit_tasks_rcu_finish(void);
static inline void rcu_tasks_trampoline_enter(void) { }
static inline void rcu_tasks_trampoline_exit(void) { }
static inline void rcu_tasks_trampoline_assert_none(void) { }
+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
#define call_rcu_tasks call_rcu
#define synchronize_rcu_tasks synchronize_rcu
static inline void exit_tasks_rcu_start(void) { }
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e4acd50bd81a..cd3feaca6420 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -134,6 +134,27 @@ static inline bool arch_irqentry_exit_need_resched(void);
static inline bool arch_irqentry_exit_need_resched(void) { return true; }
#endif
+/*
+ * Preempt the interrupted kernel context. If the interrupt landed in text
+ * that may be a Tasks-RCU-protected trampoline (see
+ * rcu_tasks_trampoline_enter()), hold current->rcu_tramp_nesting elevated
+ * across the context switch so that it is not mistaken for a Tasks RCU
+ * quiescent state. This closes the few-instruction windows at trampoline
+ * entry/exit where the trampoline's own increment has not yet run or its
+ * decrement already has.
+ */
+static void irqentry_preempt(struct pt_regs *regs)
+{
+ bool in_tramp = IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS) &&
+ rcu_tasks_ip_in_trampoline(instruction_pointer(regs));
+
+ if (in_tramp)
+ rcu_tasks_trampoline_enter();
+ preempt_schedule_irq();
+ if (in_tramp)
+ rcu_tasks_trampoline_exit();
+}
+
void raw_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!preempt_count()) {
@@ -142,7 +163,7 @@ void raw_irqentry_exit_cond_resched(struct pt_regs *regs)
if (IS_ENABLED(CONFIG_DEBUG_ENTRY))
WARN_ON_ONCE(!on_thread_stack());
if (need_resched() && arch_irqentry_exit_need_resched())
- preempt_schedule_irq();
+ irqentry_preempt(regs);
}
}
#ifdef CONFIG_PREEMPT_DYNAMIC
diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig
index 332df7a7a634..999f8228a13d 100644
--- a/kernel/rcu/Kconfig
+++ b/kernel/rcu/Kconfig
@@ -107,6 +107,16 @@ config TASKS_RCU
default NEED_TASKS_RCU && PREEMPTION
select IRQ_WORK
+# Selected by architectures whose ftrace, BPF and kprobe trampolines maintain
+# current->rcu_tramp_nesting and which use the generic irqentry code, so that
+# a preemption outside any trampoline can be treated as a Tasks RCU
+# quiescent state. See rcu_tasks_trampoline_enter().
+config ARCH_HAS_RCU_TASKS_PREEMPT_QS
+ bool
+
+config RCU_TASKS_PREEMPT_QS
+ def_bool TASKS_RCU && ARCH_HAS_RCU_TASKS_PREEMPT_QS && GENERIC_IRQ_ENTRY
+
config FORCE_TASKS_RUDE_RCU
bool "Force selection of Tasks Rude RCU"
depends on RCU_EXPERT
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 1662ba18bf34..a801ec4a951b 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -1089,6 +1089,44 @@ static void rcu_tasks_postscan(struct list_head *hop)
timer_delete_sync(&tasks_rcu_exit_stall_timer);
}
+/*
+ * Architectures selecting ARCH_HAS_RCU_TASKS_PREEMPT_QS override this to flag
+ * core kernel text that must be treated like a trampoline, e.g. static ftrace
+ * entry stubs and return thunks that run with a trampoline address in hand.
+ */
+bool __weak arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ return false;
+}
+
+/**
+ * rcu_tasks_ip_in_trampoline - Could a task interrupted at @ip be a Tasks RCU reader?
+ * @ip: interrupted instruction pointer
+ *
+ * Called from the irq-exit preemption path with interrupts disabled, to decide
+ * whether the imminent preemption may be reported as a Tasks RCU quiescent
+ * state when current->rcu_tramp_nesting is zero. Returns true, meaning "do
+ * not report", when @ip is:
+ *
+ * - outside static kernel and module text, i.e. possibly in an ftrace
+ * trampoline, BPF trampoline image or program, kprobe insn/optinsn slot or
+ * other dynamically allocated text whose lifetime Tasks RCU guards. This
+ * deliberately does not consult is_ftrace_trampoline() and friends: text
+ * being torn down may already be unregistered there while a task still
+ * stands on it;
+ * - in core text the architecture flags via arch_rcu_tasks_ip_in_trampoline().
+ *
+ * A false positive only defers the quiescent state to the task's next
+ * context switch.
+ */
+bool rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ if (core_kernel_text(ip))
+ return arch_rcu_tasks_ip_in_trampoline(ip);
+ return !is_module_text_address(ip);
+}
+NOKPROBE_SYMBOL(rcu_tasks_ip_in_trampoline);
+
/* See if tasks are still holding out, complain if so. */
static void check_holdout_task(struct task_struct *t,
bool needreport, bool *firstreport)
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
index b62735a67884..23be7e97c3b5 100644
--- a/kernel/rcu/update.c
+++ b/kernel/rcu/update.c
@@ -41,6 +41,8 @@
#include <linux/rcupdate_wait.h>
#include <linux/sched/isolation.h>
#include <linux/kprobes.h>
+#include <linux/kallsyms.h>
+#include <linux/module.h>
#include <linux/slab.h>
#include <linux/irq_work.h>
#include <linux/rcupdate_trace.h>
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 04/15] kprobes: Let Tasks RCU recognise tasks preempted in an optprobe jump window
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (2 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 03/15] rcu-tasks: Hold trampoline nesting across irq-exit preemption in trampoline text Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 05/15] ftrace: Mark modules hosting direct-call trampolines for Tasks RCU Josef Bacik
` (10 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
kprobe_optimizer() is the one synchronize_rcu_tasks() user that is not
about trampoline text: it waits for tasks that were preempted on an
instruction boundary inside the bytes it is about to overwrite with the
optimized jump, so that none of them resumes into the middle of the new
instruction. Such a task sits in ordinary kernel or module text with
rcu_tramp_nesting == 0, and can only have got there via an irq-exit
preemption.
Add kprobe_in_optimized_region(), a lockless and conservative form of
get_optimized_kprobe() that reports whether any registered kprobe lies
within MAX_OPTIMIZED_LENGTH before the given address regardless of its
optimization state. The hash walk is only done while kprobe_optimizer()
is actually inside its synchronize_rcu_tasks(), tracked by a flag it sets
around the call; otherwise the check is a single load. The kprobe hash
is RCU-protected and every free path waits for a grace period after
unhashing, so the lockless walk is safe from any context with preemption
disabled.
Unlike trampoline text, which a task can only be interrupted in while
the trampoline exists, these bytes are ordinary text a task may have
been parked in since before the kprobe was registered, and the optimizer
may start waiting while that task is already switched out. So the check
cannot be made once at preemption time the way the trampoline cases are:
have irqentry_preempt() record the interrupted IP in
current->rcu_tasks_irq_ip for the duration of the preemption, and add
rcu_tasks_irq_ip_holds() to test it, to be evaluated at every
quiescent-state decision once preemption becomes a quiescent state --
each pass through __schedule() in preempt_schedule_irq()'s loop as well
as any remote check. A task switched out synchronously cannot have a
resume point inside such a window (a call there returns beyond it), so
only the irq-exit IP needs checking, and preempt_schedule_irq() cannot
nest, so one slot per task suffices.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/kprobes.h | 8 +++++++-
include/linux/rcupdate.h | 17 +++++++++++++++++
include/linux/sched.h | 1 +
kernel/entry/common.c | 13 +++++++++++--
kernel/fork.c | 1 +
kernel/kprobes.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
kernel/rcu/tasks.h | 23 +++++++++++++++++++++++
7 files changed, 106 insertions(+), 3 deletions(-)
diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index e6de7ae55bda..74cc48c04417 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -530,11 +530,17 @@ static inline bool is_kprobe_insn_slot(unsigned long addr)
}
#endif /* !CONFIG_KPROBES */
-#ifndef CONFIG_OPTPROBES
+#ifdef CONFIG_OPTPROBES
+bool kprobe_in_optimized_region(unsigned long addr);
+#else /* !CONFIG_OPTPROBES */
static inline bool is_kprobe_optinsn_slot(unsigned long addr)
{
return false;
}
+static inline bool kprobe_in_optimized_region(unsigned long addr)
+{
+ return false;
+}
#endif /* !CONFIG_OPTPROBES */
#ifdef CONFIG_KRETPROBES
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 0a408e36ea15..4cfe096d624f 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -202,6 +202,14 @@ bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip);
* rcu_tasks_ip_in_trampoline() and holding the count elevated across
* preempt_schedule_irq() when it matches.
*
+ * The one non-trampoline user, kprobe jump optimization, waits for tasks
+ * preempted inside ordinary instruction bytes it is about to overwrite. A
+ * task can be parked there from before the kprobe even existed, so that
+ * cannot be decided once at preemption time: irqentry_preempt() records the
+ * interrupted IP in current->rcu_tasks_irq_ip for the duration of the
+ * preemption and rcu_tasks_irq_ip_holds() checks it at every quiescent-state
+ * decision, locally and from the grace-period kthread.
+ *
* Only current writes the count and only current (or an interrupt on the same
* CPU) reads it, so plain accesses suffice.
*/
@@ -225,6 +233,13 @@ static __always_inline void rcu_tasks_trampoline_assert_none(void)
}
bool rcu_tasks_ip_in_trampoline(unsigned long ip);
+bool rcu_tasks_irq_ip_holds(struct task_struct *t);
+
+/* Record where current is being irq-preempted; 0 once it has resumed. */
+static __always_inline void rcu_tasks_note_irq_ip(unsigned long ip)
+{
+ WRITE_ONCE(current->rcu_tasks_irq_ip, ip);
+}
# define rcu_tasks_classic_qs(t, preempt) \
do { \
@@ -242,6 +257,7 @@ static inline void rcu_tasks_trampoline_enter(void) { }
static inline void rcu_tasks_trampoline_exit(void) { }
static inline void rcu_tasks_trampoline_assert_none(void) { }
static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }
# endif
#define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))
@@ -262,6 +278,7 @@ static inline void rcu_tasks_trampoline_enter(void) { }
static inline void rcu_tasks_trampoline_exit(void) { }
static inline void rcu_tasks_trampoline_assert_none(void) { }
static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }
#define call_rcu_tasks call_rcu
#define synchronize_rcu_tasks synchronize_rcu
static inline void exit_tasks_rcu_start(void) { }
diff --git a/include/linux/sched.h b/include/linux/sched.h
index d2e7b1b3c9d2..7f0bdc81fba3 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -957,6 +957,7 @@ struct task_struct {
u8 rcu_tasks_holdout;
u8 rcu_tasks_idx;
int rcu_tramp_nesting;
+ unsigned long rcu_tasks_irq_ip;
int rcu_tasks_idle_cpu;
struct list_head rcu_tasks_holdout_list;
int rcu_tasks_exit_cpu;
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index cd3feaca6420..b372f2670d4f 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -141,16 +141,25 @@ static inline bool arch_irqentry_exit_need_resched(void) { return true; }
* across the context switch so that it is not mistaken for a Tasks RCU
* quiescent state. This closes the few-instruction windows at trampoline
* entry/exit where the trampoline's own increment has not yet run or its
- * decrement already has.
+ * decrement already has. The interrupted IP is also recorded for the
+ * duration, for conditions that must be re-evaluated at each quiescent-state
+ * decision rather than once here (see rcu_tasks_irq_ip_holds()); nested
+ * irq-exit preemption cannot happen inside preempt_schedule_irq(), so one
+ * slot per task is enough.
*/
static void irqentry_preempt(struct pt_regs *regs)
{
+ unsigned long ip = instruction_pointer(regs);
bool in_tramp = IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS) &&
- rcu_tasks_ip_in_trampoline(instruction_pointer(regs));
+ rcu_tasks_ip_in_trampoline(ip);
if (in_tramp)
rcu_tasks_trampoline_enter();
+ if (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))
+ rcu_tasks_note_irq_ip(ip);
preempt_schedule_irq();
+ if (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))
+ rcu_tasks_note_irq_ip(0);
if (in_tramp)
rcu_tasks_trampoline_exit();
}
diff --git a/kernel/fork.c b/kernel/fork.c
index cfe3a8e53fbd..1277603bc472 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1870,6 +1870,7 @@ static inline void rcu_copy_process(struct task_struct *p)
#ifdef CONFIG_TASKS_RCU
p->rcu_tasks_holdout = false;
p->rcu_tramp_nesting = 0;
+ p->rcu_tasks_irq_ip = 0;
INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
p->rcu_tasks_idle_cpu = -1;
INIT_LIST_HEAD(&p->rcu_tasks_exit_list);
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 6337da5cab9e..cf2ea278fdf5 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -511,6 +511,48 @@ static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)
return NULL;
}
+/*
+ * True while kprobe_optimizer() is waiting for its Tasks RCU grace period.
+ * Only in that window can a preemption inside an optprobe's jump region
+ * matter to it, so kprobe_in_optimized_region() does no work otherwise.
+ */
+static bool kprobe_optimizer_waiting;
+
+/**
+ * kprobe_in_optimized_region - Could @addr be inside bytes a jump-optimized
+ * kprobe replaces?
+ * @addr: kernel text address, typically an interrupted instruction pointer
+ *
+ * kprobe_optimizer() relies on synchronize_rcu_tasks() to wait for tasks that
+ * were preempted on an instruction boundary inside the region about to be
+ * overwritten by the optimized jump; such a task must not report a Tasks RCU
+ * quiescent state when it is preempted (see rcu_tasks_ip_in_trampoline()).
+ * This is the lockless, conservative form of get_optimized_kprobe(): it does
+ * not care whether the kprobe found is, or ever will be, optimized. May be
+ * called from any context with preemption disabled; the kprobe hash is
+ * RCU-protected and every free path waits for a grace period after unhashing.
+ *
+ * The hash walk only runs while the optimizer is actually waiting. A
+ * preemption that does not observe kprobe_optimizer_waiting predates the
+ * grace period (its leading synchronize_rcu() publishes the store to every
+ * interrupts-disabled reader before any task is sampled as a holdout); such a
+ * task is then an ordinary preempted holdout, and the jump is not written
+ * until it has run again and left the region.
+ */
+bool kprobe_in_optimized_region(unsigned long addr)
+{
+ int i;
+
+ if (!READ_ONCE(kprobe_optimizer_waiting))
+ return false;
+
+ for (i = 1; i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++)
+ if (get_kprobe((kprobe_opcode_t *)addr - i))
+ return true;
+ return false;
+}
+NOKPROBE_SYMBOL(kprobe_in_optimized_region);
+
/* Optimization staging list, protected by 'kprobe_mutex' */
static LIST_HEAD(optimizing_list);
static LIST_HEAD(unoptimizing_list);
@@ -644,8 +686,12 @@ static void kprobe_optimizer(void)
* to 2nd-Nth byte of jump instruction. This wait is for avoiding it.
* Note that on non-preemptive kernel, this is transparently converted
* to synchronoze_sched() to wait for all interrupts to have completed.
+ * kprobe_optimizer_waiting lets Tasks RCU recognise tasks preempted
+ * in such a region while we wait, see kprobe_in_optimized_region().
*/
+ WRITE_ONCE(kprobe_optimizer_waiting, true);
synchronize_rcu_tasks();
+ WRITE_ONCE(kprobe_optimizer_waiting, false);
/* Step 3: Optimize kprobes after quiesence period */
do_optimize_kprobes();
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index a801ec4a951b..0e46d8fe4d8e 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -1127,6 +1127,29 @@ bool rcu_tasks_ip_in_trampoline(unsigned long ip)
}
NOKPROBE_SYMBOL(rcu_tasks_ip_in_trampoline);
+/**
+ * rcu_tasks_irq_ip_holds - Is @t irq-preempted somewhere that must hold off Tasks RCU?
+ * @t: a task inside preempt_schedule_irq() (t->rcu_tasks_irq_ip != 0), or not
+ *
+ * Unlike trampoline text, which a task can only be interrupted in while the
+ * trampoline exists, the bytes kprobe_optimizer() is about to overwrite with a
+ * jump are ordinary text a task may have been parked in since before the
+ * kprobe was registered, and the optimizer may start waiting while the task is
+ * already switched out. So this is evaluated against the IP recorded by
+ * irqentry_preempt() at every quiescent-state decision -- each pass through
+ * __schedule() in preempt_schedule_irq()'s loop, and the grace-period
+ * kthread's scans -- rather than once at preemption time. A task switched out
+ * synchronously cannot have a resume point inside such a window (a call there
+ * returns beyond it), so only the irq-exit IP needs checking.
+ */
+bool rcu_tasks_irq_ip_holds(struct task_struct *t)
+{
+ unsigned long ip = READ_ONCE(t->rcu_tasks_irq_ip);
+
+ return ip && kprobe_in_optimized_region(ip);
+}
+NOKPROBE_SYMBOL(rcu_tasks_irq_ip_holds);
+
/* See if tasks are still holding out, complain if so. */
static void check_holdout_task(struct task_struct *t,
bool needreport, bool *firstreport)
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 05/15] ftrace: Mark modules hosting direct-call trampolines for Tasks RCU
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (3 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 04/15] kprobes: Let Tasks RCU recognise tasks preempted in an optprobe jump window Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 06/15] x86/ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller Josef Bacik
` (9 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
An out-of-line direct trampoline registered with register_ftrace_direct()
is kept alive only by Tasks RCU while a task executes it or is preempted
in something it called; ftrace_shutdown()'s synchronize_rcu_tasks() is
what stops rmmod freeing it under such a task. Once preemption becomes a
Tasks RCU quiescent state, such a trampoline must hold
current->rcu_tramp_nesting across its call-out like the ftrace and BPF
trampolines do, so document that in register_ftrace_direct().
That still leaves the few instructions before the increment and after
the decrement. For BPF images those are in dynamically allocated text
that rcu_tasks_ip_in_trampoline() already treats as protected, but the
in-tree samples (and any similar user) place their trampolines in module
.text. Add a sticky module::ftrace_direct_tramp flag, set by every
register/modify path when the direct address is module text, and have
rcu_tasks_ip_in_trampoline() treat a task interrupted anywhere in such a
module as a potential reader. Other modules' text is unaffected.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/module.h | 7 +++++++
kernel/rcu/tasks.h | 23 +++++++++++++++++++++--
kernel/trace/ftrace.c | 39 +++++++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+), 2 deletions(-)
diff --git a/include/linux/module.h b/include/linux/module.h
index 96cc98568eea..ea4727f53fab 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -521,6 +521,13 @@ struct module {
unsigned int num_ftrace_callsites;
unsigned long *ftrace_callsites;
#endif
+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ /*
+ * An ftrace direct-call trampoline lives in this module's text; see
+ * rcu_tasks_ip_in_trampoline(). Sticky once set.
+ */
+ bool ftrace_direct_tramp;
+#endif
#ifdef CONFIG_KPROBES
void *kprobes_text_start;
unsigned int kprobes_text_size;
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 0e46d8fe4d8e..1b9fe1bfa591 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -1114,16 +1114,35 @@ bool __weak arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
* deliberately does not consult is_ftrace_trampoline() and friends: text
* being torn down may already be unregistered there while a task still
* stands on it;
- * - in core text the architecture flags via arch_rcu_tasks_ip_in_trampoline().
+ * - in core text the architecture flags via arch_rcu_tasks_ip_in_trampoline();
+ * - in the text of a module that hosts an ftrace direct-call trampoline,
+ * which covers the instructions before that trampoline's increment and
+ * after its decrement (see ftrace_direct_mark_module()).
*
* A false positive only defers the quiescent state to the task's next
* context switch.
*/
bool rcu_tasks_ip_in_trampoline(unsigned long ip)
{
+ bool ret = true;
+
if (core_kernel_text(ip))
return arch_rcu_tasks_ip_in_trampoline(ip);
- return !is_module_text_address(ip);
+
+#ifdef CONFIG_MODULES
+ scoped_guard(rcu) {
+ struct module *mod = __module_text_address(ip);
+
+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ if (mod)
+ ret = READ_ONCE(mod->ftrace_direct_tramp);
+#else
+ if (mod)
+ ret = false;
+#endif
+ }
+#endif
+ return ret;
}
NOKPROBE_SYMBOL(rcu_tasks_ip_in_trampoline);
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 53d5db60bfa5..14f27b887231 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -6076,6 +6076,29 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
ops->trampoline = 0;
}
+/*
+ * A direct trampoline may live in module text rather than in dynamically
+ * allocated text that rcu_tasks_ip_in_trampoline() recognises on its own (see
+ * samples/ftrace/ftrace-direct*.c). The trampoline itself must hold
+ * current->rcu_tramp_nesting across its call-out (see register_ftrace_direct());
+ * marking the owning module here covers the instructions before that increment
+ * and after the decrement, where a task interrupted in the module's text must
+ * not be treated as Tasks-RCU quiescent, so that ftrace_shutdown()'s
+ * synchronize_rcu_tasks() still keeps the module text from being freed under
+ * it.
+ */
+static void ftrace_direct_mark_module(unsigned long addr)
+{
+#ifdef CONFIG_MODULES
+ struct module *mod;
+
+ guard(rcu)();
+ mod = __module_text_address(addr);
+ if (mod)
+ WRITE_ONCE(mod->ftrace_direct_tramp, true);
+#endif
+}
+
/**
* register_ftrace_direct - Call a custom trampoline directly
* for multiple functions registered in @ops
@@ -6090,6 +6113,17 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
* and save the parameters of the function being traced, and restore them
* (or inject new ones if needed), before returning.
*
+ * Nothing but Tasks RCU keeps the trampoline at @addr alive while a task is
+ * executing it or is preempted in something it called. On architectures that
+ * select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU quiescent
+ * state unless current->rcu_tramp_nesting is non-zero, so the trampoline must
+ * increment it before calling out and decrement it before returning, as the
+ * ftrace and BPF trampolines do (see rcu_tasks_trampoline_enter() and
+ * samples/ftrace/ftrace-direct.h). The few instructions before the increment
+ * and after the decrement are covered by the irq-exit IP check: automatically
+ * for trampolines outside kernel and module text (e.g. BPF images), and via
+ * ftrace_direct_mark_module() for trampolines in module text.
+ *
* Returns:
* 0 on success
* -EINVAL - The @ops object was already registered with this call or
@@ -6169,6 +6203,7 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
ops->flags |= MULTI_FLAGS;
ops->trampoline = FTRACE_REGS_ADDR;
ops->direct_call = addr;
+ ftrace_direct_mark_module(addr);
err = register_ftrace_function_nolock(ops);
if (err)
@@ -6237,6 +6272,8 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
lockdep_assert_held_once(&direct_mutex);
+ ftrace_direct_mark_module(addr);
+
/* Enable the tmp_ops to have the same functions as the direct ops */
ftrace_ops_init(&tmp_ops);
tmp_ops.func_hash = ops->func_hash;
@@ -6419,6 +6456,7 @@ int update_ftrace_direct_add(struct ftrace_ops *ops, struct ftrace_hash *hash)
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
if (__ftrace_lookup_ip(direct_functions, entry->ip))
goto out_unlock;
+ ftrace_direct_mark_module(entry->direct);
}
}
@@ -6702,6 +6740,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
tmp = __ftrace_lookup_ip(direct_hash, entry->ip);
if (!tmp)
continue;
+ ftrace_direct_mark_module(entry->direct);
tmp->direct = entry->direct;
}
}
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 06/15] x86/ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (4 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 05/15] ftrace: Mark modules hosting direct-call trampolines for Tasks RCU Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 07/15] x86/kprobes: Maintain Tasks RCU trampoline nesting in the optprobe template Josef Bacik
` (8 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Bracket the call out to the ftrace_ops callback in ftrace_caller and
ftrace_regs_caller with an increment/decrement of
current->rcu_tramp_nesting. The instructions sit inside the region that
create_trampoline() copies for per-ops dynamic trampolines, so those
inherit them; the %rip-relative per-CPU reference to current_task is
fixed up by text_poke_apply_relocation() like CALL_DEPTH_ACCOUNT's.
%rdx is dead at both points (about to be loaded with the ops pointer on
entry, restored by restore_mcount_regs on exit).
Two pieces of core text still run with the count at zero while holding
the address of a Tasks-RCU-protected trampoline they are about to
enter: the static stubs themselves, whose direct-call tails keep a BPF
trampoline address on the stack until the final RET, and, under
CONFIG_MITIGATION_RETHUNK, the return thunk that RET expands to. Add an
ftrace_static_tramp_end marker after ftrace_stub_direct_tramp and linker
symbols around .text..__x86.return_thunk and .text..__x86.rethunk_safe,
and provide arch_rcu_tasks_ip_in_trampoline() covering
[ftrace_caller, ftrace_static_tramp_end) and both thunk ranges so the
irq-exit check treats a task interrupted there as still inside a
trampoline.
The hook is built only under CONFIG_RCU_TASKS_PREEMPT_QS, which x86 does
not select until a later patch.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
arch/x86/kernel/asm-offsets.c | 3 +++
arch/x86/kernel/ftrace.c | 37 +++++++++++++++++++++++++++++++++++++
arch/x86/kernel/ftrace_64.S | 43 +++++++++++++++++++++++++++++++++++++++++++
arch/x86/kernel/vmlinux.lds.S | 4 ++++
4 files changed, 87 insertions(+)
diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c
index 081816888f7a..4f3b1caa5a30 100644
--- a/arch/x86/kernel/asm-offsets.c
+++ b/arch/x86/kernel/asm-offsets.c
@@ -46,6 +46,9 @@ static void __used common(void)
#ifdef CONFIG_STACKPROTECTOR
OFFSET(TASK_stack_canary, task_struct, stack_canary);
#endif
+#ifdef CONFIG_TASKS_RCU
+ OFFSET(TASK_rcu_tramp_nesting, task_struct, rcu_tramp_nesting);
+#endif
BLANK();
OFFSET(pbe_address, pbe, address);
diff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c
index 17d6edfcb7e0..8f63cd4b543c 100644
--- a/arch/x86/kernel/ftrace.c
+++ b/arch/x86/kernel/ftrace.c
@@ -275,6 +275,43 @@ static inline void tramp_free(void *tramp)
execmem_free(tramp);
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+extern void ftrace_static_tramp_end(void);
+extern char __return_thunk_start[], __return_thunk_end[];
+extern char __rethunk_safe_start[], __rethunk_safe_end[];
+
+/*
+ * See rcu_tasks_ip_in_trampoline(). Some core kernel text behaves like a
+ * trampoline for Tasks RCU purposes because a task executing there with
+ * rcu_tramp_nesting == 0 may still be about to enter a Tasks-RCU-protected
+ * trampoline whose address it already holds:
+ *
+ * - the static ftrace_caller / ftrace_regs_caller / ftrace_stub_direct_tramp
+ * stubs, which carry a direct-call target on the stack until their final
+ * RET, and
+ * - the return thunks that RET expands to under CONFIG_MITIGATION_RETHUNK,
+ * which run after leaving the stubs above and before landing in that
+ * target.
+ */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ if (ip >= (unsigned long)ftrace_caller &&
+ ip < (unsigned long)ftrace_static_tramp_end)
+ return true;
+#ifdef CONFIG_MITIGATION_RETPOLINE
+ if (ip >= (unsigned long)__return_thunk_start &&
+ ip < (unsigned long)__return_thunk_end)
+ return true;
+#endif
+#ifdef CONFIG_MITIGATION_SRSO
+ if (ip >= (unsigned long)__rethunk_safe_start &&
+ ip < (unsigned long)__rethunk_safe_end)
+ return true;
+#endif
+ return false;
+}
+#endif /* CONFIG_RCU_TASKS_PREEMPT_QS */
+
/* Defined as markers to the end of the ftrace default trampolines */
extern void ftrace_regs_caller_end(void);
extern void ftrace_caller_end(void);
diff --git a/arch/x86/kernel/ftrace_64.S b/arch/x86/kernel/ftrace_64.S
index 62c1c93aa1c6..902472c41798 100644
--- a/arch/x86/kernel/ftrace_64.S
+++ b/arch/x86/kernel/ftrace_64.S
@@ -7,6 +7,7 @@
#include <linux/cfi_types.h>
#include <linux/linkage.h>
#include <asm/asm-offsets.h>
+#include <asm/percpu.h>
#include <asm/ptrace.h>
#include <asm/ftrace.h>
#include <asm/nospec-branch.h>
@@ -145,6 +146,27 @@ SYM_FUNC_END(ftrace_stub_graph)
#ifdef CONFIG_DYNAMIC_FTRACE
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). These live
+ * inside the region copied into dynamic trampolines; the %rip-relative per-CPU
+ * reference is fixed up by text_poke_apply_relocation() in create_trampoline().
+ * The increment must precede the function_trace_op load: between that load and
+ * the call, the ops pointer in %rdx is protected only by Tasks RCU.
+ */
+.macro RCU_TASKS_TRAMP_ENTER reg:req
+#ifdef CONFIG_TASKS_RCU
+ movq PER_CPU_VAR(current_task), \reg
+ incl TASK_rcu_tramp_nesting(\reg)
+#endif
+.endm
+
+.macro RCU_TASKS_TRAMP_EXIT reg:req
+#ifdef CONFIG_TASKS_RCU
+ movq PER_CPU_VAR(current_task), \reg
+ decl TASK_rcu_tramp_nesting(\reg)
+#endif
+.endm
+
SYM_FUNC_START(__fentry__)
ANNOTATE_NOENDBR
CALL_DEPTH_ACCOUNT
@@ -163,6 +185,8 @@ SYM_FUNC_START(ftrace_caller)
leaq MCOUNT_REG_SIZE+8(%rsp), %rcx
movq %rcx, RSP(%rsp)
+ RCU_TASKS_TRAMP_ENTER %rdx
+
SYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Load the ftrace_ops into the 3rd parameter */
@@ -181,6 +205,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
call ftrace_stub
+ RCU_TASKS_TRAMP_EXIT %rdx
+
/* Handlers can change the RIP */
movq RIP(%rsp), %rax
movq %rax, MCOUNT_REG_SIZE(%rsp)
@@ -209,6 +235,8 @@ SYM_FUNC_START(ftrace_regs_caller)
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER %rdx
+
SYM_INNER_LABEL(ftrace_regs_caller_op_ptr, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Load the ftrace_ops into the 3rd parameter */
@@ -246,6 +274,8 @@ SYM_INNER_LABEL(ftrace_regs_call, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
call ftrace_stub
+ RCU_TASKS_TRAMP_EXIT %rdx
+
/* Copy flags back to SS, to restore them */
movq EFLAGS(%rsp), %rax
movq %rax, MCOUNT_REG_SIZE(%rsp)
@@ -328,6 +358,19 @@ SYM_FUNC_START(ftrace_stub_direct_tramp)
RET
SYM_FUNC_END(ftrace_stub_direct_tramp)
+/*
+ * [ftrace_caller, ftrace_static_tramp_end) is treated as trampoline text by
+ * rcu_tasks_ip_in_trampoline(): after RCU_TASKS_TRAMP_EXIT the stubs may
+ * still hold a direct-call target (a BPF trampoline) on the stack until the
+ * final RET, and that target's lifetime is guarded by Tasks RCU. With
+ * return thunks the RET itself runs elsewhere; arch_rcu_tasks_ip_in_trampoline()
+ * covers the thunk text too.
+ */
+SYM_CODE_START_NOALIGN(ftrace_static_tramp_end)
+ UNWIND_HINT_UNDEFINED
+ ANNOTATE_NOENDBR
+SYM_CODE_END(ftrace_static_tramp_end)
+
#else /* ! CONFIG_DYNAMIC_FTRACE */
SYM_FUNC_START(__fentry__)
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 2438b89a4620..e546283dc267 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -151,7 +151,9 @@ SECTIONS
* definition.
*/
. = srso_alias_untrain_ret | (1 << 2) | (1 << 8) | (1 << 14) | (1 << 20);
+ __rethunk_safe_start = .;
*(.text..__x86.rethunk_safe)
+ __rethunk_safe_end = .;
#endif
ALIGN_ENTRY_TEXT_END
@@ -162,7 +164,9 @@ SECTIONS
SOFTIRQENTRY_TEXT
#ifdef CONFIG_MITIGATION_RETPOLINE
*(.text..__x86.indirect_thunk)
+ __return_thunk_start = .;
*(.text..__x86.return_thunk)
+ __return_thunk_end = .;
#endif
STATIC_CALL_TEXT
*(.gnu.warning)
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 07/15] x86/kprobes: Maintain Tasks RCU trampoline nesting in the optprobe template
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (5 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 06/15] x86/ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
` (7 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
The jump-optimized kprobe template calls optimized_callback() with
preemption still enabled for its first few instructions, so bracket the
call with an increment/decrement of current->rcu_tramp_nesting. The
template lives in .rodata and is memcpy()d into each optinsn slot without
relocation processing, so the per-CPU reference to current_task must be
an absolute %gs: address (R_X86_64_32S, relocated for KASLR like any
other) rather than %rip-relative. %rax has already been saved by
SAVE_REGS_STRING and is dead after the call.
The slot itself is dynamically allocated text, so the instructions before
the increment and after the decrement are covered by the irq-exit IP
check. 64-bit only; 32-bit x86 does not take part.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
arch/x86/kernel/kprobes/opt.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/arch/x86/kernel/kprobes/opt.c b/arch/x86/kernel/kprobes/opt.c
index 3f8fea52619f..f722520bb989 100644
--- a/arch/x86/kernel/kprobes/opt.c
+++ b/arch/x86/kernel/kprobes/opt.c
@@ -31,6 +31,7 @@
#include <asm/set_memory.h>
#include <asm/sections.h>
#include <asm/nospec-branch.h>
+#include <asm/asm-offsets.h>
#include "common.h"
@@ -101,6 +102,23 @@ static void synthesize_set_arg1(kprobe_opcode_t *addr, unsigned long val)
*(unsigned long *)addr = val;
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The
+ * template is memcpy()d into the slot without relocation processing, so the
+ * per-CPU reference must be absolute, not %rip-relative.
+ */
+#if defined(CONFIG_TASKS_RCU) && defined(CONFIG_X86_64)
+#define OPTPROBE_RCU_TASKS_ENTER \
+ " movq %gs:current_task, %rax\n" \
+ " incl " __stringify(TASK_rcu_tramp_nesting) "(%rax)\n"
+#define OPTPROBE_RCU_TASKS_EXIT \
+ " movq %gs:current_task, %rax\n" \
+ " decl " __stringify(TASK_rcu_tramp_nesting) "(%rax)\n"
+#else
+#define OPTPROBE_RCU_TASKS_ENTER
+#define OPTPROBE_RCU_TASKS_EXIT
+#endif
+
asm (
".pushsection .rodata\n"
".global optprobe_template_entry\n"
@@ -114,6 +132,7 @@ asm (
"optprobe_template_clac:\n"
ASM_NOP3
SAVE_REGS_STRING
+ OPTPROBE_RCU_TASKS_ENTER
" movq %rsp, %rsi\n"
".global optprobe_template_val\n"
"optprobe_template_val:\n"
@@ -122,6 +141,7 @@ asm (
".global optprobe_template_call\n"
"optprobe_template_call:\n"
ASM_NOP5
+ OPTPROBE_RCU_TASKS_EXIT
/* Copy 'regs->flags' into 'regs->ss'. */
" movq 18*8(%rsp), %rdx\n"
" movq %rdx, 20*8(%rsp)\n"
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (6 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 07/15] x86/kprobes: Maintain Tasks RCU trampoline nesting in the optprobe template Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-12 3:27 ` Alexei Starovoitov
2026-09-11 14:08 ` [PATCH RFC v2 09/15] arm64: ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller Josef Bacik
` (6 subsequent siblings)
14 siblings, 1 reply; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Emit an increment of current->rcu_tramp_nesting once the trampoline's
frame is set up and a decrement before the final register restore, so
that a task preempted while running fentry/fexit/fmod_ret/LSM programs
or the __bpf_tramp_enter()/__bpf_tramp_exit() glue is not treated as
Tasks-RCU quiescent. Drop the count around the call to the original
function: that may run arbitrarily long without sleeping and must not pin
a Tasks RCU grace period, and the trampoline frame above it is held by
im->pcref rather than by Tasks RCU (see bpf_tramp_image_put()). The
fmod_ret early-exit branch and the ip_after_call -> ip_epilogue poke both
skip the decrement/increment pair around the original call, so the count
stays balanced on every path.
The sequence is "mov r11, gs:[current_task]; inc/dec dword [r11 + off]";
r11 is scratch at every emission point and (u32)¤t_task is a valid
sign-extended %gs-absolute with the current per-CPU layout, the same form
the JIT already uses for this_cpu_off. The image is dynamically
allocated text, so the instructions outside the bracketed region are
covered by the irq-exit IP check.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
arch/x86/net/bpf_jit_comp.c | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 2853e87797a7..a375c1b7bd50 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
*pprog = prog;
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
+ *
+ * mov r11, QWORD PTR gs:[current_task]
+ * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
+ *
+ * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
+ */
+static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
+{
+#ifdef CONFIG_TASKS_RCU
+ u8 *prog = *pprog;
+
+ /* mov r11, gs:[abs32] */
+ EMIT2(0x65, 0x4C);
+ EMIT3(0x8B, 0x1C, 0x25);
+ EMIT((u32)(unsigned long)¤t_task, 4);
+ /* inc/dec dword ptr [r11 + disp32] */
+ EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
+ EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
+
+ *pprog = prog;
+#endif
+}
+
static void emit_return(u8 **pprog, u8 *ip)
{
u8 *prog = *pprog;
@@ -3610,6 +3635,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
/* mov QWORD PTR [rbp - rbx_off], rbx */
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);
+ /*
+ * From here until the matching decrement before the final return, a
+ * preemption of this task is not a Tasks RCU quiescent state. The
+ * instructions above this point are covered by the irq-exit IP check.
+ */
+ emit_rcu_tasks_tramp_nesting(&prog, true);
+
func_meta = nr_regs;
/* Store number of argument registers of the traced function */
emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta);
@@ -3670,6 +3702,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
}
+ /*
+ * The original function may run for a long time without
+ * sleeping; do not let it pin a Tasks RCU grace period. The
+ * trampoline frame above it is held by im->pcref
+ * (__bpf_tramp_enter()), not by Tasks RCU, across the call.
+ */
+ emit_rcu_tasks_tramp_nesting(&prog, false);
if (flags & BPF_TRAMP_F_ORIG_STACK) {
emit_ldx(&prog, BPF_DW, BPF_REG_6, BPF_REG_FP, 8);
EMIT2(0xff, 0xd3); /* call *rbx */
@@ -3680,6 +3719,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
goto cleanup;
}
}
+ emit_rcu_tasks_tramp_nesting(&prog, true);
/* remember return value in a stack for bpf prog to access */
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
im->ip_after_call = image + (prog - (u8 *)rw_image);
@@ -3741,6 +3781,9 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
if (save_ret)
emit_ldx(&prog, BPF_DW, BPF_REG_0, BPF_REG_FP, -8);
+ /* Remaining instructions are covered by the irq-exit IP check. */
+ emit_rcu_tasks_tramp_nesting(&prog, false);
+
emit_ldx(&prog, BPF_DW, BPF_REG_6, BPF_REG_FP, -rbx_off);
EMIT1(0xC9); /* leave */
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 09/15] arm64: ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (7 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 10/15] bpf, arm64: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
` (5 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Bracket the call out to the ftrace_ops callback in ftrace_caller with an
increment/decrement of current->rcu_tramp_nesting, using x12/w13 which
are scratch there. The read-modify-write is not atomic, but only current
modifies the count and every interrupting user is balanced, so nothing is
lost.
ftrace_caller itself, including the early CALL_OPS direct path and the
late direct tail that carry a BPF trampoline address in x17 with the
count at zero, is static kernel text: add an ftrace_static_tramp_end
marker after ftrace_stub_direct_tramp and provide
arch_rcu_tasks_ip_in_trampoline() covering
[ftrace_caller, ftrace_static_tramp_end) so the irq-exit check treats a
task interrupted anywhere in it as inside a trampoline.
The hook is built only under CONFIG_RCU_TASKS_PREEMPT_QS, which arm64
does not select until a later patch.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
arch/arm64/kernel/asm-offsets.c | 3 +++
arch/arm64/kernel/entry-ftrace.S | 35 +++++++++++++++++++++++++++++++++++
arch/arm64/kernel/ftrace.c | 16 ++++++++++++++++
3 files changed, 54 insertions(+)
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index 9c853ed3ceab..f6655a284f18 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -39,6 +39,9 @@ int main(void)
DEFINE(TSK_STACK, offsetof(struct task_struct, stack));
#ifdef CONFIG_STACKPROTECTOR
DEFINE(TSK_STACK_CANARY, offsetof(struct task_struct, stack_canary));
+#endif
+#ifdef CONFIG_TASKS_RCU
+ DEFINE(TSK_RCU_TRAMP_NESTING, offsetof(struct task_struct, rcu_tramp_nesting));
#endif
BLANK();
DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));
diff --git a/arch/arm64/kernel/entry-ftrace.S b/arch/arm64/kernel/entry-ftrace.S
index 025140caafe7..46a102e7199a 100644
--- a/arch/arm64/kernel/entry-ftrace.S
+++ b/arch/arm64/kernel/entry-ftrace.S
@@ -14,6 +14,33 @@
#include <asm/insn.h>
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The whole
+ * of ftrace_caller is treated as trampoline text by the irq-exit IP check (see
+ * arch_rcu_tasks_ip_in_trampoline()), so these only need to bracket the call
+ * out to ops->func; everything before the increment and after the decrement,
+ * including the direct-call tails that carry a BPF trampoline address in x17,
+ * is covered by that. The count is only modified by current and every nested
+ * user (interrupts) is balanced, so a plain ldr/add/str is sufficient.
+ */
+ .macro rcu_tasks_tramp_enter, tsk:req, tmp:req
+#ifdef CONFIG_TASKS_RCU
+ mrs \tsk, sp_el0
+ ldr \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+ add \tmp, \tmp, #1
+ str \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+#endif
+ .endm
+
+ .macro rcu_tasks_tramp_exit, tsk:req, tmp:req
+#ifdef CONFIG_TASKS_RCU
+ mrs \tsk, sp_el0
+ ldr \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+ sub \tmp, \tmp, #1
+ str \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+#endif
+ .endm
+
/*
* Due to -fpatchable-function-entry=2, the compiler has placed two NOPs before
* the regular function prologue. For an enabled callsite, ftrace_init_nop() and
@@ -94,6 +121,8 @@ SYM_CODE_START(ftrace_caller)
stp x29, x30, [sp, #FREGS_SIZE]
add x29, sp, #FREGS_SIZE
+ rcu_tasks_tramp_enter x12, w13
+
/* Prepare arguments for the tracer func */
sub x0, x30, #AARCH64_INSN_SIZE // ip (callsite's BL insn)
mov x1, x9 // parent_ip (callsite's LR)
@@ -111,6 +140,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
bl ftrace_stub // func(ip, parent_ip, op, regs)
#endif
+ rcu_tasks_tramp_exit x12, w13
+
/*
* At the callsite x0-x8 and x19-x30 were live. Any C code will have preserved
* x19-x29 per the AAPCS, and we created frame records upon entry, so we need
@@ -178,6 +209,10 @@ SYM_CODE_START(ftrace_stub_direct_tramp)
SYM_CODE_END(ftrace_stub_direct_tramp)
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
+/* End of [ftrace_caller, ...) for arch_rcu_tasks_ip_in_trampoline(). */
+SYM_CODE_START(ftrace_static_tramp_end)
+SYM_CODE_END(ftrace_static_tramp_end)
+
#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
/*
diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
index e1a3c0b3a051..1b7ac2afed0d 100644
--- a/arch/arm64/kernel/ftrace.c
+++ b/arch/arm64/kernel/ftrace.c
@@ -17,6 +17,22 @@
#include <asm/insn.h>
#include <asm/text-patching.h>
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+extern void ftrace_static_tramp_end(void);
+
+/*
+ * See rcu_tasks_ip_in_trampoline(). ftrace_caller and ftrace_stub_direct_tramp
+ * are core kernel text but must be treated as trampolines: a task preempted in
+ * them may be carrying an ops pointer (x11) or a direct-call BPF trampoline
+ * address (x17) whose lifetime is guarded only by Tasks RCU.
+ */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ return ip >= (unsigned long)ftrace_caller &&
+ ip < (unsigned long)ftrace_static_tramp_end;
+}
+#endif
+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
struct fregs_offset {
const char *name;
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 10/15] bpf, arm64: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (8 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 09/15] arm64: ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 11/15] samples: ftrace: Maintain Tasks RCU trampoline nesting in direct-call trampolines Josef Bacik
` (4 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Same scheme as x86: emit "mrs x10, sp_el0; ldr/add|sub/str w11" to bump
current->rcu_tramp_nesting after the callee-saved registers are stored
and to drop it before they are restored, and release it around the call
to the original function, which im->pcref protects and which must not
pin a Tasks RCU grace period. x10/x11 are scratch at every emission
point; the fmod_ret cbnz target lies after the decrement/increment pair
around the original call, and the ip_after_call nop follows the
re-increment, so the count is balanced on every path. BUILD_BUG_ON
guards the LDR/STR immediate range for the task_struct offset.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
arch/arm64/net/bpf_jit_comp.c | 46 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index c18e005a41db..5c9a7bde5cc9 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -2591,6 +2591,34 @@ static void emit_arena_arg_conv(struct jit_ctx *ctx, u8 dst, u8 src, bool nullab
emit(A64_SUB(0, dst, src, base_lo), ctx);
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
+ *
+ * mrs x10, sp_el0
+ * ldr w11, [x10, #offsetof(struct task_struct, rcu_tramp_nesting)]
+ * add/sub w11, w11, #1
+ * str w11, [x10, #...]
+ *
+ * x10/x11 are scratch in the trampoline at every point this is emitted.
+ */
+static void emit_rcu_tasks_tramp_nesting(struct jit_ctx *ctx, bool enter)
+{
+#ifdef CONFIG_TASKS_RCU
+ const int off = offsetof(struct task_struct, rcu_tramp_nesting);
+ const u8 tsk = A64_R(10), cnt = A64_R(11);
+
+ BUILD_BUG_ON(off & 3 || off >= SZ_16K); /* LDR/STR (imm12, scaled) */
+
+ emit(A64_MRS_SP_EL0(tsk), ctx);
+ emit(A64_LDR32I(cnt, tsk, off), ctx);
+ if (enter)
+ emit(A64_ADD_I(0, cnt, cnt, 1), ctx);
+ else
+ emit(A64_SUB_I(0, cnt, cnt, 1), ctx);
+ emit(A64_STR32I(cnt, tsk, off), ctx);
+#endif
+}
+
static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
const struct btf_func_model *m, const struct arg_aux *a,
bool for_call_origin, bool is_struct_ops, u64 arena_base)
@@ -2854,6 +2882,13 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);
emit(A64_STR64I(A64_R(20), A64_SP, regs_off + 8), ctx);
+ /*
+ * From here until the matching decrement in the epilogue, a preemption
+ * of this task is not a Tasks RCU quiescent state. The instructions
+ * above this point are covered by the irq-exit IP check.
+ */
+ emit_rcu_tasks_tramp_nesting(ctx, true);
+
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* for the first pass, assume the worst case */
if (!ctx->image)
@@ -2898,12 +2933,20 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* the original func takes kernel addresses, never converted ones */
save_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);
+ /*
+ * The original function may run for a long time without
+ * sleeping; do not let it pin a Tasks RCU grace period. The
+ * trampoline frame above it is held by im->pcref
+ * (__bpf_tramp_enter()), not by Tasks RCU, across the call.
+ */
+ emit_rcu_tasks_tramp_nesting(ctx, false);
/* call original func */
emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);
emit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);
emit(A64_RET(A64_R(10)), ctx);
/* store return value */
emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx);
+ emit_rcu_tasks_tramp_nesting(ctx, true);
/* reserve a nop for bpf_tramp_image_put */
im->ip_after_call = ctx->ro_image + ctx->idx;
emit(A64_NOP, ctx);
@@ -2945,6 +2988,9 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
if (flags & BPF_TRAMP_F_RESTORE_REGS)
restore_args(ctx, bargs_off, a->regs_for_args);
+ /* Remaining instructions are covered by the irq-exit IP check. */
+ emit_rcu_tasks_tramp_nesting(ctx, false);
+
/* restore callee saved register x19 and x20 */
emit(A64_LDR64I(A64_R(19), A64_SP, regs_off), ctx);
emit(A64_LDR64I(A64_R(20), A64_SP, regs_off + 8), ctx);
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 11/15] samples: ftrace: Maintain Tasks RCU trampoline nesting in direct-call trampolines
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (9 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 10/15] bpf, arm64: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 12/15] rcutorture: Bracket Tasks RCU readers with trampoline nesting Josef Bacik
` (3 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Follow the register_ftrace_direct() contract in the sample modules: on
x86-64 and arm64, have each hand-written trampoline increment
current->rcu_tramp_nesting before calling its C handler and decrement it
before returning, via a small shared samples/ftrace/ftrace-direct.h.
%r11 and x12/w13 are used as scratch; both are caller-saved, non-argument
registers and therefore dead on entry to and exit from an fentry
trampoline.
The header pulls in the generated asm-offsets.h only on those two
architectures, since it is not generally safe to include from C (PPC32's
TASK_SIZE and arm64's TRAMP_VALIAS clash with the C definitions; the
latter is worked around locally with push_macro/pop_macro). Other
architectures get empty macros and are unchanged.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
samples/ftrace/ftrace-direct-modify.c | 9 ++++
samples/ftrace/ftrace-direct-multi-modify.c | 9 ++++
samples/ftrace/ftrace-direct-multi.c | 5 +++
samples/ftrace/ftrace-direct-too.c | 5 +++
samples/ftrace/ftrace-direct.c | 5 +++
samples/ftrace/ftrace-direct.h | 64 +++++++++++++++++++++++++++++
6 files changed, 97 insertions(+)
diff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c
index 164d9dd6fd92..eb8230fa4242 100644
--- a/samples/ftrace/ftrace-direct-modify.c
+++ b/samples/ftrace/ftrace-direct-modify.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -73,7 +74,9 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" call my_direct_func1\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp1, .-my_tramp1\n"
@@ -85,7 +88,9 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" call my_direct_func2\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp2, .-my_tramp2\n"
@@ -141,11 +146,13 @@ asm (
" .globl my_tramp1\n"
" my_tramp1:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #16\n"
" stp x9, x30, [sp]\n"
" bl my_direct_func1\n"
" ldp x30, x9, [sp]\n"
" add sp, sp, #16\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp1, .-my_tramp1\n"
@@ -153,11 +160,13 @@ asm (
" .globl my_tramp2\n"
" my_tramp2:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #16\n"
" stp x9, x30, [sp]\n"
" bl my_direct_func2\n"
" ldp x30, x9, [sp]\n"
" add sp, sp, #16\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp2, .-my_tramp2\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c
index b03766c6217b..c8f1062e5d1a 100644
--- a/samples/ftrace/ftrace-direct-multi-modify.c
+++ b/samples/ftrace/ftrace-direct-multi-modify.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -77,10 +78,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func1\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp1, .-my_tramp1\n"
@@ -92,10 +95,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func2\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp2, .-my_tramp2\n"
@@ -154,6 +159,7 @@ asm (
" .globl my_tramp1\n"
" my_tramp1:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -162,6 +168,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp1, .-my_tramp1\n"
@@ -169,6 +176,7 @@ asm (
" .globl my_tramp2\n"
" my_tramp2:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -177,6 +185,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp2, .-my_tramp2\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-multi.c b/samples/ftrace/ftrace-direct-multi.c
index 3fe6ddaf0b69..bc6a88dd4ffc 100644
--- a/samples/ftrace/ftrace-direct-multi.c
+++ b/samples/ftrace/ftrace-direct-multi.c
@@ -3,6 +3,7 @@
#include <linux/mm.h> /* for handle_mm_fault() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#include <linux/sched/stat.h>
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
@@ -56,10 +57,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -101,6 +104,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -109,6 +113,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-too.c b/samples/ftrace/ftrace-direct-too.c
index bf2411aa6fd7..247e418644a2 100644
--- a/samples/ftrace/ftrace-direct-too.c
+++ b/samples/ftrace/ftrace-direct-too.c
@@ -3,6 +3,7 @@
#include <linux/mm.h> /* for handle_mm_fault() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -61,6 +62,7 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" pushq %rsi\n"
" pushq %rdx\n"
@@ -70,6 +72,7 @@ asm (
" popq %rdx\n"
" popq %rsi\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -110,6 +113,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #48\n"
" stp x9, x30, [sp]\n"
" stp x0, x1, [sp, #16]\n"
@@ -119,6 +123,7 @@ asm (
" ldp x0, x1, [sp, #16]\n"
" ldp x2, x3, [sp, #32]\n"
" add sp, sp, #48\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct.c b/samples/ftrace/ftrace-direct.c
index 5368c8c39cbb..9e1964baf28b 100644
--- a/samples/ftrace/ftrace-direct.c
+++ b/samples/ftrace/ftrace-direct.c
@@ -3,6 +3,7 @@
#include <linux/sched.h> /* for wake_up_process() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -54,9 +55,11 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" call my_direct_func\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -97,6 +100,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -104,6 +108,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct.h b/samples/ftrace/ftrace-direct.h
new file mode 100644
index 000000000000..d0313f33f47f
--- /dev/null
+++ b/samples/ftrace/ftrace-direct.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _SAMPLES_FTRACE_DIRECT_H
+#define _SAMPLES_FTRACE_DIRECT_H
+
+#include <linux/stringify.h>
+
+/*
+ * A direct-call trampoline is entered with no lock, refcount or RCU marker
+ * held; only Tasks RCU keeps it (and, for a module, its text) alive while a
+ * task is inside it or preempted in something it called. On architectures
+ * that select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU
+ * quiescent state unless current->rcu_tramp_nesting is non-zero, so the
+ * trampoline must raise it before calling out and drop it afterwards, exactly
+ * like the ftrace and BPF trampolines do. See rcu_tasks_trampoline_enter()
+ * and register_ftrace_direct(). The instructions before the increment and
+ * after the decrement are covered by ftrace_direct_mark_module().
+ *
+ * These expand to instruction strings for use inside the samples' asm()
+ * trampolines. The scratch register is caller-saved and not an argument
+ * register, so it is dead on entry to and exit from an fentry trampoline.
+ *
+ * The generated asm-offsets.h is only pulled in on the architectures that need
+ * it here: it is not generally safe to include from C (e.g. PPC32's TASK_SIZE
+ * and arm64's TRAMP_VALIAS clash with the C definitions), which is why the
+ * samples themselves guard their own include of it.
+ */
+#if defined(CONFIG_TASKS_RCU) && defined(CONFIG_X86_64)
+
+#include <asm/asm-offsets.h>
+
+#define RCU_TASKS_TRAMP_ENTER \
+ " movq %gs:current_task(%rip), %r11\n" \
+ " incl " __stringify(TASK_rcu_tramp_nesting) "(%r11)\n"
+#define RCU_TASKS_TRAMP_EXIT \
+ " movq %gs:current_task(%rip), %r11\n" \
+ " decl " __stringify(TASK_rcu_tramp_nesting) "(%r11)\n"
+
+#elif defined(CONFIG_TASKS_RCU) && defined(CONFIG_ARM64)
+
+/* arm64's asm-offsets.h redefines TRAMP_VALIAS from <asm/fixmap.h>. */
+#pragma push_macro("TRAMP_VALIAS")
+#undef TRAMP_VALIAS
+#include <asm/asm-offsets.h>
+#pragma pop_macro("TRAMP_VALIAS")
+
+#define RCU_TASKS_TRAMP_ENTER \
+ " mrs x12, sp_el0\n" \
+ " ldr w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n" \
+ " add w13, w13, #1\n" \
+ " str w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n"
+#define RCU_TASKS_TRAMP_EXIT \
+ " mrs x12, sp_el0\n" \
+ " ldr w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n" \
+ " sub w13, w13, #1\n" \
+ " str w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n"
+
+#else
+
+#define RCU_TASKS_TRAMP_ENTER
+#define RCU_TASKS_TRAMP_EXIT
+
+#endif
+
+#endif /* _SAMPLES_FTRACE_DIRECT_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 12/15] rcutorture: Bracket Tasks RCU readers with trampoline nesting
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (10 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 11/15] samples: ftrace: Maintain Tasks RCU trampoline nesting in direct-call trampolines Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 13/15] rcu-tasks: Treat preemption outside trampolines as a quiescent state Josef Bacik
` (2 subsequent siblings)
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
rcutorture's tasks flavor models a Tasks RCU reader as "any stretch of
kernel code", and rcu_read_delay() deliberately preempts inside it to
check that a preemption does not end the read-side critical section.
Once preemption outside a trampoline becomes a quiescent state that
model no longer matches what Tasks RCU protects, and the readers would
report false too-short grace periods.
Have tasks_torture_read_lock()/unlock() raise and drop
current->rcu_tramp_nesting so the reader models a trampoline, which is
the thing Tasks RCU actually guards; the deliberate preemption inside it
then continues to be, correctly, not a quiescent state.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
kernel/rcu/rcutorture.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 794937e13e7c..df6dd708cea7 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -1144,11 +1144,17 @@ static struct rcu_torture_ops trivial_preempt_ops = {
static int tasks_torture_read_lock(void)
{
+ /*
+ * Model a trampoline: with CONFIG_RCU_TASKS_PREEMPT_QS a preemption is
+ * otherwise a quiescent state and rcu_read_delay() preempts on purpose.
+ */
+ rcu_tasks_trampoline_enter();
return 0;
}
static void tasks_torture_read_unlock(int idx)
{
+ rcu_tasks_trampoline_exit();
}
static void rcu_tasks_torture_deferred_free(struct rcu_torture *p)
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 13/15] rcu-tasks: Treat preemption outside trampolines as a quiescent state
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (11 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 12/15] rcutorture: Bracket Tasks RCU readers with trampoline nesting Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 14/15] rcu-tasks: Retire switched-out tasks with no trampoline nesting at scan time Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 15/15] rcu-tasks: Kick running holdouts through the scheduler Josef Bacik
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
Tasks RCU only accepts a voluntary context switch, usermode or idle as a
quiescent state, because a task that was preempted may be sitting in a
trampoline whose text is about to be freed. On PREEMPT_LAZY kernels,
where cond_resched() is a no-op and CPU-bound kernel threads only ever
lose the CPU through preemption, that means any long-running kthread or
kworker stalls every synchronize_rcu_tasks() caller -- BPF and LSM
program detach and DYNAMIC ftrace_ops teardown via ftrace_shutdown(),
and kprobe (un)registration via the jump optimizer, which waits under
kprobe_mutex, text_mutex and cpus_read_lock() -- for its entire run,
unless someone sprinkles cond_resched_tasks_rcu_qs() into it. A cgroup
writeback worker draining a large cgwb for eleven minutes was enough to
back 40+ tasks up behind trampoline_mutex and trip the hung-task panic.
With the previous patches, every Tasks-RCU-protected trampoline on
x86-64 and arm64 (ftrace_caller and its dynamic copies, BPF trampoline
images, the optprobe template, out-of-line direct trampolines) holds
current->rcu_tramp_nesting across its call-out, and the irq-exit
preemption path holds it across preempt_schedule_irq() whenever the
interrupted IP is somewhere the counter cannot cover: trampoline
entry/exit instructions and other dynamically allocated text, the static
ftrace stubs and x86 return thunks on the way into a direct-call target,
modules hosting their own direct trampolines. The kprobe
jump-optimization window, which is ordinary text a task may have been
parked in before the kprobe existed, is instead re-checked against the
recorded irq-preemption IP at each decision (rcu_tasks_irq_ip_holds()).
A task that is context-switched with the count at zero and no such IP
therefore cannot be inside, called from, or about to resume into
anything Tasks RCU protects.
So let rcu_tasks_classic_qs() clear the holdout flag on a preemption
too when rcu_tramp_nesting is zero, on architectures that select
ARCH_HAS_RCU_TASKS_PREEMPT_QS, and select it for x86-64 and for arm64
with DYNAMIC_FTRACE_WITH_ARGS. A running holdout is already poked via
rcu_request_urgent_qs_task(), which makes the next tick set
NEED_RESCHED; the resulting preemption -- from irq exit, or synchronously
at the next preempt_enable() -- now retires it, so a Tasks RCU grace
period is bounded by roughly a tick plus the longest preempt-disabled
section instead of by the longest stretch without a voluntary schedule().
Other architectures keep the voluntary-only rule. Update the Tasks RCU
comments, Documentation/RCU (Requirements.rst, checklist.rst) and the
FORCE_TASKS_RCU help text to match.
Cost: one load of current plus an inc/dec per trampoline entry and exit,
and on irq-exit preemption one core_kernel_text() check plus, with
OPTPROBES, MAX_OPTIMIZED_LENGTH-1 lockless kprobe hash lookups.
Not covered: x86-32 and the other GENERIC_IRQ_ENTRY architectures, and
return_to_handler / the rethook trampoline, whose C callees take the
ftrace recursion lock before touching any ops.
Tested under QEMU (x86-64, PREEMPT_LAZY, PREEMPT_RCU=n, PROVE_RCU, with
and without PREEMPT_DYNAMIC) against a kthread spinning in-kernel for
30s with the function tracer, an ftrace kprobe, an optimized kprobe and
fentry/fexit programs live: synchronize_rcu_tasks() 29.7s -> 0.1-0.3s,
ftrace_shutdown() of a DYNAMIC ops 27s -> 0.2-0.8s, the ftrace-direct
sample modules load/fire/unload in ~2.5s each during the spin, no
warnings. arm64 is build-tested only.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
.../RCU/Design/Requirements/Requirements.rst | 28 ++++++++++++++++------
Documentation/RCU/checklist.rst | 8 ++++++-
arch/arm64/Kconfig | 1 +
arch/x86/Kconfig | 1 +
include/linux/rcupdate.h | 15 +++++++++++-
kernel/rcu/Kconfig | 7 +++---
kernel/rcu/tasks.h | 15 ++++++++----
7 files changed, 59 insertions(+), 16 deletions(-)
diff --git a/Documentation/RCU/Design/Requirements/Requirements.rst b/Documentation/RCU/Design/Requirements/Requirements.rst
index 8101fe6229d5..428b5e8f4b4e 100644
--- a/Documentation/RCU/Design/Requirements/Requirements.rst
+++ b/Documentation/RCU/Design/Requirements/Requirements.rst
@@ -2739,13 +2739,27 @@ userspace execution also delimit tasks-RCU read-side critical sections.
Idle tasks are ignored by Tasks RCU, and Tasks Rude RCU may be used to
interact with them.
-Note well that involuntary context switches are *not* Tasks-RCU quiescent
-states. After all, in preemptible kernels, a task executing code in a
-trampoline might be preempted. In this case, the Tasks-RCU grace period
-clearly cannot end until that task resumes and its execution leaves that
-trampoline. This means, among other things, that cond_resched() does
-not provide a Tasks RCU quiescent state. (Instead, use rcu_softirq_qs()
-from softirq or rcu_tasks_classic_qs() otherwise.)
+Note well that, by default, involuntary context switches are *not*
+Tasks-RCU quiescent states. After all, in preemptible kernels, a task
+executing code in a trampoline might be preempted. In this case, the
+Tasks-RCU grace period clearly cannot end until that task resumes and its
+execution leaves that trampoline. This means, among other things, that
+cond_resched() does not provide a Tasks RCU quiescent state. (Instead,
+use rcu_softirq_qs() from softirq or rcu_tasks_classic_qs() otherwise.)
+
+Architectures that select ``CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS`` relax
+this: there, every trampoline whose lifetime Tasks RCU guards (the ftrace
+and BPF trampolines, optprobe slots, out-of-line ftrace direct-call
+trampolines) increments ``current->rcu_tramp_nesting`` before calling out
+and decrements it before returning, and the irq-exit preemption path
+covers the few instructions the counter cannot (see
+rcu_tasks_ip_in_trampoline() and rcu_tasks_irq_ip_holds()). A task that
+is preempted with that count at zero is therefore known not to be in, or
+called from, any trampoline, and such a preemption *is* a Tasks-RCU
+quiescent state. The obligation moves to the trampolines: anything that
+relies on synchronize_rcu_tasks() to protect code a task may be preempted
+in must maintain the count (see register_ftrace_direct()), or Tasks RCU
+will not wait for it on those architectures.
The tasks-RCU API is quite compact, consisting only of
call_rcu_tasks(), synchronize_rcu_tasks(), and
diff --git a/Documentation/RCU/checklist.rst b/Documentation/RCU/checklist.rst
index 4b30f701225f..28df48fecac7 100644
--- a/Documentation/RCU/checklist.rst
+++ b/Documentation/RCU/checklist.rst
@@ -252,7 +252,13 @@ over a rather long period of time, but improvements are always welcome!
a. If the updater uses synchronize_rcu_tasks() or
call_rcu_tasks(), then the readers must refrain from
executing voluntary context switches, that is, from
- blocking.
+ blocking. On architectures that select
+ CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS an involuntary
+ context switch is also a quiescent state unless
+ current->rcu_tramp_nesting is non-zero, so a reader
+ there is a trampoline that maintains that count (see
+ rcu_tasks_trampoline_enter()), not an arbitrary
+ stretch of kernel code.
b. If the updater uses call_rcu_tasks_trace()
or synchronize_rcu_tasks_trace(), then the
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef944..0e6c1e0b236f 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -44,6 +44,7 @@ config ARM64
select ARCH_HAS_PREEMPT_LAZY
select ARCH_HAS_PTDUMP
select ARCH_HAS_PTE_SPECIAL
+ select ARCH_HAS_RCU_TASKS_PREEMPT_QS if DYNAMIC_FTRACE_WITH_ARGS
select ARCH_HAS_HW_PTE_YOUNG
select ARCH_HAS_SETUP_DMA_OPS
select ARCH_HAS_SET_DIRECT_MAP
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 15fd9ec5ecac..0a6427019345 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -99,6 +99,7 @@ config X86
select ARCH_HAS_PREEMPT_LAZY
select ARCH_HAS_PTDUMP
select ARCH_HAS_PTE_SPECIAL
+ select ARCH_HAS_RCU_TASKS_PREEMPT_QS if X86_64
select ARCH_HAS_HW_PTE_YOUNG
select ARCH_HAS_NONLEAF_PMD_YOUNG if PGTABLE_LEVELS > 2
select ARCH_HAS_UACCESS_FLUSHCACHE if X86_64
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 4cfe096d624f..9509f99ec965 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -210,6 +210,11 @@ bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip);
* preemption and rcu_tasks_irq_ip_holds() checks it at every quiescent-state
* decision, locally and from the grace-period kthread.
*
+ * With both in place, on architectures that select
+ * ARCH_HAS_RCU_TASKS_PREEMPT_QS, a preemption with rcu_tramp_nesting == 0 is
+ * a Tasks RCU quiescent state, and a CPU-bound kernel thread no longer needs
+ * to volunteer one via cond_resched_tasks_rcu_qs().
+ *
* Only current writes the count and only current (or an interrupt on the same
* CPU) reads it, so plain accesses suffice.
*/
@@ -241,9 +246,17 @@ static __always_inline void rcu_tasks_note_irq_ip(unsigned long ip)
WRITE_ONCE(current->rcu_tasks_irq_ip, ip);
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+#define rcu_tasks_preempt_is_qs(t) \
+ (!READ_ONCE((t)->rcu_tramp_nesting) && !rcu_tasks_irq_ip_holds(t))
+#else
+#define rcu_tasks_preempt_is_qs(t) false
+#endif
+
# define rcu_tasks_classic_qs(t, preempt) \
do { \
- if (!(preempt) && READ_ONCE((t)->rcu_tasks_holdout)) \
+ if (READ_ONCE((t)->rcu_tasks_holdout) && \
+ (!(preempt) || rcu_tasks_preempt_is_qs(t))) \
WRITE_ONCE((t)->rcu_tasks_holdout, false); \
} while (0)
void call_rcu_tasks(struct rcu_head *head, rcu_callback_t func);
diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig
index 999f8228a13d..8e7c94329105 100644
--- a/kernel/rcu/Kconfig
+++ b/kernel/rcu/Kconfig
@@ -94,9 +94,10 @@ config FORCE_TASKS_RCU
default n
help
This option force-enables a task-based RCU implementation
- that uses only voluntary context switch (not preemption!),
- idle, and user-mode execution as quiescent states. Not for
- manual selection in most cases.
+ that uses only voluntary context switch (not preemption, unless
+ the architecture selects ARCH_HAS_RCU_TASKS_PREEMPT_QS and the
+ task is outside any trampoline), idle, and user-mode execution
+ as quiescent states. Not for manual selection in most cases.
config NEED_TASKS_RCU
bool
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 1b9fe1bfa591..bab08a666dc0 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -905,7 +905,10 @@ static void rcu_tasks_wait_gp(struct rcu_tasks *rtp)
//
// Simple variant of RCU whose quiescent states are voluntary context
// switch, cond_resched_tasks_rcu_qs(), user-space execution, and idle.
-// As such, grace periods can take one good long time. There are no
+// With CONFIG_RCU_TASKS_PREEMPT_QS, a preemption taken while the task is
+// not inside a trampoline (current->rcu_tramp_nesting == 0, see
+// rcu_tasks_trampoline_enter()) is a quiescent state as well; without it,
+// grace periods can take one good long time. There are no
// read-side primitives similar to rcu_read_lock() and rcu_read_unlock()
// because this implementation is intended to get the system into a safe
// state for some of the manipulations involved in tracing and the like.
@@ -1263,8 +1266,11 @@ static void tasks_rcu_exit_stall(struct timer_list *unused)
* period elapses, in other words after all currently executing rcu-tasks
* read-side critical sections have completed. call_rcu_tasks() assumes
* that the read-side critical sections end at a voluntary context
- * switch (not a preemption!), cond_resched_tasks_rcu_qs(), entry into idle,
- * or transition to usermode execution. As such, there are no read-side
+ * switch, cond_resched_tasks_rcu_qs(), entry into idle, transition to
+ * usermode execution, or, with CONFIG_RCU_TASKS_PREEMPT_QS, a preemption
+ * taken outside any trampoline (current->rcu_tramp_nesting == 0, see
+ * rcu_tasks_trampoline_enter()); otherwise a preemption is not a
+ * quiescent state. As such, there are no read-side
* primitives analogous to rcu_read_lock() and rcu_read_unlock() because
* this primitive is intended to determine that all tasks have passed
* through a safe state, not so much for data-structure synchronization.
@@ -1286,7 +1292,8 @@ EXPORT_SYMBOL_GPL(call_rcu_tasks);
* executing rcu-tasks read-side critical sections have elapsed. These
* read-side critical sections are delimited by calls to schedule(),
* cond_resched_tasks_rcu_qs(), idle execution, userspace execution, calls
- * to synchronize_rcu_tasks(), and (in theory, anyway) cond_resched().
+ * to synchronize_rcu_tasks(), (in theory, anyway) cond_resched(), and,
+ * with CONFIG_RCU_TASKS_PREEMPT_QS, preemption outside any trampoline.
*
* This is a very specialized primitive, intended only for a few uses in
* tracing and other situations requiring manipulation of function
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 14/15] rcu-tasks: Retire switched-out tasks with no trampoline nesting at scan time
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (12 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 13/15] rcu-tasks: Treat preemption outside trampolines as a quiescent state Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 15/15] rcu-tasks: Kick running holdouts through the scheduler Josef Bacik
14 siblings, 0 replies; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
The previous patch lets a task report its own quiescent state when it is
preempted with rcu_tramp_nesting == 0, but a task that was preempted
before the grace period started and simply has not run since is still
listed as a holdout until it next passes through __schedule(). As Paul
pointed out, the grace-period kthread can settle that case itself.
For a task that is switched out, task_call_func() pins it and
!task_curr() tells us it left the CPU through __schedule(), whose
locking orders its last rcu_tramp_nesting and rcu_tasks_irq_ip updates
before ours; a task preempted from irq exit inside trampoline text has
the count held non-zero across the switch by irqentry_preempt(). So a
pinned, not-running task with a zero count is neither in nor called
from a trampoline, and with rcu_tasks_irq_ip_holds() also clear (the
kprobe jump window, which can open after the task was switched out) it
is quiescent now, whether or not it ever runs again. Check that in
rcu_tasks_pertask() so such tasks never become holdouts, and in
check_holdout_task() so they are retired on the next scan.
This is the only remote reader of the count, and it only reads it for a
pinned, switched-out task, so the trampoline-side increment and
decrement stay plain on every preemption model, PREEMPT_RT included.
Only under CONFIG_RCU_TASKS_PREEMPT_QS; other architectures are
unchanged.
Suggested-by: Paul E. McKenney <paulmck@kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
kernel/rcu/tasks.h | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index bab08a666dc0..ba432bd922e2 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -1014,10 +1014,39 @@ static bool rcu_tasks_is_holdout(struct task_struct *t)
return true;
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+/* task_call_func() callback: is @t switched out with no trampoline in play? */
+static int rcu_tasks_switched_out_clean(struct task_struct *t, void *arg)
+{
+ /*
+ * With @t pinned, !task_curr() means it last left the CPU through
+ * __schedule(), so its rcu_tramp_nesting and rcu_tasks_irq_ip are
+ * stable and ordered before the rq lock we hold. A task preempted
+ * from irq exit inside trampoline text has the count held non-zero
+ * across the switch by irqentry_preempt(), so zero here means neither
+ * in nor called from a trampoline; rcu_tasks_irq_ip_holds() covers the
+ * one case that can become true after the task was switched out (a
+ * kprobe jump-optimization window). Both clear: already quiescent,
+ * whether or not it ever runs again.
+ */
+ return !task_curr(t) && !READ_ONCE(t->rcu_tramp_nesting) &&
+ !rcu_tasks_irq_ip_holds(t);
+}
+
+/* Is @t, right now, switched out somewhere that is a quiescent state? */
+static bool rcu_tasks_preempted_qs(struct task_struct *t)
+{
+ return task_call_func(t, rcu_tasks_switched_out_clean, NULL);
+}
+#else
+static bool rcu_tasks_preempted_qs(struct task_struct *t) { return false; }
+#endif
+
/* Per-task initial processing. */
static void rcu_tasks_pertask(struct task_struct *t, struct list_head *hop)
{
- if (t != current && rcu_tasks_is_holdout(t)) {
+ if (t != current && rcu_tasks_is_holdout(t) &&
+ !rcu_tasks_preempted_qs(t)) {
get_task_struct(t);
t->rcu_tasks_nvcsw = READ_ONCE(t->nvcsw);
WRITE_ONCE(t->rcu_tasks_holdout, true);
@@ -1181,6 +1210,7 @@ static void check_holdout_task(struct task_struct *t,
if (!READ_ONCE(t->rcu_tasks_holdout) ||
t->rcu_tasks_nvcsw != READ_ONCE(t->nvcsw) ||
!rcu_tasks_is_holdout(t) ||
+ rcu_tasks_preempted_qs(t) ||
(IS_ENABLED(CONFIG_NO_HZ_FULL) &&
!is_idle_task(t) && READ_ONCE(t->rcu_tasks_idle_cpu) >= 0)) {
WRITE_ONCE(t->rcu_tasks_holdout, false);
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH RFC v2 15/15] rcu-tasks: Kick running holdouts through the scheduler
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
` (13 preceding siblings ...)
2026-09-11 14:08 ` [PATCH RFC v2 14/15] rcu-tasks: Retire switched-out tasks with no trampoline nesting at scan time Josef Bacik
@ 2026-09-11 14:08 ` Josef Bacik
2026-09-11 18:46 ` Paul E. McKenney
14 siblings, 1 reply; 26+ messages in thread
From: Josef Bacik @ 2026-09-11 14:08 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Boqun Feng, Thomas Gleixner, Peter Zijlstra,
Steven Rostedt, Masami Hiramatsu, Mark Rutland, Jiri Olsa,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, x86,
Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel, Josef Bacik
A holdout that is running on a CPU with nothing else runnable is only
preempted if the tick acts on rcu_request_urgent_qs_task()'s flag, and
there may be no tick. Now that a preemption outside a trampoline is a
quiescent state, have check_holdout_task() call resched_cpu() on a
running holdout as well, so the scheduler IPIs it and it goes through
__schedule() and reports (or, if it is inside a trampoline, does not
report) its own state with purely local ordering. Nothing reads a
running task's rcu_tramp_nesting remotely.
Only under CONFIG_RCU_TASKS_PREEMPT_QS; other architectures are
unchanged.
Suggested-by: Paul E. McKenney <paulmck@kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
kernel/rcu/tasks.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index ba432bd922e2..02d2592ab7a3 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -1038,8 +1038,18 @@ static bool rcu_tasks_preempted_qs(struct task_struct *t)
{
return task_call_func(t, rcu_tasks_switched_out_clean, NULL);
}
+
+/* Make a running holdout pass through __schedule() soon, tick or no tick. */
+static void rcu_tasks_kick_running(struct task_struct *t)
+{
+ int cpu = task_cpu(t);
+
+ if (task_curr(t) && cpu_online(cpu))
+ resched_cpu(cpu);
+}
#else
static bool rcu_tasks_preempted_qs(struct task_struct *t) { return false; }
+static void rcu_tasks_kick_running(struct task_struct *t) { }
#endif
/* Per-task initial processing. */
@@ -1219,6 +1229,7 @@ static void check_holdout_task(struct task_struct *t,
return;
}
rcu_request_urgent_qs_task(t);
+ rcu_tasks_kick_running(t);
if (!needreport)
return;
if (*firstreport) {
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count
2026-09-11 14:08 ` [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count Josef Bacik
@ 2026-09-11 17:23 ` Paul E. McKenney
0 siblings, 0 replies; 26+ messages in thread
From: Paul E. McKenney @ 2026-09-11 17:23 UTC (permalink / raw)
To: Josef Bacik
Cc: Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes, Boqun Feng,
Thomas Gleixner, Peter Zijlstra, Steven Rostedt, Masami Hiramatsu,
Mark Rutland, Jiri Olsa, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, x86, Catalin Marinas, Will Deacon,
Puranjay Mohan, Xu Kuohai, Andy Lutomirski, Josh Triplett,
Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Juergen Gross, Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel
On Fri, Sep 11, 2026 at 02:08:39PM +0000, Josef Bacik wrote:
> Tasks RCU exists so that ftrace, BPF and kprobes can free trampoline
> text once no task can still be executing in it. Today the only way a
> task tells Tasks RCU "I am not in a trampoline" is a voluntary context
> switch, so a preempted task is always assumed to be inside one.
>
> Add task_struct::rcu_tramp_nesting so that trampolines can say so
> directly: a trampoline increments it before calling out and decrements
> it before returning, and while it is non-zero the task must not be
> treated as Tasks-RCU quiescent. Provide rcu_tasks_trampoline_enter()
> and rcu_tasks_trampoline_exit() for C users, report the count in the
> Tasks RCU stall output, and, under CONFIG_PROVE_RCU, assert that it is
> zero on every return to userspace since no task can legitimately reach
> userspace with a trampoline on its stack.
>
> Only current ever writes the count and every nested user (interrupts
> running their own trampolines) is balanced, so plain accesses suffice.
>
> The callbacks reached from static trampolines (return_to_handler, the
> rethook and kretprobe trampolines) are covered by the preempt_disable()
> in the ftrace recursion protection rather than by the count; note that
> dependency in trace_recursion.h so it is not lost if the
> preempt_disable() is ever removed from there.
>
> Nothing increments the count and nothing consults it for quiescent-state
> decisions yet; both come in later patches.
>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Please see below for a line-saving nit.
Thanx, Paul
> ---
> include/linux/irq-entry-common.h | 2 ++
> include/linux/rcupdate.h | 37 +++++++++++++++++++++++++++++++++++++
> include/linux/sched.h | 1 +
> include/linux/trace_recursion.h | 11 +++++++++++
> kernel/fork.c | 1 +
> kernel/rcu/tasks.h | 3 ++-
> 6 files changed, 54 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
> index 0bb6c03481fa..8da571622000 100644
> --- a/include/linux/irq-entry-common.h
> +++ b/include/linux/irq-entry-common.h
> @@ -5,6 +5,7 @@
> #include <linux/context_tracking.h>
> #include <linux/hrtimer_rearm.h>
> #include <linux/kmsan.h>
> +#include <linux/rcupdate.h>
> #include <linux/rseq_entry.h>
> #include <linux/static_call_types.h>
> #include <linux/syscalls.h>
> @@ -214,6 +215,7 @@ static __always_inline void __exit_to_user_mode_validate(void)
> {
> /* Ensure that kernel state is sane for a return to userspace */
> kmap_assert_nomap();
> + rcu_tasks_trampoline_assert_none();
> lockdep_assert_irqs_disabled();
> lockdep_sys_exit();
> }
> diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
> index 44c07a66edff..b5c666c82479 100644
> --- a/include/linux/rcupdate.h
> +++ b/include/linux/rcupdate.h
> @@ -180,6 +180,37 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
> #ifdef CONFIG_TASKS_RCU_GENERIC
>
> # ifdef CONFIG_TASKS_RCU
> +
> +/*
> + * Trampoline nesting: dynamically allocated text (ftrace trampolines, BPF
> + * trampoline images, kprobe optinsn slots) that relies on Tasks RCU for its
> + * lifetime brackets itself with an increment/decrement of
> + * current->rcu_tramp_nesting. While the count is non-zero the task is inside,
> + * or was called from, such text and an involuntary context switch must not be
> + * treated as a Tasks RCU quiescent state.
> + *
> + * Only current writes the count and only current (or an interrupt on the same
> + * CPU) reads it, so plain accesses suffice.
> + */
> +static __always_inline void rcu_tasks_trampoline_enter(void)
> +{
> + current->rcu_tramp_nesting++;
> + barrier();
> +}
> +
> +static __always_inline void rcu_tasks_trampoline_exit(void)
> +{
> + barrier();
> + current->rcu_tramp_nesting--;
> +}
> +
> +/* A task must never reach userspace with a trampoline on its stack. */
> +static __always_inline void rcu_tasks_trampoline_assert_none(void)
> +{
> + if (IS_ENABLED(CONFIG_PROVE_RCU))
> + WARN_ON_ONCE(current->rcu_tramp_nesting);
Save a line as follows?
WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && current->rcu_tramp_nesting);
> +}
> +
> # define rcu_tasks_classic_qs(t, preempt) \
> do { \
> if (!(preempt) && READ_ONCE((t)->rcu_tasks_holdout)) \
> @@ -192,6 +223,9 @@ void rcu_tasks_torture_stats_print(char *tt, char *tf);
> # define rcu_tasks_classic_qs(t, preempt) do { } while (0)
> # define call_rcu_tasks call_rcu
> # define synchronize_rcu_tasks synchronize_rcu
> +static inline void rcu_tasks_trampoline_enter(void) { }
> +static inline void rcu_tasks_trampoline_exit(void) { }
> +static inline void rcu_tasks_trampoline_assert_none(void) { }
> # endif
>
> #define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))
> @@ -208,6 +242,9 @@ void exit_tasks_rcu_finish(void);
> #define rcu_tasks_classic_qs(t, preempt) do { } while (0)
> #define rcu_tasks_qs(t, preempt) do { } while (0)
> #define rcu_note_voluntary_context_switch(t) do { } while (0)
> +static inline void rcu_tasks_trampoline_enter(void) { }
> +static inline void rcu_tasks_trampoline_exit(void) { }
> +static inline void rcu_tasks_trampoline_assert_none(void) { }
> #define call_rcu_tasks call_rcu
> #define synchronize_rcu_tasks synchronize_rcu
> static inline void exit_tasks_rcu_start(void) { }
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 8b3d47a325cc..d2e7b1b3c9d2 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -956,6 +956,7 @@ struct task_struct {
> unsigned long rcu_tasks_nvcsw;
> u8 rcu_tasks_holdout;
> u8 rcu_tasks_idx;
> + int rcu_tramp_nesting;
> int rcu_tasks_idle_cpu;
> struct list_head rcu_tasks_holdout_list;
> int rcu_tasks_exit_cpu;
> diff --git a/include/linux/trace_recursion.h b/include/linux/trace_recursion.h
> index e6ca052b2a85..2da23a52ca4a 100644
> --- a/include/linux/trace_recursion.h
> +++ b/include/linux/trace_recursion.h
> @@ -153,6 +153,17 @@ static __always_inline int trace_test_and_set_recursion(unsigned long ip, unsign
> current->trace_recursion = val;
> barrier();
>
> + /*
> + * Callbacks reached from static trampoline text (return_to_handler,
> + * the rethook and kretprobe trampolines) do not maintain
> + * current->rcu_tramp_nesting themselves; they rely on this
> + * preempt_disable() to keep the task from being preempted, and thus
> + * from reporting a Tasks RCU quiescent state, while an ftrace_ops or
> + * its data is in use. If the preempt_disable() is ever removed from
> + * the recursion protection, this must rcu_tasks_trampoline_enter()
> + * here and rcu_tasks_trampoline_exit() in trace_clear_recursion()
> + * instead. See CONFIG_RCU_TASKS_PREEMPT_QS.
> + */
> preempt_disable_notrace();
>
> return bit;
> diff --git a/kernel/fork.c b/kernel/fork.c
> index 416758c8a3d4..cfe3a8e53fbd 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -1869,6 +1869,7 @@ static inline void rcu_copy_process(struct task_struct *p)
> #endif /* #ifdef CONFIG_PREEMPT_RCU */
> #ifdef CONFIG_TASKS_RCU
> p->rcu_tasks_holdout = false;
> + p->rcu_tramp_nesting = 0;
> INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
> p->rcu_tasks_idle_cpu = -1;
> INIT_LIST_HEAD(&p->rcu_tasks_exit_list);
> diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
> index 627295396cd9..1662ba18bf34 100644
> --- a/kernel/rcu/tasks.h
> +++ b/kernel/rcu/tasks.h
> @@ -1113,10 +1113,11 @@ static void check_holdout_task(struct task_struct *t,
> *firstreport = false;
> }
> cpu = task_cpu(t);
> - pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d idle_cpu: %d/%d\n",
> + pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d tramp_nesting: %d idle_cpu: %d/%d\n",
> t, ".I"[is_idle_task(t)],
> "N."[cpu < 0 || !tick_nohz_full_cpu(cpu)],
> t->rcu_tasks_nvcsw, t->nvcsw, t->rcu_tasks_holdout,
> + data_race(t->rcu_tramp_nesting),
> data_race(t->rcu_tasks_idle_cpu), cpu);
> sched_show_task(t);
> }
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 15/15] rcu-tasks: Kick running holdouts through the scheduler
2026-09-11 14:08 ` [PATCH RFC v2 15/15] rcu-tasks: Kick running holdouts through the scheduler Josef Bacik
@ 2026-09-11 18:46 ` Paul E. McKenney
0 siblings, 0 replies; 26+ messages in thread
From: Paul E. McKenney @ 2026-09-11 18:46 UTC (permalink / raw)
To: Josef Bacik
Cc: Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes, Boqun Feng,
Thomas Gleixner, Peter Zijlstra, Steven Rostedt, Masami Hiramatsu,
Mark Rutland, Jiri Olsa, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, x86, Catalin Marinas, Will Deacon,
Puranjay Mohan, Xu Kuohai, Andy Lutomirski, Josh Triplett,
Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Juergen Gross, Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel
On Fri, Sep 11, 2026 at 02:08:53PM +0000, Josef Bacik wrote:
> A holdout that is running on a CPU with nothing else runnable is only
> preempted if the tick acts on rcu_request_urgent_qs_task()'s flag, and
> there may be no tick. Now that a preemption outside a trampoline is a
> quiescent state, have check_holdout_task() call resched_cpu() on a
> running holdout as well, so the scheduler IPIs it and it goes through
> __schedule() and reports (or, if it is inside a trampoline, does not
> report) its own state with purely local ordering. Nothing reads a
> running task's rcu_tramp_nesting remotely.
>
> Only under CONFIG_RCU_TASKS_PREEMPT_QS; other architectures are
> unchanged.
>
> Suggested-by: Paul E. McKenney <paulmck@kernel.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Much better! But please see below.
Thanx, Paul
> ---
> kernel/rcu/tasks.h | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
> index ba432bd922e2..02d2592ab7a3 100644
> --- a/kernel/rcu/tasks.h
> +++ b/kernel/rcu/tasks.h
> @@ -1038,8 +1038,18 @@ static bool rcu_tasks_preempted_qs(struct task_struct *t)
> {
> return task_call_func(t, rcu_tasks_switched_out_clean, NULL);
> }
> +
> +/* Make a running holdout pass through __schedule() soon, tick or no tick. */
> +static void rcu_tasks_kick_running(struct task_struct *t)
> +{
> + int cpu = task_cpu(t);
> +
> + if (task_curr(t) && cpu_online(cpu))
> + resched_cpu(cpu);
> +}
> #else
> static bool rcu_tasks_preempted_qs(struct task_struct *t) { return false; }
> +static void rcu_tasks_kick_running(struct task_struct *t) { }
> #endif
>
> /* Per-task initial processing. */
> @@ -1219,6 +1229,7 @@ static void check_holdout_task(struct task_struct *t,
> return;
> }
> rcu_request_urgent_qs_task(t);
> + rcu_tasks_kick_running(t);
Something I learned the hard way, though Google paid most of the tuition
for this lesson: There can be a *lot* of tasks on large systems, as
in hundreds of thousands of them. If these tasks are consuming CPU in
very short bursts, we could easily repeatedly invoke resched_cpu() on
the same CPU, and all invocations other than the last one are redundant.
I instead suggest doing something similar to force_qs_rnp(), where a
cpumask is accumulated and at the end resched_cpu() is invoked for each
CPU with a bit set in that mask.
Of course, if a CPU appears twice while traversing the tasks list, then
each of those tasks did a context switch, which would have reported a
Tasks RCU quiescent state. Unless those tasks happened to have non-zero
->rcu_tramp_nesting at that time.
Which suggests that each task have a pair of counters. Or that
rcu_tasks_trampoline_exit() should check for zero ->rcu_tramp_nesting,
and report a quiescent state at that point.
Either way, if a CPU appears only once in the tasks_list traversal,
hitting it with resched_cpu() makes sense. Though we might also
need to suppress calls to resched_cpu() for grace periods that are
(say) less than one second old.
Thoughts?
Thanx, Paul
> if (!needreport)
> return;
> if (*firstreport) {
>
> --
> 2.55.0
>
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-11 14:08 ` [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
@ 2026-09-12 3:27 ` Alexei Starovoitov
2026-09-12 5:10 ` Paul E. McKenney
0 siblings, 1 reply; 26+ messages in thread
From: Alexei Starovoitov @ 2026-09-12 3:27 UTC (permalink / raw)
To: Josef Bacik, Paul E. McKenney, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Boqun Feng, Thomas Gleixner,
Peter Zijlstra, Steven Rostedt, Masami Hiramatsu, Mark Rutland,
Jiri Olsa, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
x86, Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai
Cc: Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel
On Fri Sep 11, 2026 at 7:08 AM PDT, Josef Bacik wrote:
> Emit an increment of current->rcu_tramp_nesting once the trampoline's
> frame is set up and a decrement before the final register restore, so
> that a task preempted while running fentry/fexit/fmod_ret/LSM programs
> or the __bpf_tramp_enter()/__bpf_tramp_exit() glue is not treated as
> Tasks-RCU quiescent. Drop the count around the call to the original
> function: that may run arbitrarily long without sleeping and must not pin
> a Tasks RCU grace period, and the trampoline frame above it is held by
> im->pcref rather than by Tasks RCU (see bpf_tramp_image_put()). The
> fmod_ret early-exit branch and the ip_after_call -> ip_epilogue poke both
> skip the decrement/increment pair around the original call, so the count
> stays balanced on every path.
>
> The sequence is "mov r11, gs:[current_task]; inc/dec dword [r11 + off]";
> r11 is scratch at every emission point and (u32)¤t_task is a valid
> sign-extended %gs-absolute with the current per-CPU layout, the same form
> the JIT already uses for this_cpu_off. The image is dynamically
> allocated text, so the instructions outside the bracketed region are
> covered by the irq-exit IP check.
>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> ---
> arch/x86/net/bpf_jit_comp.c | 43 +++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 43 insertions(+)
>
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index 2853e87797a7..a375c1b7bd50 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
> @@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
> *pprog = prog;
> }
>
> +/*
> + * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
> + *
> + * mov r11, QWORD PTR gs:[current_task]
> + * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
> + *
> + * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
> + */
> +static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
> +{
> +#ifdef CONFIG_TASKS_RCU
> + u8 *prog = *pprog;
> +
> + /* mov r11, gs:[abs32] */
> + EMIT2(0x65, 0x4C);
> + EMIT3(0x8B, 0x1C, 0x25);
> + EMIT((u32)(unsigned long)¤t_task, 4);
> + /* inc/dec dword ptr [r11 + disp32] */
> + EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
> + EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
> +
> + *pprog = prog;
> +#endif
It's not a lot of overhead, but I feel it will be the death by thousand cuts.
rcu_read_lock_trace() in bpf_prog_enter_sleepable is doing the same thing...
increamenting a variable inside current.
Can they be combined? Like treat current->trc_reader_nesting > 0 as
current->rcu_tramp_nesting > 0 ?
Or replace one with the other?
Two current->foo++ operations look redundant.
bpf trampoline is already quite heavy. I'd like to find ways to reduce
its overhead instead of adding more.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 3:27 ` Alexei Starovoitov
@ 2026-09-12 5:10 ` Paul E. McKenney
2026-09-12 17:18 ` Alexei Starovoitov
0 siblings, 1 reply; 26+ messages in thread
From: Paul E. McKenney @ 2026-09-12 5:10 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Josef Bacik, Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Boqun Feng, Thomas Gleixner, Peter Zijlstra, Steven Rostedt,
Masami Hiramatsu, Mark Rutland, Jiri Olsa, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, x86, Catalin Marinas,
Will Deacon, Puranjay Mohan, Xu Kuohai, Andy Lutomirski,
Josh Triplett, Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan,
Zqiang, Juergen Gross, Luis Chamberlain, Ihor Solodrai,
linux-kernel, rcu, linux-trace-kernel, bpf, linux-arm-kernel,
xen-devel
On Fri, Sep 11, 2026 at 08:27:56PM -0700, Alexei Starovoitov wrote:
> On Fri Sep 11, 2026 at 7:08 AM PDT, Josef Bacik wrote:
> > Emit an increment of current->rcu_tramp_nesting once the trampoline's
> > frame is set up and a decrement before the final register restore, so
> > that a task preempted while running fentry/fexit/fmod_ret/LSM programs
> > or the __bpf_tramp_enter()/__bpf_tramp_exit() glue is not treated as
> > Tasks-RCU quiescent. Drop the count around the call to the original
> > function: that may run arbitrarily long without sleeping and must not pin
> > a Tasks RCU grace period, and the trampoline frame above it is held by
> > im->pcref rather than by Tasks RCU (see bpf_tramp_image_put()). The
> > fmod_ret early-exit branch and the ip_after_call -> ip_epilogue poke both
> > skip the decrement/increment pair around the original call, so the count
> > stays balanced on every path.
> >
> > The sequence is "mov r11, gs:[current_task]; inc/dec dword [r11 + off]";
> > r11 is scratch at every emission point and (u32)¤t_task is a valid
> > sign-extended %gs-absolute with the current per-CPU layout, the same form
> > the JIT already uses for this_cpu_off. The image is dynamically
> > allocated text, so the instructions outside the bracketed region are
> > covered by the irq-exit IP check.
> >
> > Assisted-by: LLM
> > Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> > ---
> > arch/x86/net/bpf_jit_comp.c | 43 +++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 43 insertions(+)
> >
> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > index 2853e87797a7..a375c1b7bd50 100644
> > --- a/arch/x86/net/bpf_jit_comp.c
> > +++ b/arch/x86/net/bpf_jit_comp.c
> > @@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
> > *pprog = prog;
> > }
> >
> > +/*
> > + * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
> > + *
> > + * mov r11, QWORD PTR gs:[current_task]
> > + * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
> > + *
> > + * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
> > + */
> > +static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
> > +{
> > +#ifdef CONFIG_TASKS_RCU
> > + u8 *prog = *pprog;
> > +
> > + /* mov r11, gs:[abs32] */
> > + EMIT2(0x65, 0x4C);
> > + EMIT3(0x8B, 0x1C, 0x25);
> > + EMIT((u32)(unsigned long)¤t_task, 4);
> > + /* inc/dec dword ptr [r11 + disp32] */
> > + EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
> > + EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
> > +
> > + *pprog = prog;
> > +#endif
>
> It's not a lot of overhead, but I feel it will be the death by thousand cuts.
> rcu_read_lock_trace() in bpf_prog_enter_sleepable is doing the same thing...
> increamenting a variable inside current.
> Can they be combined? Like treat current->trc_reader_nesting > 0 as
> current->rcu_tramp_nesting > 0 ?
> Or replace one with the other?
> Two current->foo++ operations look redundant.
>
> bpf trampoline is already quite heavy. I'd like to find ways to reduce
> its overhead instead of adding more.
Replace rcu_read_lock_trace() with Josef's rcu_tasks_trampoline_enter)?
But if this means reverting the re-implementation of RCU Tasks Trace in
terms of SRCU, I will be rather annoyed with myself. ;-)
Thanx, Paul
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 5:10 ` Paul E. McKenney
@ 2026-09-12 17:18 ` Alexei Starovoitov
2026-09-12 18:03 ` Paul E. McKenney
0 siblings, 1 reply; 26+ messages in thread
From: Alexei Starovoitov @ 2026-09-12 17:18 UTC (permalink / raw)
To: paulmck
Cc: Josef Bacik, Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Boqun Feng, Thomas Gleixner, Peter Zijlstra, Steven Rostedt,
Masami Hiramatsu, Mark Rutland, Jiri Olsa, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, x86, Catalin Marinas,
Will Deacon, Puranjay Mohan, Xu Kuohai, Andy Lutomirski,
Josh Triplett, Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan,
Zqiang, Juergen Gross, Luis Chamberlain, Ihor Solodrai,
linux-kernel, rcu, linux-trace-kernel, bpf, linux-arm-kernel,
xen-devel
On Fri Sep 11, 2026 at 10:10 PM PDT, Paul E. McKenney wrote:
> On Fri, Sep 11, 2026 at 08:27:56PM -0700, Alexei Starovoitov wrote:
>> On Fri Sep 11, 2026 at 7:08 AM PDT, Josef Bacik wrote:
>> > Emit an increment of current->rcu_tramp_nesting once the trampoline's
>> > frame is set up and a decrement before the final register restore, so
>> > that a task preempted while running fentry/fexit/fmod_ret/LSM programs
>> > or the __bpf_tramp_enter()/__bpf_tramp_exit() glue is not treated as
>> > Tasks-RCU quiescent. Drop the count around the call to the original
>> > function: that may run arbitrarily long without sleeping and must not pin
>> > a Tasks RCU grace period, and the trampoline frame above it is held by
>> > im->pcref rather than by Tasks RCU (see bpf_tramp_image_put()). The
>> > fmod_ret early-exit branch and the ip_after_call -> ip_epilogue poke both
>> > skip the decrement/increment pair around the original call, so the count
>> > stays balanced on every path.
>> >
>> > The sequence is "mov r11, gs:[current_task]; inc/dec dword [r11 + off]";
>> > r11 is scratch at every emission point and (u32)¤t_task is a valid
>> > sign-extended %gs-absolute with the current per-CPU layout, the same form
>> > the JIT already uses for this_cpu_off. The image is dynamically
>> > allocated text, so the instructions outside the bracketed region are
>> > covered by the irq-exit IP check.
>> >
>> > Assisted-by: LLM
>> > Signed-off-by: Josef Bacik <josef@toxicpanda.com>
>> > ---
>> > arch/x86/net/bpf_jit_comp.c | 43 +++++++++++++++++++++++++++++++++++++++++++
>> > 1 file changed, 43 insertions(+)
>> >
>> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
>> > index 2853e87797a7..a375c1b7bd50 100644
>> > --- a/arch/x86/net/bpf_jit_comp.c
>> > +++ b/arch/x86/net/bpf_jit_comp.c
>> > @@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
>> > *pprog = prog;
>> > }
>> >
>> > +/*
>> > + * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
>> > + *
>> > + * mov r11, QWORD PTR gs:[current_task]
>> > + * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
>> > + *
>> > + * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
>> > + */
>> > +static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
>> > +{
>> > +#ifdef CONFIG_TASKS_RCU
>> > + u8 *prog = *pprog;
>> > +
>> > + /* mov r11, gs:[abs32] */
>> > + EMIT2(0x65, 0x4C);
>> > + EMIT3(0x8B, 0x1C, 0x25);
>> > + EMIT((u32)(unsigned long)¤t_task, 4);
>> > + /* inc/dec dword ptr [r11 + disp32] */
>> > + EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
>> > + EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
>> > +
>> > + *pprog = prog;
>> > +#endif
>>
>> It's not a lot of overhead, but I feel it will be the death by thousand cuts.
>> rcu_read_lock_trace() in bpf_prog_enter_sleepable is doing the same thing...
>> increamenting a variable inside current.
>> Can they be combined? Like treat current->trc_reader_nesting > 0 as
>> current->rcu_tramp_nesting > 0 ?
>> Or replace one with the other?
>> Two current->foo++ operations look redundant.
>>
>> bpf trampoline is already quite heavy. I'd like to find ways to reduce
>> its overhead instead of adding more.
>
> Replace rcu_read_lock_trace() with Josef's rcu_tasks_trampoline_enter)?
If necessary...
what I don't understand why we need another rcu_tasks_trampoline_enter-like counter.
Can existing rcu_read_lock_trace() current be used ?
It's already doing current->trc_reader_nesting++
so use that as a signal ?
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 17:18 ` Alexei Starovoitov
@ 2026-09-12 18:03 ` Paul E. McKenney
2026-09-12 19:40 ` Alexei Starovoitov
2026-09-12 21:14 ` David Laight
0 siblings, 2 replies; 26+ messages in thread
From: Paul E. McKenney @ 2026-09-12 18:03 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Josef Bacik, Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Boqun Feng, Thomas Gleixner, Peter Zijlstra, Steven Rostedt,
Masami Hiramatsu, Mark Rutland, Jiri Olsa, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, x86, Catalin Marinas,
Will Deacon, Puranjay Mohan, Xu Kuohai, Andy Lutomirski,
Josh Triplett, Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan,
Zqiang, Juergen Gross, Luis Chamberlain, Ihor Solodrai,
linux-kernel, rcu, linux-trace-kernel, bpf, linux-arm-kernel,
xen-devel
On Sat, Sep 12, 2026 at 10:18:48AM -0700, Alexei Starovoitov wrote:
> On Fri Sep 11, 2026 at 10:10 PM PDT, Paul E. McKenney wrote:
> > On Fri, Sep 11, 2026 at 08:27:56PM -0700, Alexei Starovoitov wrote:
> >> On Fri Sep 11, 2026 at 7:08 AM PDT, Josef Bacik wrote:
> >> > Emit an increment of current->rcu_tramp_nesting once the trampoline's
> >> > frame is set up and a decrement before the final register restore, so
> >> > that a task preempted while running fentry/fexit/fmod_ret/LSM programs
> >> > or the __bpf_tramp_enter()/__bpf_tramp_exit() glue is not treated as
> >> > Tasks-RCU quiescent. Drop the count around the call to the original
> >> > function: that may run arbitrarily long without sleeping and must not pin
> >> > a Tasks RCU grace period, and the trampoline frame above it is held by
> >> > im->pcref rather than by Tasks RCU (see bpf_tramp_image_put()). The
> >> > fmod_ret early-exit branch and the ip_after_call -> ip_epilogue poke both
> >> > skip the decrement/increment pair around the original call, so the count
> >> > stays balanced on every path.
> >> >
> >> > The sequence is "mov r11, gs:[current_task]; inc/dec dword [r11 + off]";
> >> > r11 is scratch at every emission point and (u32)¤t_task is a valid
> >> > sign-extended %gs-absolute with the current per-CPU layout, the same form
> >> > the JIT already uses for this_cpu_off. The image is dynamically
> >> > allocated text, so the instructions outside the bracketed region are
> >> > covered by the irq-exit IP check.
> >> >
> >> > Assisted-by: LLM
> >> > Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> >> > ---
> >> > arch/x86/net/bpf_jit_comp.c | 43 +++++++++++++++++++++++++++++++++++++++++++
> >> > 1 file changed, 43 insertions(+)
> >> >
> >> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> >> > index 2853e87797a7..a375c1b7bd50 100644
> >> > --- a/arch/x86/net/bpf_jit_comp.c
> >> > +++ b/arch/x86/net/bpf_jit_comp.c
> >> > @@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
> >> > *pprog = prog;
> >> > }
> >> >
> >> > +/*
> >> > + * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
> >> > + *
> >> > + * mov r11, QWORD PTR gs:[current_task]
> >> > + * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
> >> > + *
> >> > + * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
> >> > + */
> >> > +static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
> >> > +{
> >> > +#ifdef CONFIG_TASKS_RCU
> >> > + u8 *prog = *pprog;
> >> > +
> >> > + /* mov r11, gs:[abs32] */
> >> > + EMIT2(0x65, 0x4C);
> >> > + EMIT3(0x8B, 0x1C, 0x25);
> >> > + EMIT((u32)(unsigned long)¤t_task, 4);
> >> > + /* inc/dec dword ptr [r11 + disp32] */
> >> > + EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
> >> > + EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
> >> > +
> >> > + *pprog = prog;
> >> > +#endif
> >>
> >> It's not a lot of overhead, but I feel it will be the death by thousand cuts.
> >> rcu_read_lock_trace() in bpf_prog_enter_sleepable is doing the same thing...
> >> increamenting a variable inside current.
> >> Can they be combined? Like treat current->trc_reader_nesting > 0 as
> >> current->rcu_tramp_nesting > 0 ?
> >> Or replace one with the other?
> >> Two current->foo++ operations look redundant.
> >>
> >> bpf trampoline is already quite heavy. I'd like to find ways to reduce
> >> its overhead instead of adding more.
> >
> > Replace rcu_read_lock_trace() with Josef's rcu_tasks_trampoline_enter)?
>
> If necessary...
> what I don't understand why we need another rcu_tasks_trampoline_enter-like counter.
> Can existing rcu_read_lock_trace() current be used ?
> It's already doing current->trc_reader_nesting++
> so use that as a signal ?
In the old kernels, yes, we have current->trc_reader_nesting++.
In the newer kernels, Tasks Trace RCU is instead implemented in terms
of SRCU-fast, which instead increments per-CPU counters. Which among
other thins is a bit faster and does not need to hook into the scheduler.
So we have several ways forward:
1. Revert the implementation of RCU Tasks Trace in terms of
SRCU-fast, and use the existing current->trc_reader_nesting++,
as you suggest.
2. Deprecate RCU Tasks Trace entirely in favor of RCU Tasks
augmented by rcu_tasks_trampoline_enter() and friends, as
I was suggesting.
3. Implement rcu_tasks_trampoline_enter() in terms of SRCU-fast,
keeping the speedup, and put a synchronize_srcu() in the
RCU Tasks grace-period mechanism. This again deprecates
RCU Tasks Trace entirely in favor of the augmented RCU Tasks.
4. It is always good to explicitly state the apparent status quo,
which involves redundant trampoline entry/exit overhead.
5. As always, your additional ideas here!
Thanx, Paul
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 18:03 ` Paul E. McKenney
@ 2026-09-12 19:40 ` Alexei Starovoitov
2026-09-12 22:28 ` Paul E. McKenney
2026-09-12 21:14 ` David Laight
1 sibling, 1 reply; 26+ messages in thread
From: Alexei Starovoitov @ 2026-09-12 19:40 UTC (permalink / raw)
To: paulmck
Cc: Josef Bacik, Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Boqun Feng, Thomas Gleixner, Peter Zijlstra, Steven Rostedt,
Masami Hiramatsu, Mark Rutland, Jiri Olsa, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, x86, Catalin Marinas,
Will Deacon, Puranjay Mohan, Xu Kuohai, Andy Lutomirski,
Josh Triplett, Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan,
Zqiang, Juergen Gross, Luis Chamberlain, Ihor Solodrai,
linux-kernel, rcu, linux-trace-kernel, bpf, linux-arm-kernel,
xen-devel
On Sat Sep 12, 2026 at 11:03 AM PDT, Paul E. McKenney wrote:
>
> In the old kernels, yes, we have current->trc_reader_nesting++.
> In the newer kernels, Tasks Trace RCU is instead implemented in terms
> of SRCU-fast, which instead increments per-CPU counters. Which among
> other thins is a bit faster and does not need to hook into the scheduler.
old kernels? I'm confused.
rcu_read_lock_trace() in bpf-next is doing t->trc_reader_nesting++
and then calls __srcu_read_lock_fast().
Are you talking about some RCU branch that you target for next merge window?
>
> So we have several ways forward:
>
> 1. Revert the implementation of RCU Tasks Trace in terms of
> SRCU-fast, and use the existing current->trc_reader_nesting++,
> as you suggest.
>
> 2. Deprecate RCU Tasks Trace entirely in favor of RCU Tasks
> augmented by rcu_tasks_trampoline_enter() and friends, as
> I was suggesting.
>
> 3. Implement rcu_tasks_trampoline_enter() in terms of SRCU-fast,
> keeping the speedup, and put a synchronize_srcu() in the
> RCU Tasks grace-period mechanism. This again deprecates
> RCU Tasks Trace entirely in favor of the augmented RCU Tasks.
>
> 4. It is always good to explicitly state the apparent status quo,
> which involves redundant trampoline entry/exit overhead.
>
> 5. As always, your additional ideas here!
>
> Thanx, Paul
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 18:03 ` Paul E. McKenney
2026-09-12 19:40 ` Alexei Starovoitov
@ 2026-09-12 21:14 ` David Laight
2026-09-12 22:31 ` Paul E. McKenney
1 sibling, 1 reply; 26+ messages in thread
From: David Laight @ 2026-09-12 21:14 UTC (permalink / raw)
To: Paul E. McKenney
Cc: Alexei Starovoitov, Josef Bacik, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Boqun Feng, Thomas Gleixner,
Peter Zijlstra, Steven Rostedt, Masami Hiramatsu, Mark Rutland,
Jiri Olsa, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
x86, Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai,
Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel
On Sat, 12 Sep 2026 11:03:34 -0700
"Paul E. McKenney" <paulmck@kernel.org> wrote:
> In the old kernels, yes, we have current->trc_reader_nesting++.
> In the newer kernels, Tasks Trace RCU is instead implemented in terms
> of SRCU-fast, which instead increments per-CPU counters. Which among
> other thins is a bit faster and does not need to hook into the scheduler.
Isn't that rather architecture dependant?
It is fine on x86, but on arm incrementing a per-cpu variable is
significantly expensive.
David
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 19:40 ` Alexei Starovoitov
@ 2026-09-12 22:28 ` Paul E. McKenney
0 siblings, 0 replies; 26+ messages in thread
From: Paul E. McKenney @ 2026-09-12 22:28 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Josef Bacik, Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Boqun Feng, Thomas Gleixner, Peter Zijlstra, Steven Rostedt,
Masami Hiramatsu, Mark Rutland, Jiri Olsa, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, x86, Catalin Marinas,
Will Deacon, Puranjay Mohan, Xu Kuohai, Andy Lutomirski,
Josh Triplett, Uladzislau Rezki, Mathieu Desnoyers, Lai Jiangshan,
Zqiang, Juergen Gross, Luis Chamberlain, Ihor Solodrai,
linux-kernel, rcu, linux-trace-kernel, bpf, linux-arm-kernel,
xen-devel
On Sat, Sep 12, 2026 at 12:40:55PM -0700, Alexei Starovoitov wrote:
> On Sat Sep 12, 2026 at 11:03 AM PDT, Paul E. McKenney wrote:
> >
> > In the old kernels, yes, we have current->trc_reader_nesting++.
> > In the newer kernels, Tasks Trace RCU is instead implemented in terms
> > of SRCU-fast, which instead increments per-CPU counters. Which among
> > other thins is a bit faster and does not need to hook into the scheduler.
>
> old kernels? I'm confused.
> rcu_read_lock_trace() in bpf-next is doing t->trc_reader_nesting++
> and then calls __srcu_read_lock_fast().
>
> Are you talking about some RCU branch that you target for next merge window?
No, I was thinking of rcu_read_lock_tasks_trace(), forgetting that
rcu_read_lock_trace() is still used. (For good reason, just be clear.)
Your comments are quite correct for rcu_read_lock_trace().
Hmmm... Josep's using t->trc_reader_nesting would break for
partially overlapping RCU Tasks and rcu_read_lock_trace() readers.
But yes, your #5 makes sense: Deprecate RCU Tasks, upgrade RCU Tasks
Trace to check for preemption from within trampolines, and move RCU
Tasks users over to the rcu_read_lock_trace() variant of RCU Tasks Trace.
(Or am I still missing your point?)
Josef, thoughts?
Thanx, Paul
> > So we have several ways forward:
> >
> > 1. Revert the implementation of RCU Tasks Trace in terms of
> > SRCU-fast, and use the existing current->trc_reader_nesting++,
> > as you suggest.
> >
> > 2. Deprecate RCU Tasks Trace entirely in favor of RCU Tasks
> > augmented by rcu_tasks_trampoline_enter() and friends, as
> > I was suggesting.
> >
> > 3. Implement rcu_tasks_trampoline_enter() in terms of SRCU-fast,
> > keeping the speedup, and put a synchronize_srcu() in the
> > RCU Tasks grace-period mechanism. This again deprecates
> > RCU Tasks Trace entirely in favor of the augmented RCU Tasks.
> >
> > 4. It is always good to explicitly state the apparent status quo,
> > which involves redundant trampoline entry/exit overhead.
> >
> > 5. As always, your additional ideas here!
> >
> > Thanx, Paul
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline
2026-09-12 21:14 ` David Laight
@ 2026-09-12 22:31 ` Paul E. McKenney
0 siblings, 0 replies; 26+ messages in thread
From: Paul E. McKenney @ 2026-09-12 22:31 UTC (permalink / raw)
To: David Laight
Cc: Alexei Starovoitov, Josef Bacik, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Boqun Feng, Thomas Gleixner,
Peter Zijlstra, Steven Rostedt, Masami Hiramatsu, Mark Rutland,
Jiri Olsa, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
x86, Catalin Marinas, Will Deacon, Puranjay Mohan, Xu Kuohai,
Andy Lutomirski, Josh Triplett, Uladzislau Rezki,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Juergen Gross,
Luis Chamberlain, Ihor Solodrai, linux-kernel, rcu,
linux-trace-kernel, bpf, linux-arm-kernel, xen-devel
On Sat, Sep 12, 2026 at 10:14:00PM +0100, David Laight wrote:
> On Sat, 12 Sep 2026 11:03:34 -0700
> "Paul E. McKenney" <paulmck@kernel.org> wrote:
>
> > In the old kernels, yes, we have current->trc_reader_nesting++.
> > In the newer kernels, Tasks Trace RCU is instead implemented in terms
> > of SRCU-fast, which instead increments per-CPU counters. Which among
> > other thins is a bit faster and does not need to hook into the scheduler.
>
> Isn't that rather architecture dependant?
> It is fine on x86, but on arm incrementing a per-cpu variable is
> significantly expensive.
Last I heard, slow ARM increments of per-CPU variables were to be a
transitory phenomemon. Plus changes late last year greatly sped up the
per-CPU increment operations. Plus this change removed some hundreds
of lines of RCU code.
Thanx, Paul
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2026-09-12 22:31 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-11 14:08 [PATCH RFC v2 00/15] rcu-tasks: let preemption outside trampolines be a quiescent state Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 01/15] rcu-tasks: Add per-task trampoline nesting count Josef Bacik
2026-09-11 17:23 ` Paul E. McKenney
2026-09-11 14:08 ` [PATCH RFC v2 02/15] entry: Pass pt_regs to irqentry_exit_cond_resched() Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 03/15] rcu-tasks: Hold trampoline nesting across irq-exit preemption in trampoline text Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 04/15] kprobes: Let Tasks RCU recognise tasks preempted in an optprobe jump window Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 05/15] ftrace: Mark modules hosting direct-call trampolines for Tasks RCU Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 06/15] x86/ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 07/15] x86/kprobes: Maintain Tasks RCU trampoline nesting in the optprobe template Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 08/15] bpf, x86: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
2026-09-12 3:27 ` Alexei Starovoitov
2026-09-12 5:10 ` Paul E. McKenney
2026-09-12 17:18 ` Alexei Starovoitov
2026-09-12 18:03 ` Paul E. McKenney
2026-09-12 19:40 ` Alexei Starovoitov
2026-09-12 22:28 ` Paul E. McKenney
2026-09-12 21:14 ` David Laight
2026-09-12 22:31 ` Paul E. McKenney
2026-09-11 14:08 ` [PATCH RFC v2 09/15] arm64: ftrace: Maintain Tasks RCU trampoline nesting in ftrace_caller Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 10/15] bpf, arm64: Maintain Tasks RCU trampoline nesting in the BPF trampoline Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 11/15] samples: ftrace: Maintain Tasks RCU trampoline nesting in direct-call trampolines Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 12/15] rcutorture: Bracket Tasks RCU readers with trampoline nesting Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 13/15] rcu-tasks: Treat preemption outside trampolines as a quiescent state Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 14/15] rcu-tasks: Retire switched-out tasks with no trampoline nesting at scan time Josef Bacik
2026-09-11 14:08 ` [PATCH RFC v2 15/15] rcu-tasks: Kick running holdouts through the scheduler Josef Bacik
2026-09-11 18:46 ` Paul E. McKenney
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox