| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/31 01:14 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"KPROBES",
"FUNCTION_TRACER",
"TRACEPOINTS",
"PERF_EVENTS",
"RCU_TORTURE_TEST",
"BPF_SYSCALL"
],
"FocusSymbols": [
"rcu_barrier",
"srcu_barrier",
"rcutree_migrate_callbacks",
"srcu_offline_drain"
],
"Reasoning": "The patch introduces a deferral mechanism for call_rcu() and call_srcu() to safely handle callback enqueues when interrupts are disabled (e.g., from NMIs or BPF programs). It adds lockless lists and irq_work to defer the actual enqueue until it is safe. It also updates rcu_barrier(), srcu_barrier(), and CPU hotplug paths to flush these deferred callbacks. Additionally, it adds NMI testing to rcutorture using perf events, and a BPF selftest. This is a significant functional change to core RCU logic and should be fuzzed. Focus symbols include the barrier and CPU offline functions that drain the new deferred lists. Required configs include KPROBES/FUNCTION_TRACER/TRACEPOINTS to enable CONFIG_RCU_DEFER, PERF_EVENTS and RCU_TORTURE_TEST for the rcutorture changes, and BPF_SYSCALL as BPF is a primary trigger for this code.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/31 01:14 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 3ef36742b17beb0e7346b58f518ba7e86a766742\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Jul 31 01:14:32 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/srcutiny.h b/include/linux/srcutiny.h\nindex fbcf13bc12d15..47275d182966c 100644\n--- a/include/linux/srcutiny.h\n+++ b/include/linux/srcutiny.h\n@@ -12,6 +12,7 @@\n #define _LINUX_SRCU_TINY_H\n \n #include \u003clinux/irq_work_types.h\u003e\n+#include \u003clinux/llist.h\u003e\n #include \u003clinux/swait.h\u003e\n \n struct srcu_struct {\n@@ -26,6 +27,8 @@ struct srcu_struct {\n \tstruct rcu_head **srcu_cb_tail;\t/* Pending callbacks: Tail. */\n \tstruct work_struct srcu_work;\t/* For driving grace periods. */\n \tstruct irq_work srcu_irq_work;\t/* Defer schedule_work() to irq work. */\n+\tstruct llist_head defer_cbs;\t/* Callbacks deferred on re-entry. */\n+\tstruct irq_work defer_iw;\t\t/* Registers defer_cbs later. */\n #ifdef CONFIG_DEBUG_LOCK_ALLOC\n \tstruct lockdep_map dep_map;\n #endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */\n@@ -33,6 +36,7 @@ struct srcu_struct {\n \n void srcu_drive_gp(struct work_struct *wp);\n void srcu_tiny_irq_work(struct irq_work *irq_work);\n+void srcu_defer_drain(struct irq_work *irq_work);\n \n #define __SRCU_STRUCT_INIT(name, __ignored, ___ignored, ____ignored)\t\\\n {\t\t\t\t\t\t\t\t\t\\\n@@ -40,6 +44,8 @@ void srcu_tiny_irq_work(struct irq_work *irq_work);\n \t.srcu_cb_tail = \u0026name.srcu_cb_head,\t\t\t\t\\\n \t.srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp),\t\\\n \t.srcu_irq_work = { .func = srcu_tiny_irq_work },\t\t\\\n+\t.defer_cbs = LLIST_HEAD_INIT(name.defer_cbs),\t\t\t\\\n+\t.defer_iw = { .func = srcu_defer_drain },\t\t\t\t\\\n \t__SRCU_DEP_MAP_INIT(name)\t\t\t\t\t\\\n }\n \n@@ -131,10 +137,7 @@ static inline void synchronize_srcu_expedited(struct srcu_struct *ssp)\n \tsynchronize_srcu(ssp);\n }\n \n-static inline void srcu_barrier(struct srcu_struct *ssp)\n-{\n-\tsynchronize_srcu(ssp);\n-}\n+void srcu_barrier(struct srcu_struct *ssp);\n \n static inline void srcu_expedite_current(struct srcu_struct *ssp) { }\n #define srcu_check_read_flavor(ssp, read_flavor) do { } while (0)\ndiff --git a/include/linux/srcutree.h b/include/linux/srcutree.h\nindex 75e54e4f963fa..1ce759fb70948 100644\n--- a/include/linux/srcutree.h\n+++ b/include/linux/srcutree.h\n@@ -13,6 +13,8 @@\n \n #include \u003clinux/rcu_node_tree.h\u003e\n #include \u003clinux/completion.h\u003e\n+#include \u003clinux/irq_work_types.h\u003e\n+#include \u003clinux/llist.h\u003e\n \n struct srcu_node;\n struct srcu_struct;\n@@ -41,6 +43,8 @@ struct srcu_data {\n \tbool srcu_cblist_invoking;\t\t/* Invoking these CBs? */\n \tstruct timer_list delay_work;\t\t/* Delay for CB invoking */\n \tstruct work_struct work;\t\t/* Context for CB invoking. */\n+\tstruct llist_head defer_cbs;\t\t/* Callbacks deferred on re-entry. */\n+\tstruct llist_node defer_link;\t\t/* Links onto the per-CPU deferral drain list */\n \tstruct rcu_head srcu_barrier_head;\t/* For srcu_barrier() use. */\n \tstruct rcu_head srcu_ec_head;\t\t/* For srcu_expedite_current() use. */\n \tint srcu_ec_state;\t\t\t/* State for srcu_expedite_current(). */\ndiff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig\nindex 332df7a7a6347..bc9483c0b5d84 100644\n--- a/kernel/rcu/Kconfig\n+++ b/kernel/rcu/Kconfig\n@@ -175,6 +175,12 @@ config RCU_STALL_COMMON\n config RCU_NEED_SEGCBLIST\n \tdef_bool ( TREE_RCU || TREE_SRCU || TASKS_RCU_GENERIC )\n \n+# The deferral (and the IRQ_WORK it uses) is only needed where call_rcu() /\n+# call_srcu() can be invoked while a callback-list operation is in flight.\n+config RCU_DEFER\n+\tdef_bool HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS\n+\tselect IRQ_WORK\n+\n config RCU_FANOUT\n \tint \"Tree-based hierarchical RCU fanout value\"\n \trange 2 64 if 64BIT\ndiff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h\nindex 39a9f6fa9a7b2..036d28516e92d 100644\n--- a/kernel/rcu/rcu.h\n+++ b/kernel/rcu/rcu.h\n@@ -572,6 +572,24 @@ static inline void tasks_cblist_init_generic(void) { }\n #define RCU_SCHEDULER_INIT\t1\n #define RCU_SCHEDULER_RUNNING\t2\n \n+/*\n+ * Should a call_rcu()/call_srcu() callback be deferred rather than enqueued\n+ * now? Defer whenever interrupts are disabled: a callback-list operation may\n+ * be in flight on this CPU, so enqueuing now could corrupt it. But not while\n+ * the scheduler is down -- early boot is single-threaded and can call this\n+ * before init_IRQ() makes irq_work usable (e.g. rcu_init()'s self-tests).\n+ */\n+static inline bool should_rcu_defer(void)\n+{\n+\tif (!IS_ENABLED(CONFIG_RCU_DEFER))\n+\t\treturn false;\n+\n+\treturn irqs_disabled() \u0026\u0026 rcu_scheduler_active != RCU_SCHEDULER_INACTIVE;\n+}\n+\n+/* Drain an outgoing CPU's deferred SRCU callbacks; see rcutree_migrate_callbacks(). */\n+void srcu_offline_drain(int cpu);\n+\n enum rcutorture_type {\n \tRCU_FLAVOR,\n \tRCU_TASKS_FLAVOR,\ndiff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c\nindex 3d0b59a7b0b38..a06100b823597 100644\n--- a/kernel/rcu/rcutorture.c\n+++ b/kernel/rcu/rcutorture.c\n@@ -48,6 +48,7 @@\n #include \u003clinux/tick.h\u003e\n #include \u003clinux/rcupdate_trace.h\u003e\n #include \u003clinux/nmi.h\u003e\n+#include \u003clinux/perf_event.h\u003e\n \n #include \"rcu.h\"\n \n@@ -115,6 +116,7 @@ torture_param(int, leakpointer, 0, \"Leak pointer dereferences from readers\");\n torture_param(int, n_barrier_cbs, 0, \"# of callbacks/kthreads for barrier testing\");\n torture_param(int, n_up_down, 32, \"# of concurrent up/down hrtimer-based RCU readers\");\n torture_param(int, nfakewriters, 4, \"Number of RCU fake writer threads\");\n+torture_param(bool, nmi_calls, true, \"Exercise -\u003ecall() from NMI on nmi_capable flavors\");\n torture_param(int, nreaders, -1, \"Number of RCU reader threads\");\n torture_param(bool, nwriters, 1, \"Number of RCU writer threads (0 or 1)\");\n torture_param(int, object_debug, 0, \"Enable debug-object double call_rcu() testing\");\n@@ -216,6 +218,8 @@ static long n_rcu_torture_boost_failure;\n static long n_rcu_torture_boosts;\n static atomic_long_t n_rcu_torture_timers;\n static atomic_long_t n_rcu_torture_irqs;\n+static atomic_long_t n_rcu_torture_nmi_call;\n+static atomic_long_t n_rcu_torture_nmi_cb;\n static long n_barrier_attempts;\n static long n_barrier_successes; /* did rcu_barrier test succeed? */\n static unsigned long n_read_exits;\n@@ -433,6 +437,7 @@ struct rcu_torture_ops {\n \tbool (*is_task_rcu_boosted)(void);\n \tlong cbflood_max;\n \tint irq_capable;\n+\tint nmi_capable;\n \tint can_boost;\n \tint extendables;\n \tint slow_gps;\n@@ -646,6 +651,7 @@ static struct rcu_torture_ops rcu_ops = {\n \t.extendables\t\t= RCUTORTURE_MAX_EXTEND,\n \t.debug_objects\t\t= 1,\n \t.start_poll_irqsoff\t= 1,\n+\t.nmi_capable\t\t= 1,\n \t.name\t\t\t= \"rcu\"\n };\n \n@@ -936,6 +942,7 @@ static struct rcu_torture_ops srcu_ops = {\n \t.debug_objects\t= 1,\n \t.have_up_down\t= IS_ENABLED(CONFIG_TINY_SRCU)\n \t\t\t\t? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN,\n+\t.nmi_capable\t= 1,\n \t.name\t\t= \"srcu\"\n };\n \n@@ -999,6 +1006,7 @@ static struct rcu_torture_ops srcud_ops = {\n \t.debug_objects\t= 1,\n \t.have_up_down\t= IS_ENABLED(CONFIG_TINY_SRCU)\n \t\t\t\t? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN,\n+\t.nmi_capable\t= 1,\n \t.name\t\t= \"srcud\"\n };\n \n@@ -1263,6 +1271,7 @@ static struct rcu_torture_ops tasks_tracing_ops = {\n \t.cbflood_max\t= 50000,\n \t.irq_capable\t= 1,\n \t.slow_gps\t= 1,\n+\t.nmi_capable\t= 1,\n \t.name\t\t= \"tasks-tracing\"\n };\n \n@@ -2653,12 +2662,97 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)\n \n static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_timer_rand);\n \n+/*\n+ * Exercise -\u003ecall() from NMI context for flavors that advertise -\u003enmi_capable.\n+ * A per-CPU hardware perf counter overflows into an NMI -- a real NMI on x86,\n+ * or a pseudo-NMI on arm64 booted with irqchip.gicv3_pseudo_nmi=1 -- and its\n+ * handler submits one preallocated callback via -\u003ecall(). Only one callback is\n+ * in flight at a time (guarded by an atomic), which avoids allocating in NMI\n+ * and is enough to exercise the deferral. This mirrors how BPF programs reach\n+ * -\u003ecall() from NMI. Requires a hardware PMU; when the overflow is not an NMI\n+ * the handler does nothing.\n+ */\n+#ifdef CONFIG_PERF_EVENTS\n+static struct perf_event_attr rcu_torture_nmi_attr = {\n+\t.type\t\t= PERF_TYPE_HARDWARE,\n+\t.config\t\t= PERF_COUNT_HW_CPU_CYCLES,\n+\t.size\t\t= sizeof(struct perf_event_attr),\n+\t.pinned\t\t= 1,\n+\t.disabled\t= 1,\n+\t.freq\t\t= 1,\n+\t.sample_freq\t= 1000,\n+};\n+\n+static struct perf_event **rcu_torture_nmi_events;\n+static struct rcu_head rcu_torture_nmi_rh;\n+static atomic_t rcu_torture_nmi_rh_inuse;\n+\n+static void rcu_torture_nmi_cb(struct rcu_head *rhp)\n+{\n+\tatomic_long_inc(\u0026n_rcu_torture_nmi_cb);\n+\tatomic_set(\u0026rcu_torture_nmi_rh_inuse, 0);\n+}\n+\n+static void rcu_torture_nmi_overflow(struct perf_event *event,\n+\t\t\t\t struct perf_sample_data *data,\n+\t\t\t\t struct pt_regs *regs)\n+{\n+\tif (!in_nmi())\n+\t\treturn;\n+\tif (cur_ops-\u003ecall \u0026\u0026 !atomic_xchg(\u0026rcu_torture_nmi_rh_inuse, 1)) {\n+\t\tcur_ops-\u003ecall(\u0026rcu_torture_nmi_rh, rcu_torture_nmi_cb);\n+\t\tatomic_long_inc(\u0026n_rcu_torture_nmi_call);\n+\t}\n+}\n+\n+static void rcu_torture_nmi_init(void)\n+{\n+\tstruct perf_event *event;\n+\tint cpu;\n+\n+\tif (!nmi_calls || !cur_ops-\u003enmi_capable || !cur_ops-\u003ecall)\n+\t\treturn;\n+\trcu_torture_nmi_events = kcalloc(nr_cpu_ids, sizeof(*rcu_torture_nmi_events),\n+\t\t\t\t\t GFP_KERNEL);\n+\tif (!rcu_torture_nmi_events)\n+\t\treturn;\n+\tfor_each_online_cpu(cpu) {\n+\t\tevent = perf_event_create_kernel_counter(\u0026rcu_torture_nmi_attr, cpu,\n+\t\t\t\t\t\t\t NULL, rcu_torture_nmi_overflow, NULL);\n+\t\tif (IS_ERR(event))\n+\t\t\tcontinue;\n+\t\trcu_torture_nmi_events[cpu] = event;\n+\t\tperf_event_enable(event);\n+\t}\n+}\n+\n+static void rcu_torture_nmi_cleanup(void)\n+{\n+\tint cpu;\n+\n+\tif (!rcu_torture_nmi_events)\n+\t\treturn;\n+\tfor_each_possible_cpu(cpu) {\n+\t\tif (!rcu_torture_nmi_events[cpu])\n+\t\t\tcontinue;\n+\t\tperf_event_disable(rcu_torture_nmi_events[cpu]);\n+\t\tperf_event_release_kernel(rcu_torture_nmi_events[cpu]);\n+\t}\n+\tkfree(rcu_torture_nmi_events);\n+\trcu_torture_nmi_events = NULL;\n+}\n+#else /* #ifdef CONFIG_PERF_EVENTS */\n+static void rcu_torture_nmi_init(void) { }\n+static void rcu_torture_nmi_cleanup(void) { }\n+#endif /* #else #ifdef CONFIG_PERF_EVENTS */\n+\n /*\n * RCU torture reader from timer handler. Dereferences rcu_torture_current,\n * incrementing the corresponding element of the pipeline array. The\n * counter in the element should never be greater than 1, otherwise, the\n * RCU implementation is broken.\n */\n+\n static void rcu_torture_timer(struct timer_list *unused)\n {\n \tWARN_ON_ONCE(!in_serving_softirq());\n@@ -3041,6 +3135,9 @@ rcu_torture_stats_print(void)\n \t\tdata_race(n_barrier_attempts),\n \t\tdata_race(n_rcu_torture_barrier_error));\n \tpr_cont(\"read-exits: %ld \", data_race(n_read_exits)); // Statistic.\n+\tpr_cont(\"nmi-calls: %ld nmi-cbs: %ld \",\n+\t\tatomic_long_read(\u0026n_rcu_torture_nmi_call),\n+\t\tatomic_long_read(\u0026n_rcu_torture_nmi_cb));\n \tpr_cont(\"nocb-toggles: %ld:%ld \",\n \t\tatomic_long_read(\u0026n_nocb_offload), atomic_long_read(\u0026n_nocb_deoffload));\n \tpr_cont(\"gpwraps: %ld\\n\", n_gpwraps);\n@@ -4315,6 +4412,8 @@ rcu_torture_cleanup(void)\n \t\tkfree(reader_tasks);\n \t\treader_tasks = NULL;\n \t}\n+\t/* Disable the perf counters (and thus the NMI -\u003ecall() firing) now. */\n+\trcu_torture_nmi_cleanup();\n \tkfree(rcu_torture_reader_mbchk);\n \trcu_torture_reader_mbchk = NULL;\n \n@@ -4344,6 +4443,20 @@ rcu_torture_cleanup(void)\n \t\tpr_info(\"%s: Invoking %pS().\\n\", __func__, cur_ops-\u003ecb_barrier);\n \t\tcur_ops-\u003ecb_barrier();\n \t}\n+\n+\t/*\n+\t * cb_barrier() above drained every deferred NMI -\u003ecall() callback, so the\n+\t * count issued from NMI must equal the count invoked; a mismatch means a\n+\t * callback was lost.\n+\t */\n+\tif (atomic_long_read(\u0026n_rcu_torture_nmi_call) !=\n+\t atomic_long_read(\u0026n_rcu_torture_nmi_cb)) {\n+\t\tpr_alert(\"%s: NMI -\u003ecall() lost a callback: issued %ld invoked %ld\\n\",\n+\t\t\t __func__, atomic_long_read(\u0026n_rcu_torture_nmi_call),\n+\t\t\t atomic_long_read(\u0026n_rcu_torture_nmi_cb));\n+\t\tatomic_inc(\u0026n_rcu_torture_error);\n+\t}\n+\n \tif (cur_ops-\u003ecleanup != NULL)\n \t\tcur_ops-\u003ecleanup();\n \n@@ -4776,6 +4889,8 @@ rcu_torture_init(void)\n \t\tfirsterr = -ENOMEM;\n \t\tgoto unwind;\n \t}\n+\t/* Arm the per-CPU perf counters that drive -\u003ecall() from NMI. */\n+\trcu_torture_nmi_init();\n \tfor (i = 0; i \u003c nrealreaders; i++) {\n \t\trcu_torture_reader_mbchk[i].rtc_chkrdr = -1;\n \t\tfirsterr = torture_create_kthread(rcu_torture_reader, (void *)i,\ndiff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c\nindex 558ba8d316db6..03a07a1f3a30b 100644\n--- a/kernel/rcu/srcutiny.c\n+++ b/kernel/rcu/srcutiny.c\n@@ -10,6 +10,7 @@\n \n #include \u003clinux/export.h\u003e\n #include \u003clinux/irq_work.h\u003e\n+#include \u003clinux/llist.h\u003e\n #include \u003clinux/mutex.h\u003e\n #include \u003clinux/preempt.h\u003e\n #include \u003clinux/rcupdate_wait.h\u003e\n@@ -43,6 +44,8 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp)\n \tINIT_WORK(\u0026ssp-\u003esrcu_work, srcu_drive_gp);\n \tINIT_LIST_HEAD(\u0026ssp-\u003esrcu_work.entry);\n \tinit_irq_work(\u0026ssp-\u003esrcu_irq_work, srcu_tiny_irq_work);\n+\tinit_llist_head(\u0026ssp-\u003edefer_cbs);\n+\tinit_irq_work(\u0026ssp-\u003edefer_iw, srcu_defer_drain);\n \treturn 0;\n }\n \n@@ -213,11 +216,11 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp)\n }\n \n /*\n- * Enqueue an SRCU callback on the specified srcu_struct structure,\n- * initiating grace-period processing if it is not already running.\n+ * Enqueue @rhp on the callback list. Also called by srcu_defer_drain() to\n+ * re-issue a deferred callback, so it must not re-check the deferral condition.\n */\n-void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,\n-\t rcu_callback_t func)\n+static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,\n+\t\t\t rcu_callback_t func)\n {\n \tunsigned long flags;\n \n@@ -231,6 +234,39 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,\n \tsrcu_gp_start_if_needed(ssp);\n \tpreempt_enable();\n }\n+\n+void srcu_defer_drain(struct irq_work *iw)\n+{\n+\tstruct srcu_struct *ssp = container_of(iw, struct srcu_struct, defer_iw);\n+\tstruct llist_node *node, *next;\n+\n+\t/*\n+\t * Callbacks have no mutual ordering guarantee and srcu_barrier() has\n+\t * already flushed us, so drain in llist order without reversing.\n+\t */\n+\tllist_for_each_safe(node, next, llist_del_all(\u0026ssp-\u003edefer_cbs)) {\n+\t\tstruct rcu_head *rhp = (struct rcu_head *)node;\n+\n+\t\tsrcu_do_enqueue(ssp, rhp, rhp-\u003efunc);\n+\t}\n+}\n+EXPORT_SYMBOL_GPL(srcu_defer_drain);\n+\n+void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,\n+\t rcu_callback_t func)\n+{\n+\tif (should_rcu_defer()) {\n+\t\trhp-\u003efunc = func;\n+\t\tif (llist_add((struct llist_node *)rhp, \u0026ssp-\u003edefer_cbs))\n+\t\t\tirq_work_queue(\u0026ssp-\u003edefer_iw);\n+\t\treturn;\n+\t}\n+\n+\t/* An NMI reaching here entered with irqs enabled, so the enqueue can race. */\n+\tWARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) \u0026\u0026 in_nmi());\n+\n+\tsrcu_do_enqueue(ssp, rhp, func);\n+}\n EXPORT_SYMBOL_GPL(call_srcu);\n \n /*\n@@ -260,6 +296,15 @@ void synchronize_srcu(struct srcu_struct *ssp)\n }\n EXPORT_SYMBOL_GPL(synchronize_srcu);\n \n+/* Register any deferred callbacks, then wait for all in-flight ones. */\n+void srcu_barrier(struct srcu_struct *ssp)\n+{\n+\tif (IS_ENABLED(CONFIG_RCU_DEFER))\n+\t\tirq_work_sync(\u0026ssp-\u003edefer_iw);\n+\tsynchronize_srcu(ssp);\n+}\n+EXPORT_SYMBOL_GPL(srcu_barrier);\n+\n /*\n * get_state_synchronize_srcu - Provide an end-of-grace-period cookie\n */\ndiff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c\nindex 304112674e8a2..b9fe57ff91000 100644\n--- a/kernel/rcu/srcutree.c\n+++ b/kernel/rcu/srcutree.c\n@@ -20,6 +20,7 @@\n #include \u003clinux/percpu.h\u003e\n #include \u003clinux/preempt.h\u003e\n #include \u003clinux/irq_work.h\u003e\n+#include \u003clinux/llist.h\u003e\n #include \u003clinux/rcupdate_wait.h\u003e\n #include \u003clinux/sched.h\u003e\n #include \u003clinux/smp.h\u003e\n@@ -79,6 +80,46 @@ static void process_srcu(struct work_struct *work);\n static void srcu_irq_work(struct irq_work *work);\n static void srcu_delay_timer(struct timer_list *t);\n \n+static void srcu_defer_drain(struct irq_work *iw);\n+\n+/*\n+ * Per-CPU call_srcu() deferral state. The irq_work is global (shared by every\n+ * srcu_struct), so it must find which srcu_structs deferred callbacks: a\n+ * callback is staged on its srcu_data's -\u003edefer_cbs, and that srcu_data is\n+ * chained (-\u003edefer_link) onto this per-CPU -\u003elist that the irq_work walks.\n+ */\n+struct srcu_defer {\n+\tstruct llist_head\tlist;\n+\tstruct irq_work\t\tiw;\n+\traw_spinlock_t\t\tlock;\n+};\n+\n+static DEFINE_PER_CPU(struct srcu_defer, srcu_defer) = {\n+\t.lock = __RAW_SPIN_LOCK_UNLOCKED(srcu_defer.lock),\n+\t.iw = IRQ_WORK_INIT_HARD(srcu_defer_drain),\n+};\n+\n+/*\n+ * Register pending deferred call_srcu() callbacks so a following srcu_barrier()\n+ * waits for them. An online CPU's own irq_work re-issues its callbacks, so wait\n+ * it out; an offline CPU's irq_work may never run again, so drain its list\n+ * directly onto this CPU instead.\n+ */\n+static void srcu_defer_flush(void)\n+{\n+\tint cpu;\n+\n+\tif (!IS_ENABLED(CONFIG_RCU_DEFER))\n+\t\treturn;\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tif (cpu_online(cpu))\n+\t\t\tirq_work_sync(\u0026per_cpu(srcu_defer, cpu).iw);\n+\t\telse\n+\t\t\tsrcu_defer_drain(\u0026per_cpu(srcu_defer, cpu).iw);\n+\t}\n+}\n+\n /*\n * Initialize SRCU per-CPU data. Note that statically allocated\n * srcu_struct structures might already have srcu_read_lock() and\n@@ -107,6 +148,11 @@ static void init_srcu_struct_data(struct srcu_struct *ssp)\n \t\tsdp-\u003ecpu = cpu;\n \t\tINIT_WORK(\u0026sdp-\u003ework, srcu_invoke_callbacks);\n \t\ttimer_setup(\u0026sdp-\u003edelay_work, srcu_delay_timer, 0);\n+\t\t/*\n+\t\t * -\u003edefer_cbs and -\u003edefer_link are valid when zeroed and are not\n+\t\t * reinitialized here, lest we clobber callbacks a reentrant\n+\t\t * call_srcu() already staged. See __call_srcu().\n+\t\t */\n \t\tsdp-\u003essp = ssp;\n \t}\n }\n@@ -697,6 +743,8 @@ void cleanup_srcu_struct(struct srcu_struct *ssp)\n \t\treturn; /* Just leak it! */\n \t/* Wait for irq_work to finish first as it may queue a new work. */\n \tirq_work_sync(\u0026sup-\u003eirq_work);\n+\t/* Drain any deferred callbacks (shared irq_work) before freeing -\u003esda. */\n+\tsrcu_defer_flush();\n \tflush_delayed_work(\u0026sup-\u003ework);\n \tfor_each_possible_cpu(cpu) {\n \t\tstruct srcu_data *sdp = per_cpu_ptr(ssp-\u003esda, cpu);\n@@ -1410,8 +1458,18 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,\n * srcu_read_lock(), and srcu_read_unlock() that are all passed the same\n * srcu_struct structure.\n */\n-static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,\n-\t\t\trcu_callback_t func, bool do_norm)\n+/*\n+ * The srcu_cblist and the srcu_node tree are only ever accessed with interrupts\n+ * disabled (srcu_gp_start_if_needed() enqueues under raw_spin_lock_irqsave() and\n+ * may walk the tree; callback invocation and grace-period work likewise). So\n+ * like call_rcu(), enqueuing while a list operation is in flight on this CPU\n+ * would corrupt it or deadlock -- e.g. call_rcu_tasks_trace(), which is\n+ * call_srcu() on a dedicated srcu_struct, from a BPF program. __call_srcu()\n+ * defers whenever interrupts are disabled, via a per-CPU irq_work; only kernels\n+ * where that can happen (CONFIG_RCU_DEFER) need this.\n+ */\n+static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,\n+\t\t\t rcu_callback_t func, bool do_norm)\n {\n \tif (debug_rcu_head_queue(rhp)) {\n \t\t/* Probable double call_srcu(), so leak the callback. */\n@@ -1423,6 +1481,72 @@ static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,\n \t(void)srcu_gp_start_if_needed(ssp, rhp, do_norm);\n }\n \n+static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,\n+\t\t\trcu_callback_t func, bool do_norm)\n+{\n+\tif (should_rcu_defer()) {\n+\t\tstruct srcu_data *sdp = this_cpu_ptr(ssp-\u003esda);\n+\n+\t\trhp-\u003efunc = func;\n+\t\tif (llist_add((struct llist_node *)rhp, \u0026sdp-\u003edefer_cbs)) {\n+\t\t\t/* First deferral on this srcu_data: chain it for the drain. */\n+\t\t\tstruct srcu_defer *sndp = this_cpu_ptr(\u0026srcu_defer);\n+\n+\t\t\tsdp-\u003essp = ssp;\n+\t\t\tif (llist_add(\u0026sdp-\u003edefer_link, \u0026sndp-\u003elist))\n+\t\t\t\tirq_work_queue(\u0026sndp-\u003eiw);\n+\t\t}\n+\t\treturn;\n+\t}\n+\n+\t/* An NMI reaching here entered with irqs enabled, so the enqueue can race. */\n+\tWARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) \u0026\u0026 in_nmi());\n+\n+\tsrcu_do_enqueue(ssp, rhp, func, do_norm);\n+}\n+\n+/*\n+ * Re-issue this CPU's deferred callbacks, going straight to srcu_do_enqueue() so\n+ * they cannot defer again. The irq_work walks its -\u003elist of srcu_data, each\n+ * carrying its own -\u003edefer_cbs. -\u003elock is held across the drain so drainers\n+ * (irq_work, srcu_defer_flush() and srcu_offline_drain()) serialize and never\n+ * leave a callback off the lists yet not on a callback list.\n+ */\n+static void srcu_defer_drain(struct irq_work *iw)\n+{\n+\tstruct srcu_defer *sndp = container_of(iw, struct srcu_defer, iw);\n+\tstruct llist_node *snode, *snext;\n+\tunsigned long flags;\n+\n+\traw_spin_lock_irqsave(\u0026sndp-\u003elock, flags);\n+\tllist_for_each_safe(snode, snext, llist_del_all(\u0026sndp-\u003elist)) {\n+\t\tstruct srcu_data *sdp = container_of(snode, struct srcu_data, defer_link);\n+\t\tstruct srcu_struct *ssp = sdp-\u003essp;\n+\t\tstruct llist_node *cnode, *cnext;\n+\n+\t\tcnode = llist_del_all(\u0026sdp-\u003edefer_cbs);\n+\t\tllist_for_each_safe(cnode, cnext, cnode) {\n+\t\t\tstruct rcu_head *rhp = (struct rcu_head *)cnode;\n+\n+\t\t\tsrcu_do_enqueue(ssp, rhp, rhp-\u003efunc, true);\n+\t\t}\n+\t}\n+\traw_spin_unlock_irqrestore(\u0026sndp-\u003elock, flags);\n+}\n+\n+/*\n+ * Drain @cpu's deferred call_srcu() callbacks; called from\n+ * rcutree_migrate_callbacks() once @cpu is dead. One pass covers every\n+ * srcu_struct, as the per-CPU srcu_defer list chains them all, and the\n+ * re-issue lands on the current CPU's srcu_data.\n+ */\n+void srcu_offline_drain(int cpu)\n+{\n+\tif (!IS_ENABLED(CONFIG_RCU_DEFER))\n+\t\treturn;\n+\tsrcu_defer_drain(\u0026per_cpu(srcu_defer, cpu).iw);\n+}\n+\n /**\n * call_srcu() - Queue a callback for invocation after an SRCU grace period\n * @ssp: srcu_struct in queue the callback\n@@ -1677,9 +1801,17 @@ void srcu_barrier(struct srcu_struct *ssp)\n {\n \tint cpu;\n \tint idx;\n-\tunsigned long s = rcu_seq_snap(\u0026ssp-\u003esrcu_sup-\u003esrcu_barrier_seq);\n+\tunsigned long s;\n \n \tcheck_init_srcu_struct(ssp);\n+\n+\t/*\n+\t * Register any deferred callbacks before snapshotting the sequence. The\n+\t * shared irq_work may also drain other srcu_structs', which is harmless.\n+\t */\n+\tsrcu_defer_flush();\n+\n+\ts = rcu_seq_snap(\u0026ssp-\u003esrcu_sup-\u003esrcu_barrier_seq);\n \tmutex_lock(\u0026ssp-\u003esrcu_sup-\u003esrcu_barrier_mutex);\n \tif (rcu_seq_done(\u0026ssp-\u003esrcu_sup-\u003esrcu_barrier_seq, s)) {\n \t\tsmp_mb(); /* Force ordering following return. */\ndiff --git a/kernel/rcu/tiny.c b/kernel/rcu/tiny.c\nindex dccccd6be9411..71249eac61021 100644\n--- a/kernel/rcu/tiny.c\n+++ b/kernel/rcu/tiny.c\n@@ -11,6 +11,8 @@\n */\n #include \u003clinux/completion.h\u003e\n #include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/irq_work.h\u003e\n+#include \u003clinux/llist.h\u003e\n #include \u003clinux/notifier.h\u003e\n #include \u003clinux/rcupdate_wait.h\u003e\n #include \u003clinux/kernel.h\u003e\n@@ -42,8 +44,83 @@ static struct rcu_ctrlblk rcu_ctrlblk = {\n \t.gp_seq\t\t= 0 - 300UL,\n };\n \n+/*\n+ * The callback list is only ever accessed with interrupts disabled (this\n+ * enqueue, callback invocation, grace-period work), so enqueuing while a list\n+ * operation is in flight would corrupt it. Defer whenever interrupts are\n+ * disabled: the callback goes on a lockless list that an irq_work re-issues\n+ * later. One global list and irq_work suffice, as Tiny RCU is uniprocessor.\n+ * Only kernels where that can happen (CONFIG_RCU_DEFER) need this.\n+ */\n+static void rcu_defer_drain(struct irq_work *iw);\n+static LLIST_HEAD(rcu_defer_list);\n+static DEFINE_IRQ_WORK(rcu_defer_iw, rcu_defer_drain);\n+\n+/*\n+ * Enqueue @head on the callback list. Also called by rcu_defer_drain() to\n+ * re-issue a deferred callback, so it must not re-check the deferral condition.\n+ */\n+static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func)\n+{\n+\tstatic atomic_t doublefrees;\n+\tunsigned long flags;\n+\n+\tif (debug_rcu_head_queue(head)) {\n+\t\tif (atomic_inc_return(\u0026doublefrees) \u003c 4) {\n+\t\t\tpr_err(\"%s(): Double-freed CB %p-\u003e%pS()!!! \", __func__, head, head-\u003efunc);\n+\t\t\tmem_dump_obj(head);\n+\t\t}\n+\t\treturn;\n+\t}\n+\n+\thead-\u003efunc = func;\n+\thead-\u003enext = NULL;\n+\n+\tlocal_irq_save(flags);\n+\t*rcu_ctrlblk.curtail = head;\n+\trcu_ctrlblk.curtail = \u0026head-\u003enext;\n+\tlocal_irq_restore(flags);\n+\n+\tif (unlikely(is_idle_task(current))) {\n+\t\t/* force scheduling for rcu_qs() */\n+\t\tresched_cpu(0);\n+\t}\n+}\n+\n+static void rcu_defer_drain(struct irq_work *iw)\n+{\n+\tstruct llist_node *node, *next;\n+\n+\t/*\n+\t * Callbacks have no mutual ordering guarantee and rcu_barrier() has\n+\t * already flushed us, so drain in llist order without reversing.\n+\t */\n+\tllist_for_each_safe(node, next, llist_del_all(\u0026rcu_defer_list)) {\n+\t\tstruct rcu_head *head = (struct rcu_head *)node;\n+\n+\t\trcu_do_enqueue(head, head-\u003efunc);\n+\t}\n+}\n+\n+static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func)\n+{\n+\thead-\u003efunc = func;\n+\tif (llist_add((struct llist_node *)head, \u0026rcu_defer_list))\n+\t\tirq_work_queue(\u0026rcu_defer_iw);\n+}\n+\n+/* Register any deferred callbacks so a following rcu_barrier() waits for them. */\n+static void rcu_defer_flush(void)\n+{\n+\tif (!IS_ENABLED(CONFIG_RCU_DEFER))\n+\t\treturn;\n+\tirq_work_sync(\u0026rcu_defer_iw);\n+}\n+\n void rcu_barrier(void)\n {\n+\t/* Register any deferred callbacks first. */\n+\trcu_defer_flush();\n \twait_rcu_gp(call_rcu_hurry);\n }\n EXPORT_SYMBOL(rcu_barrier);\n@@ -157,29 +234,15 @@ EXPORT_SYMBOL_GPL(synchronize_rcu);\n */\n void call_rcu(struct rcu_head *head, rcu_callback_t func)\n {\n-\tstatic atomic_t doublefrees;\n-\tunsigned long flags;\n-\n-\tif (debug_rcu_head_queue(head)) {\n-\t\tif (atomic_inc_return(\u0026doublefrees) \u003c 4) {\n-\t\t\tpr_err(\"%s(): Double-freed CB %p-\u003e%pS()!!! \", __func__, head, head-\u003efunc);\n-\t\t\tmem_dump_obj(head);\n-\t\t}\n+\tif (should_rcu_defer()) {\n+\t\tcall_rcu_defer(head, func);\n \t\treturn;\n \t}\n \n-\thead-\u003efunc = func;\n-\thead-\u003enext = NULL;\n-\n-\tlocal_irq_save(flags);\n-\t*rcu_ctrlblk.curtail = head;\n-\trcu_ctrlblk.curtail = \u0026head-\u003enext;\n-\tlocal_irq_restore(flags);\n+\t/* An NMI reaching here entered with irqs enabled, so the enqueue can race. */\n+\tWARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) \u0026\u0026 in_nmi());\n \n-\tif (unlikely(is_idle_task(current))) {\n-\t\t/* force scheduling for rcu_qs() */\n-\t\tresched_cpu(0);\n-\t}\n+\trcu_do_enqueue(head, func);\n }\n EXPORT_SYMBOL_GPL(call_rcu);\n \ndiff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c\nindex 62bf2bf551f49..7d0ae94bdf39f 100644\n--- a/kernel/rcu/tree.c\n+++ b/kernel/rcu/tree.c\n@@ -24,6 +24,7 @@\n #include \u003clinux/smp.h\u003e\n #include \u003clinux/rcupdate_wait.h\u003e\n #include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/llist.h\u003e\n #include \u003clinux/sched.h\u003e\n #include \u003clinux/sched/debug.h\u003e\n #include \u003clinux/nmi.h\u003e\n@@ -3148,21 +3149,27 @@ static void check_cb_ovld(struct rcu_data *rdp)\n \traw_spin_unlock_rcu_node(rnp);\n }\n \n-static void\n-__call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)\n+/*\n+ * The callback list is only ever accessed with interrupts disabled (enqueue,\n+ * callback invocation, grace-period work). A call_rcu() with interrupts already\n+ * disabled may interrupt one of those, so __call_rcu_common() defers: the\n+ * callback is staged on a per-CPU llist that an irq_work re-issues once\n+ * interrupts are on. Only CONFIG_RCU_DEFER kernels can hit this.\n+ */\n+static void rcu_defer_drain(struct irq_work *iw);\n+\n+/*\n+ * Enqueue @head on this CPU's rcu_segcblist. Also called by rcu_defer_drain()\n+ * to re-issue a deferred callback, so it must not re-check the deferral\n+ * condition. Either caller may have interrupts already disabled.\n+ */\n+static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func, bool lazy_in)\n {\n \tstatic atomic_t doublefrees;\n \tunsigned long flags;\n \tbool lazy;\n \tstruct rcu_data *rdp;\n \n-\t/* Misaligned rcu_head! */\n-\tWARN_ON_ONCE((unsigned long)head \u0026 (sizeof(void *) - 1));\n-\n-\t/* Avoid NULL dereference if callback is NULL. */\n-\tif (WARN_ON_ONCE(!func))\n-\t\treturn;\n-\n \tif (debug_rcu_head_queue(head)) {\n \t\t/*\n \t\t * Probable double call_rcu(), so leak the callback.\n@@ -3206,6 +3213,83 @@ __call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)\n \tlocal_irq_restore(flags);\n }\n \n+/*\n+ * Re-issue deferred callbacks, going straight to the enqueue so they cannot\n+ * defer again. defer_lock is held across llist_del_all() and the re-issue so\n+ * the drainers -- this CPU's irq_work, rcu_defer_flush() and\n+ * rcutree_migrate_callbacks() -- serialize and never leave a callback off\n+ * -\u003edefer_head yet not on a callback list.\n+ */\n+static void rcu_defer_drain(struct irq_work *iw)\n+{\n+\tstruct rcu_data *rdp = container_of(iw, struct rcu_data, defer_work);\n+\tstruct llist_node *node, *next;\n+\tunsigned long flags;\n+\n+\traw_spin_lock_irqsave(\u0026rdp-\u003edefer_lock, flags);\n+\tllist_for_each_safe(node, next, llist_del_all(\u0026rdp-\u003edefer_head)) {\n+\t\tstruct rcu_head *head = (struct rcu_head *)node;\n+\n+\t\trcu_do_enqueue(head, head-\u003efunc, false);\n+\t}\n+\traw_spin_unlock_irqrestore(\u0026rdp-\u003edefer_lock, flags);\n+}\n+\n+/* Stage @head for this CPU's irq_work when call_rcu() cannot enqueue now. */\n+static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func)\n+{\n+\tstruct rcu_data *rdp = this_cpu_ptr(\u0026rcu_data);\n+\n+\thead-\u003efunc = func;\n+\tif (llist_add((struct llist_node *)head, \u0026rdp-\u003edefer_head))\n+\t\tirq_work_queue(\u0026rdp-\u003edefer_work);\n+}\n+\n+/*\n+ * Register pending deferred callbacks into the callback lists so a following\n+ * rcu_barrier() waits for them. This runs before rcu_barrier() scans the\n+ * lists. An online CPU's own irq_work re-issues its callbacks, so wait it out;\n+ * an offline CPU's irq_work may never run again, so drain its list directly\n+ * onto this CPU instead.\n+ */\n+static void rcu_defer_flush(void)\n+{\n+\tint cpu;\n+\n+\tif (!IS_ENABLED(CONFIG_RCU_DEFER))\n+\t\treturn;\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tstruct rcu_data *rdp = per_cpu_ptr(\u0026rcu_data, cpu);\n+\n+\t\tif (cpu_online(cpu))\n+\t\t\tirq_work_sync(\u0026rdp-\u003edefer_work);\n+\t\telse\n+\t\t\trcu_defer_drain(\u0026rdp-\u003edefer_work);\n+\t}\n+}\n+\n+static void\n+__call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)\n+{\n+\t/* Misaligned rcu_head! */\n+\tWARN_ON_ONCE((unsigned long)head \u0026 (sizeof(void *) - 1));\n+\n+\t/* Avoid NULL dereference if callback is NULL. */\n+\tif (WARN_ON_ONCE(!func))\n+\t\treturn;\n+\n+\tif (should_rcu_defer()) {\n+\t\tcall_rcu_defer(head, func);\n+\t\treturn;\n+\t}\n+\n+\t/* An NMI reaching here entered with irqs enabled, so the enqueue can race. */\n+\tWARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) \u0026\u0026 in_nmi());\n+\n+\trcu_do_enqueue(head, func, lazy_in);\n+}\n+\n #ifdef CONFIG_RCU_LAZY\n static bool enable_rcu_lazy __read_mostly = !IS_ENABLED(CONFIG_RCU_LAZY_DEFAULT_OFF);\n module_param(enable_rcu_lazy, bool, 0444);\n@@ -3896,8 +3980,12 @@ void rcu_barrier(void)\n \tunsigned long flags;\n \tunsigned long gseq;\n \tstruct rcu_data *rdp;\n-\tunsigned long s = rcu_seq_snap(\u0026rcu_state.barrier_sequence);\n+\tunsigned long s;\n+\n+\t/* Register any deferred callbacks before snapshotting the sequence. */\n+\trcu_defer_flush();\n \n+\ts = rcu_seq_snap(\u0026rcu_state.barrier_sequence);\n \trcu_barrier_trace(TPS(\"Begin\"), -1, s);\n \n \t/* Take mutex to serialize concurrent rcu_barrier() requests. */\n@@ -4231,6 +4319,10 @@ rcu_boot_init_percpu_data(int cpu)\n \trdp-\u003ercu_onl_gp_state = RCU_GP_CLEANED;\n \trdp-\u003elast_sched_clock = jiffies;\n \trdp-\u003ecpu = cpu;\n+\tinit_llist_head(\u0026rdp-\u003edefer_head);\n+\traw_spin_lock_init(\u0026rdp-\u003edefer_lock);\n+\t/* Hard irq_work so the re-issue runs promptly. */\n+\trdp-\u003edefer_work = IRQ_WORK_INIT_HARD(rcu_defer_drain);\n \trcu_boot_init_nocb_percpu_data(rdp);\n }\n \n@@ -4528,6 +4620,16 @@ void rcutree_migrate_callbacks(int cpu)\n \tstruct rcu_data *rdp = per_cpu_ptr(\u0026rcu_data, cpu);\n \tbool needwake;\n \n+\t/*\n+\t * Callbacks the outgoing CPU deferred late in the offline path (past the\n+\t * point its irq_work can run) sit on -\u003edefer_head, which the -\u003ecblist\n+\t * migration below does not cover. Drain them here, before the early\n+\t * returns; the re-issue lands on this CPU.\n+\t */\n+\trcu_defer_drain(\u0026rdp-\u003edefer_work);\n+\t/* Likewise for the outgoing CPU's deferred call_srcu() callbacks. */\n+\tsrcu_offline_drain(cpu);\n+\n \tif (rcu_rdp_is_offloaded(rdp))\n \t\treturn;\n \ndiff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h\nindex 9b85eeaa0071a..41c2b20f0c83e 100644\n--- a/kernel/rcu/tree.h\n+++ b/kernel/rcu/tree.h\n@@ -229,6 +229,11 @@ struct rcu_data {\n \tstruct rcu_head barrier_head;\n \tint exp_watching_snap;\t\t/* Double-check need for IPI. */\n \n+\t/* Deferral of an NMI/reentrant call_rcu(); see __call_rcu_common(). */\n+\tstruct llist_head defer_head;\n+\tstruct irq_work defer_work;\n+\traw_spinlock_t defer_lock;\n+\n \t/* 5) Callback offloading. */\n #ifdef CONFIG_RCU_NOCB_CPU\n \tstruct swait_queue_head nocb_cb_wq; /* For nocb kthreads to sleep on. */\ndiff --git a/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c\nnew file mode 100644\nindex 0000000000000..f6ecd93be30f4\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c\n@@ -0,0 +1,58 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Exercise re-entry into call_srcu() from BPF; see progs/rcu_reentry.c. */\n+#define _GNU_SOURCE\n+#include \u003csched.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003ctest_progs.h\u003e\n+#include \"rcu_reentry.skel.h\"\n+\n+static int sys_pidfd_open(pid_t pid, unsigned int flags)\n+{\n+\treturn syscall(__NR_pidfd_open, pid, flags);\n+}\n+\n+void test_rcu_reentry(void)\n+{\n+\tstruct rcu_reentry *skel;\n+\tint err, pidfd = -1, map_fd;\n+\t__u64 val = 1;\n+\tcpu_set_t set;\n+\n+\tskel = rcu_reentry__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open_and_load\"))\n+\t\treturn;\n+\n+\terr = rcu_reentry__attach(skel);\n+\tif (!ASSERT_OK(err, \"skel_attach\"))\n+\t\tgoto out;\n+\n+\t/* Keep the re-entry on a single CPU. */\n+\tCPU_ZERO(\u0026set);\n+\tCPU_SET(0, \u0026set);\n+\tif (sched_setaffinity(0, sizeof(set), \u0026set))\n+\t\tperror(\"sched_setaffinity\");\n+\n+\tpidfd = sys_pidfd_open(getpid(), 0);\n+\tif (!ASSERT_GE(pidfd, 0, \"pidfd_open\"))\n+\t\tgoto out;\n+\tmap_fd = bpf_map__fd(skel-\u003emaps.task_stg);\n+\terr = bpf_map_update_elem(map_fd, \u0026pidfd, \u0026val, BPF_NOEXIST);\n+\tif (!ASSERT_OK(err, \"boot_create\"))\n+\t\tgoto out;\n+\n+\t/* Arm the handler for this thread, then trigger call_rcu_tasks_trace(). */\n+\tskel-\u003ebss-\u003etarget_pid = syscall(__NR_gettid);\n+\terr = bpf_map_delete_elem(map_fd, \u0026pidfd);\n+\tASSERT_OK(err, \"boot_delete\");\n+\n+\t/* Only Tree SRCU enqueues via rcu_segcblist_enqueue(); skip elsewhere. */\n+\tif (!skel-\u003ebss-\u003ehits) {\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\n+\tASSERT_EQ(skel-\u003ebss-\u003ereentered, 1, \"reentry_deferred\");\n+out:\n+\tif (pidfd \u003e= 0)\n+\t\tclose(pidfd);\n+\trcu_reentry__destroy(skel);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/rcu_reentry.c b/tools/testing/selftests/bpf/progs/rcu_reentry.c\nnew file mode 100644\nindex 0000000000000..d92a927ff51c0\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/rcu_reentry.c\n@@ -0,0 +1,45 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Re-enter call_srcu() from a BPF program. fentry on rcu_segcblist_enqueue()\n+ * fires inside call_srcu()'s enqueue (reached from srcu_gp_start_if_needed()\n+ * with the srcu_data -\u003elock held); the handler then calls call_rcu_tasks_trace()\n+ * -- itself call_srcu() on rcu_tasks_trace_srcu_struct -- re-entering the same\n+ * srcu_data on the same CPU.\n+ */\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_TASK_STORAGE);\n+\t__uint(map_flags, BPF_F_NO_PREALLOC);\n+\t__type(key, int);\n+\t__type(value, __u64);\n+} task_stg SEC(\".maps\");\n+\n+int target_pid;\n+int hits;\n+int reentered;\n+\n+SEC(\"fentry/rcu_segcblist_enqueue\")\n+int BPF_PROG(reenter)\n+{\n+\tstruct task_struct *cur;\n+\n+\tif (reentered || !target_pid)\n+\t\treturn 0;\n+\n+\tcur = bpf_get_current_task_btf();\n+\tif (!cur || cur-\u003epid != target_pid)\n+\t\treturn 0;\n+\n+\t/* Re-enter via a task-storage delete, which calls call_rcu_tasks_trace(). */\n+\t__sync_fetch_and_add(\u0026hits, 1);\n+\tbpf_task_storage_get(\u0026task_stg, cur, 0, BPF_LOCAL_STORAGE_GET_F_CREATE);\n+\tbpf_task_storage_delete(\u0026task_stg, cur);\n+\n+\treentered = 1;\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/07/31 01:14 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"KPROBES",
"FUNCTION_TRACER",
"TRACEPOINTS",
"PERF_EVENTS",
"RCU_TORTURE_TEST",
"BPF_SYSCALL"
],
"FocusSymbols": [
"rcu_barrier",
"srcu_barrier",
"rcutree_migrate_callbacks",
"srcu_offline_drain"
],
"Reasoning": "The patch introduces a deferral mechanism for call_rcu() and call_srcu() to safely handle callback enqueues when interrupts are disabled (e.g., from NMIs or BPF programs). It adds lockless lists and irq_work to defer the actual enqueue until it is safe. It also updates rcu_barrier(), srcu_barrier(), and CPU hotplug paths to flush these deferred callbacks. Additionally, it adds NMI testing to rcutorture using perf events, and a BPF selftest. This is a significant functional change to core RCU logic and should be fuzzed. Focus symbols include the barrier and CPU offline functions that drain the new deferred lists. Required configs include KPROBES/FUNCTION_TRACER/TRACEPOINTS to enable CONFIG_RCU_DEFER, PERF_EVENTS and RCU_TORTURE_TEST for the rcutorture changes, and BPF_SYSCALL as BPF is a primary trigger for this code.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 3ef36742b17beb0e7346b58f518ba7e86a766742
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Jul 31 01:14:32 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/srcutiny.h b/include/linux/srcutiny.h
index fbcf13bc12d15..47275d182966c 100644
--- a/include/linux/srcutiny.h
+++ b/include/linux/srcutiny.h
@@ -12,6 +12,7 @@
#define _LINUX_SRCU_TINY_H
#include <linux/irq_work_types.h>
+#include <linux/llist.h>
#include <linux/swait.h>
struct srcu_struct {
@@ -26,6 +27,8 @@ struct srcu_struct {
struct rcu_head **srcu_cb_tail; /* Pending callbacks: Tail. */
struct work_struct srcu_work; /* For driving grace periods. */
struct irq_work srcu_irq_work; /* Defer schedule_work() to irq work. */
+ struct llist_head defer_cbs; /* Callbacks deferred on re-entry. */
+ struct irq_work defer_iw; /* Registers defer_cbs later. */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
@@ -33,6 +36,7 @@ struct srcu_struct {
void srcu_drive_gp(struct work_struct *wp);
void srcu_tiny_irq_work(struct irq_work *irq_work);
+void srcu_defer_drain(struct irq_work *irq_work);
#define __SRCU_STRUCT_INIT(name, __ignored, ___ignored, ____ignored) \
{ \
@@ -40,6 +44,8 @@ void srcu_tiny_irq_work(struct irq_work *irq_work);
.srcu_cb_tail = &name.srcu_cb_head, \
.srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp), \
.srcu_irq_work = { .func = srcu_tiny_irq_work }, \
+ .defer_cbs = LLIST_HEAD_INIT(name.defer_cbs), \
+ .defer_iw = { .func = srcu_defer_drain }, \
__SRCU_DEP_MAP_INIT(name) \
}
@@ -131,10 +137,7 @@ static inline void synchronize_srcu_expedited(struct srcu_struct *ssp)
synchronize_srcu(ssp);
}
-static inline void srcu_barrier(struct srcu_struct *ssp)
-{
- synchronize_srcu(ssp);
-}
+void srcu_barrier(struct srcu_struct *ssp);
static inline void srcu_expedite_current(struct srcu_struct *ssp) { }
#define srcu_check_read_flavor(ssp, read_flavor) do { } while (0)
diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h
index 75e54e4f963fa..1ce759fb70948 100644
--- a/include/linux/srcutree.h
+++ b/include/linux/srcutree.h
@@ -13,6 +13,8 @@
#include <linux/rcu_node_tree.h>
#include <linux/completion.h>
+#include <linux/irq_work_types.h>
+#include <linux/llist.h>
struct srcu_node;
struct srcu_struct;
@@ -41,6 +43,8 @@ struct srcu_data {
bool srcu_cblist_invoking; /* Invoking these CBs? */
struct timer_list delay_work; /* Delay for CB invoking */
struct work_struct work; /* Context for CB invoking. */
+ struct llist_head defer_cbs; /* Callbacks deferred on re-entry. */
+ struct llist_node defer_link; /* Links onto the per-CPU deferral drain list */
struct rcu_head srcu_barrier_head; /* For srcu_barrier() use. */
struct rcu_head srcu_ec_head; /* For srcu_expedite_current() use. */
int srcu_ec_state; /* State for srcu_expedite_current(). */
diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig
index 332df7a7a6347..bc9483c0b5d84 100644
--- a/kernel/rcu/Kconfig
+++ b/kernel/rcu/Kconfig
@@ -175,6 +175,12 @@ config RCU_STALL_COMMON
config RCU_NEED_SEGCBLIST
def_bool ( TREE_RCU || TREE_SRCU || TASKS_RCU_GENERIC )
+# The deferral (and the IRQ_WORK it uses) is only needed where call_rcu() /
+# call_srcu() can be invoked while a callback-list operation is in flight.
+config RCU_DEFER
+ def_bool HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS
+ select IRQ_WORK
+
config RCU_FANOUT
int "Tree-based hierarchical RCU fanout value"
range 2 64 if 64BIT
diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h
index 39a9f6fa9a7b2..036d28516e92d 100644
--- a/kernel/rcu/rcu.h
+++ b/kernel/rcu/rcu.h
@@ -572,6 +572,24 @@ static inline void tasks_cblist_init_generic(void) { }
#define RCU_SCHEDULER_INIT 1
#define RCU_SCHEDULER_RUNNING 2
+/*
+ * Should a call_rcu()/call_srcu() callback be deferred rather than enqueued
+ * now? Defer whenever interrupts are disabled: a callback-list operation may
+ * be in flight on this CPU, so enqueuing now could corrupt it. But not while
+ * the scheduler is down -- early boot is single-threaded and can call this
+ * before init_IRQ() makes irq_work usable (e.g. rcu_init()'s self-tests).
+ */
+static inline bool should_rcu_defer(void)
+{
+ if (!IS_ENABLED(CONFIG_RCU_DEFER))
+ return false;
+
+ return irqs_disabled() && rcu_scheduler_active != RCU_SCHEDULER_INACTIVE;
+}
+
+/* Drain an outgoing CPU's deferred SRCU callbacks; see rcutree_migrate_callbacks(). */
+void srcu_offline_drain(int cpu);
+
enum rcutorture_type {
RCU_FLAVOR,
RCU_TASKS_FLAVOR,
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 3d0b59a7b0b38..a06100b823597 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -48,6 +48,7 @@
#include <linux/tick.h>
#include <linux/rcupdate_trace.h>
#include <linux/nmi.h>
+#include <linux/perf_event.h>
#include "rcu.h"
@@ -115,6 +116,7 @@ torture_param(int, leakpointer, 0, "Leak pointer dereferences from readers");
torture_param(int, n_barrier_cbs, 0, "# of callbacks/kthreads for barrier testing");
torture_param(int, n_up_down, 32, "# of concurrent up/down hrtimer-based RCU readers");
torture_param(int, nfakewriters, 4, "Number of RCU fake writer threads");
+torture_param(bool, nmi_calls, true, "Exercise ->call() from NMI on nmi_capable flavors");
torture_param(int, nreaders, -1, "Number of RCU reader threads");
torture_param(bool, nwriters, 1, "Number of RCU writer threads (0 or 1)");
torture_param(int, object_debug, 0, "Enable debug-object double call_rcu() testing");
@@ -216,6 +218,8 @@ static long n_rcu_torture_boost_failure;
static long n_rcu_torture_boosts;
static atomic_long_t n_rcu_torture_timers;
static atomic_long_t n_rcu_torture_irqs;
+static atomic_long_t n_rcu_torture_nmi_call;
+static atomic_long_t n_rcu_torture_nmi_cb;
static long n_barrier_attempts;
static long n_barrier_successes; /* did rcu_barrier test succeed? */
static unsigned long n_read_exits;
@@ -433,6 +437,7 @@ struct rcu_torture_ops {
bool (*is_task_rcu_boosted)(void);
long cbflood_max;
int irq_capable;
+ int nmi_capable;
int can_boost;
int extendables;
int slow_gps;
@@ -646,6 +651,7 @@ static struct rcu_torture_ops rcu_ops = {
.extendables = RCUTORTURE_MAX_EXTEND,
.debug_objects = 1,
.start_poll_irqsoff = 1,
+ .nmi_capable = 1,
.name = "rcu"
};
@@ -936,6 +942,7 @@ static struct rcu_torture_ops srcu_ops = {
.debug_objects = 1,
.have_up_down = IS_ENABLED(CONFIG_TINY_SRCU)
? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN,
+ .nmi_capable = 1,
.name = "srcu"
};
@@ -999,6 +1006,7 @@ static struct rcu_torture_ops srcud_ops = {
.debug_objects = 1,
.have_up_down = IS_ENABLED(CONFIG_TINY_SRCU)
? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN,
+ .nmi_capable = 1,
.name = "srcud"
};
@@ -1263,6 +1271,7 @@ static struct rcu_torture_ops tasks_tracing_ops = {
.cbflood_max = 50000,
.irq_capable = 1,
.slow_gps = 1,
+ .nmi_capable = 1,
.name = "tasks-tracing"
};
@@ -2653,12 +2662,97 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_timer_rand);
+/*
+ * Exercise ->call() from NMI context for flavors that advertise ->nmi_capable.
+ * A per-CPU hardware perf counter overflows into an NMI -- a real NMI on x86,
+ * or a pseudo-NMI on arm64 booted with irqchip.gicv3_pseudo_nmi=1 -- and its
+ * handler submits one preallocated callback via ->call(). Only one callback is
+ * in flight at a time (guarded by an atomic), which avoids allocating in NMI
+ * and is enough to exercise the deferral. This mirrors how BPF programs reach
+ * ->call() from NMI. Requires a hardware PMU; when the overflow is not an NMI
+ * the handler does nothing.
+ */
+#ifdef CONFIG_PERF_EVENTS
+static struct perf_event_attr rcu_torture_nmi_attr = {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES,
+ .size = sizeof(struct perf_event_attr),
+ .pinned = 1,
+ .disabled = 1,
+ .freq = 1,
+ .sample_freq = 1000,
+};
+
+static struct perf_event **rcu_torture_nmi_events;
+static struct rcu_head rcu_torture_nmi_rh;
+static atomic_t rcu_torture_nmi_rh_inuse;
+
+static void rcu_torture_nmi_cb(struct rcu_head *rhp)
+{
+ atomic_long_inc(&n_rcu_torture_nmi_cb);
+ atomic_set(&rcu_torture_nmi_rh_inuse, 0);
+}
+
+static void rcu_torture_nmi_overflow(struct perf_event *event,
+ struct perf_sample_data *data,
+ struct pt_regs *regs)
+{
+ if (!in_nmi())
+ return;
+ if (cur_ops->call && !atomic_xchg(&rcu_torture_nmi_rh_inuse, 1)) {
+ cur_ops->call(&rcu_torture_nmi_rh, rcu_torture_nmi_cb);
+ atomic_long_inc(&n_rcu_torture_nmi_call);
+ }
+}
+
+static void rcu_torture_nmi_init(void)
+{
+ struct perf_event *event;
+ int cpu;
+
+ if (!nmi_calls || !cur_ops->nmi_capable || !cur_ops->call)
+ return;
+ rcu_torture_nmi_events = kcalloc(nr_cpu_ids, sizeof(*rcu_torture_nmi_events),
+ GFP_KERNEL);
+ if (!rcu_torture_nmi_events)
+ return;
+ for_each_online_cpu(cpu) {
+ event = perf_event_create_kernel_counter(&rcu_torture_nmi_attr, cpu,
+ NULL, rcu_torture_nmi_overflow, NULL);
+ if (IS_ERR(event))
+ continue;
+ rcu_torture_nmi_events[cpu] = event;
+ perf_event_enable(event);
+ }
+}
+
+static void rcu_torture_nmi_cleanup(void)
+{
+ int cpu;
+
+ if (!rcu_torture_nmi_events)
+ return;
+ for_each_possible_cpu(cpu) {
+ if (!rcu_torture_nmi_events[cpu])
+ continue;
+ perf_event_disable(rcu_torture_nmi_events[cpu]);
+ perf_event_release_kernel(rcu_torture_nmi_events[cpu]);
+ }
+ kfree(rcu_torture_nmi_events);
+ rcu_torture_nmi_events = NULL;
+}
+#else /* #ifdef CONFIG_PERF_EVENTS */
+static void rcu_torture_nmi_init(void) { }
+static void rcu_torture_nmi_cleanup(void) { }
+#endif /* #else #ifdef CONFIG_PERF_EVENTS */
+
/*
* RCU torture reader from timer handler. Dereferences rcu_torture_current,
* incrementing the corresponding element of the pipeline array. The
* counter in the element should never be greater than 1, otherwise, the
* RCU implementation is broken.
*/
+
static void rcu_torture_timer(struct timer_list *unused)
{
WARN_ON_ONCE(!in_serving_softirq());
@@ -3041,6 +3135,9 @@ rcu_torture_stats_print(void)
data_race(n_barrier_attempts),
data_race(n_rcu_torture_barrier_error));
pr_cont("read-exits: %ld ", data_race(n_read_exits)); // Statistic.
+ pr_cont("nmi-calls: %ld nmi-cbs: %ld ",
+ atomic_long_read(&n_rcu_torture_nmi_call),
+ atomic_long_read(&n_rcu_torture_nmi_cb));
pr_cont("nocb-toggles: %ld:%ld ",
atomic_long_read(&n_nocb_offload), atomic_long_read(&n_nocb_deoffload));
pr_cont("gpwraps: %ld\n", n_gpwraps);
@@ -4315,6 +4412,8 @@ rcu_torture_cleanup(void)
kfree(reader_tasks);
reader_tasks = NULL;
}
+ /* Disable the perf counters (and thus the NMI ->call() firing) now. */
+ rcu_torture_nmi_cleanup();
kfree(rcu_torture_reader_mbchk);
rcu_torture_reader_mbchk = NULL;
@@ -4344,6 +4443,20 @@ rcu_torture_cleanup(void)
pr_info("%s: Invoking %pS().\n", __func__, cur_ops->cb_barrier);
cur_ops->cb_barrier();
}
+
+ /*
+ * cb_barrier() above drained every deferred NMI ->call() callback, so the
+ * count issued from NMI must equal the count invoked; a mismatch means a
+ * callback was lost.
+ */
+ if (atomic_long_read(&n_rcu_torture_nmi_call) !=
+ atomic_long_read(&n_rcu_torture_nmi_cb)) {
+ pr_alert("%s: NMI ->call() lost a callback: issued %ld invoked %ld\n",
+ __func__, atomic_long_read(&n_rcu_torture_nmi_call),
+ atomic_long_read(&n_rcu_torture_nmi_cb));
+ atomic_inc(&n_rcu_torture_error);
+ }
+
if (cur_ops->cleanup != NULL)
cur_ops->cleanup();
@@ -4776,6 +4889,8 @@ rcu_torture_init(void)
firsterr = -ENOMEM;
goto unwind;
}
+ /* Arm the per-CPU perf counters that drive ->call() from NMI. */
+ rcu_torture_nmi_init();
for (i = 0; i < nrealreaders; i++) {
rcu_torture_reader_mbchk[i].rtc_chkrdr = -1;
firsterr = torture_create_kthread(rcu_torture_reader, (void *)i,
diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c
index 558ba8d316db6..03a07a1f3a30b 100644
--- a/kernel/rcu/srcutiny.c
+++ b/kernel/rcu/srcutiny.c
@@ -10,6 +10,7 @@
#include <linux/export.h>
#include <linux/irq_work.h>
+#include <linux/llist.h>
#include <linux/mutex.h>
#include <linux/preempt.h>
#include <linux/rcupdate_wait.h>
@@ -43,6 +44,8 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp)
INIT_WORK(&ssp->srcu_work, srcu_drive_gp);
INIT_LIST_HEAD(&ssp->srcu_work.entry);
init_irq_work(&ssp->srcu_irq_work, srcu_tiny_irq_work);
+ init_llist_head(&ssp->defer_cbs);
+ init_irq_work(&ssp->defer_iw, srcu_defer_drain);
return 0;
}
@@ -213,11 +216,11 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
}
/*
- * Enqueue an SRCU callback on the specified srcu_struct structure,
- * initiating grace-period processing if it is not already running.
+ * Enqueue @rhp on the callback list. Also called by srcu_defer_drain() to
+ * re-issue a deferred callback, so it must not re-check the deferral condition.
*/
-void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
- rcu_callback_t func)
+static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,
+ rcu_callback_t func)
{
unsigned long flags;
@@ -231,6 +234,39 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
srcu_gp_start_if_needed(ssp);
preempt_enable();
}
+
+void srcu_defer_drain(struct irq_work *iw)
+{
+ struct srcu_struct *ssp = container_of(iw, struct srcu_struct, defer_iw);
+ struct llist_node *node, *next;
+
+ /*
+ * Callbacks have no mutual ordering guarantee and srcu_barrier() has
+ * already flushed us, so drain in llist order without reversing.
+ */
+ llist_for_each_safe(node, next, llist_del_all(&ssp->defer_cbs)) {
+ struct rcu_head *rhp = (struct rcu_head *)node;
+
+ srcu_do_enqueue(ssp, rhp, rhp->func);
+ }
+}
+EXPORT_SYMBOL_GPL(srcu_defer_drain);
+
+void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
+ rcu_callback_t func)
+{
+ if (should_rcu_defer()) {
+ rhp->func = func;
+ if (llist_add((struct llist_node *)rhp, &ssp->defer_cbs))
+ irq_work_queue(&ssp->defer_iw);
+ return;
+ }
+
+ /* An NMI reaching here entered with irqs enabled, so the enqueue can race. */
+ WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
+
+ srcu_do_enqueue(ssp, rhp, func);
+}
EXPORT_SYMBOL_GPL(call_srcu);
/*
@@ -260,6 +296,15 @@ void synchronize_srcu(struct srcu_struct *ssp)
}
EXPORT_SYMBOL_GPL(synchronize_srcu);
+/* Register any deferred callbacks, then wait for all in-flight ones. */
+void srcu_barrier(struct srcu_struct *ssp)
+{
+ if (IS_ENABLED(CONFIG_RCU_DEFER))
+ irq_work_sync(&ssp->defer_iw);
+ synchronize_srcu(ssp);
+}
+EXPORT_SYMBOL_GPL(srcu_barrier);
+
/*
* get_state_synchronize_srcu - Provide an end-of-grace-period cookie
*/
diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
index 304112674e8a2..b9fe57ff91000 100644
--- a/kernel/rcu/srcutree.c
+++ b/kernel/rcu/srcutree.c
@@ -20,6 +20,7 @@
#include <linux/percpu.h>
#include <linux/preempt.h>
#include <linux/irq_work.h>
+#include <linux/llist.h>
#include <linux/rcupdate_wait.h>
#include <linux/sched.h>
#include <linux/smp.h>
@@ -79,6 +80,46 @@ static void process_srcu(struct work_struct *work);
static void srcu_irq_work(struct irq_work *work);
static void srcu_delay_timer(struct timer_list *t);
+static void srcu_defer_drain(struct irq_work *iw);
+
+/*
+ * Per-CPU call_srcu() deferral state. The irq_work is global (shared by every
+ * srcu_struct), so it must find which srcu_structs deferred callbacks: a
+ * callback is staged on its srcu_data's ->defer_cbs, and that srcu_data is
+ * chained (->defer_link) onto this per-CPU ->list that the irq_work walks.
+ */
+struct srcu_defer {
+ struct llist_head list;
+ struct irq_work iw;
+ raw_spinlock_t lock;
+};
+
+static DEFINE_PER_CPU(struct srcu_defer, srcu_defer) = {
+ .lock = __RAW_SPIN_LOCK_UNLOCKED(srcu_defer.lock),
+ .iw = IRQ_WORK_INIT_HARD(srcu_defer_drain),
+};
+
+/*
+ * Register pending deferred call_srcu() callbacks so a following srcu_barrier()
+ * waits for them. An online CPU's own irq_work re-issues its callbacks, so wait
+ * it out; an offline CPU's irq_work may never run again, so drain its list
+ * directly onto this CPU instead.
+ */
+static void srcu_defer_flush(void)
+{
+ int cpu;
+
+ if (!IS_ENABLED(CONFIG_RCU_DEFER))
+ return;
+
+ for_each_possible_cpu(cpu) {
+ if (cpu_online(cpu))
+ irq_work_sync(&per_cpu(srcu_defer, cpu).iw);
+ else
+ srcu_defer_drain(&per_cpu(srcu_defer, cpu).iw);
+ }
+}
+
/*
* Initialize SRCU per-CPU data. Note that statically allocated
* srcu_struct structures might already have srcu_read_lock() and
@@ -107,6 +148,11 @@ static void init_srcu_struct_data(struct srcu_struct *ssp)
sdp->cpu = cpu;
INIT_WORK(&sdp->work, srcu_invoke_callbacks);
timer_setup(&sdp->delay_work, srcu_delay_timer, 0);
+ /*
+ * ->defer_cbs and ->defer_link are valid when zeroed and are not
+ * reinitialized here, lest we clobber callbacks a reentrant
+ * call_srcu() already staged. See __call_srcu().
+ */
sdp->ssp = ssp;
}
}
@@ -697,6 +743,8 @@ void cleanup_srcu_struct(struct srcu_struct *ssp)
return; /* Just leak it! */
/* Wait for irq_work to finish first as it may queue a new work. */
irq_work_sync(&sup->irq_work);
+ /* Drain any deferred callbacks (shared irq_work) before freeing ->sda. */
+ srcu_defer_flush();
flush_delayed_work(&sup->work);
for_each_possible_cpu(cpu) {
struct srcu_data *sdp = per_cpu_ptr(ssp->sda, cpu);
@@ -1410,8 +1458,18 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,
* srcu_read_lock(), and srcu_read_unlock() that are all passed the same
* srcu_struct structure.
*/
-static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
- rcu_callback_t func, bool do_norm)
+/*
+ * The srcu_cblist and the srcu_node tree are only ever accessed with interrupts
+ * disabled (srcu_gp_start_if_needed() enqueues under raw_spin_lock_irqsave() and
+ * may walk the tree; callback invocation and grace-period work likewise). So
+ * like call_rcu(), enqueuing while a list operation is in flight on this CPU
+ * would corrupt it or deadlock -- e.g. call_rcu_tasks_trace(), which is
+ * call_srcu() on a dedicated srcu_struct, from a BPF program. __call_srcu()
+ * defers whenever interrupts are disabled, via a per-CPU irq_work; only kernels
+ * where that can happen (CONFIG_RCU_DEFER) need this.
+ */
+static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,
+ rcu_callback_t func, bool do_norm)
{
if (debug_rcu_head_queue(rhp)) {
/* Probable double call_srcu(), so leak the callback. */
@@ -1423,6 +1481,72 @@ static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
(void)srcu_gp_start_if_needed(ssp, rhp, do_norm);
}
+static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
+ rcu_callback_t func, bool do_norm)
+{
+ if (should_rcu_defer()) {
+ struct srcu_data *sdp = this_cpu_ptr(ssp->sda);
+
+ rhp->func = func;
+ if (llist_add((struct llist_node *)rhp, &sdp->defer_cbs)) {
+ /* First deferral on this srcu_data: chain it for the drain. */
+ struct srcu_defer *sndp = this_cpu_ptr(&srcu_defer);
+
+ sdp->ssp = ssp;
+ if (llist_add(&sdp->defer_link, &sndp->list))
+ irq_work_queue(&sndp->iw);
+ }
+ return;
+ }
+
+ /* An NMI reaching here entered with irqs enabled, so the enqueue can race. */
+ WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
+
+ srcu_do_enqueue(ssp, rhp, func, do_norm);
+}
+
+/*
+ * Re-issue this CPU's deferred callbacks, going straight to srcu_do_enqueue() so
+ * they cannot defer again. The irq_work walks its ->list of srcu_data, each
+ * carrying its own ->defer_cbs. ->lock is held across the drain so drainers
+ * (irq_work, srcu_defer_flush() and srcu_offline_drain()) serialize and never
+ * leave a callback off the lists yet not on a callback list.
+ */
+static void srcu_defer_drain(struct irq_work *iw)
+{
+ struct srcu_defer *sndp = container_of(iw, struct srcu_defer, iw);
+ struct llist_node *snode, *snext;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&sndp->lock, flags);
+ llist_for_each_safe(snode, snext, llist_del_all(&sndp->list)) {
+ struct srcu_data *sdp = container_of(snode, struct srcu_data, defer_link);
+ struct srcu_struct *ssp = sdp->ssp;
+ struct llist_node *cnode, *cnext;
+
+ cnode = llist_del_all(&sdp->defer_cbs);
+ llist_for_each_safe(cnode, cnext, cnode) {
+ struct rcu_head *rhp = (struct rcu_head *)cnode;
+
+ srcu_do_enqueue(ssp, rhp, rhp->func, true);
+ }
+ }
+ raw_spin_unlock_irqrestore(&sndp->lock, flags);
+}
+
+/*
+ * Drain @cpu's deferred call_srcu() callbacks; called from
+ * rcutree_migrate_callbacks() once @cpu is dead. One pass covers every
+ * srcu_struct, as the per-CPU srcu_defer list chains them all, and the
+ * re-issue lands on the current CPU's srcu_data.
+ */
+void srcu_offline_drain(int cpu)
+{
+ if (!IS_ENABLED(CONFIG_RCU_DEFER))
+ return;
+ srcu_defer_drain(&per_cpu(srcu_defer, cpu).iw);
+}
+
/**
* call_srcu() - Queue a callback for invocation after an SRCU grace period
* @ssp: srcu_struct in queue the callback
@@ -1677,9 +1801,17 @@ void srcu_barrier(struct srcu_struct *ssp)
{
int cpu;
int idx;
- unsigned long s = rcu_seq_snap(&ssp->srcu_sup->srcu_barrier_seq);
+ unsigned long s;
check_init_srcu_struct(ssp);
+
+ /*
+ * Register any deferred callbacks before snapshotting the sequence. The
+ * shared irq_work may also drain other srcu_structs', which is harmless.
+ */
+ srcu_defer_flush();
+
+ s = rcu_seq_snap(&ssp->srcu_sup->srcu_barrier_seq);
mutex_lock(&ssp->srcu_sup->srcu_barrier_mutex);
if (rcu_seq_done(&ssp->srcu_sup->srcu_barrier_seq, s)) {
smp_mb(); /* Force ordering following return. */
diff --git a/kernel/rcu/tiny.c b/kernel/rcu/tiny.c
index dccccd6be9411..71249eac61021 100644
--- a/kernel/rcu/tiny.c
+++ b/kernel/rcu/tiny.c
@@ -11,6 +11,8 @@
*/
#include <linux/completion.h>
#include <linux/interrupt.h>
+#include <linux/irq_work.h>
+#include <linux/llist.h>
#include <linux/notifier.h>
#include <linux/rcupdate_wait.h>
#include <linux/kernel.h>
@@ -42,8 +44,83 @@ static struct rcu_ctrlblk rcu_ctrlblk = {
.gp_seq = 0 - 300UL,
};
+/*
+ * The callback list is only ever accessed with interrupts disabled (this
+ * enqueue, callback invocation, grace-period work), so enqueuing while a list
+ * operation is in flight would corrupt it. Defer whenever interrupts are
+ * disabled: the callback goes on a lockless list that an irq_work re-issues
+ * later. One global list and irq_work suffice, as Tiny RCU is uniprocessor.
+ * Only kernels where that can happen (CONFIG_RCU_DEFER) need this.
+ */
+static void rcu_defer_drain(struct irq_work *iw);
+static LLIST_HEAD(rcu_defer_list);
+static DEFINE_IRQ_WORK(rcu_defer_iw, rcu_defer_drain);
+
+/*
+ * Enqueue @head on the callback list. Also called by rcu_defer_drain() to
+ * re-issue a deferred callback, so it must not re-check the deferral condition.
+ */
+static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func)
+{
+ static atomic_t doublefrees;
+ unsigned long flags;
+
+ if (debug_rcu_head_queue(head)) {
+ if (atomic_inc_return(&doublefrees) < 4) {
+ pr_err("%s(): Double-freed CB %p->%pS()!!! ", __func__, head, head->func);
+ mem_dump_obj(head);
+ }
+ return;
+ }
+
+ head->func = func;
+ head->next = NULL;
+
+ local_irq_save(flags);
+ *rcu_ctrlblk.curtail = head;
+ rcu_ctrlblk.curtail = &head->next;
+ local_irq_restore(flags);
+
+ if (unlikely(is_idle_task(current))) {
+ /* force scheduling for rcu_qs() */
+ resched_cpu(0);
+ }
+}
+
+static void rcu_defer_drain(struct irq_work *iw)
+{
+ struct llist_node *node, *next;
+
+ /*
+ * Callbacks have no mutual ordering guarantee and rcu_barrier() has
+ * already flushed us, so drain in llist order without reversing.
+ */
+ llist_for_each_safe(node, next, llist_del_all(&rcu_defer_list)) {
+ struct rcu_head *head = (struct rcu_head *)node;
+
+ rcu_do_enqueue(head, head->func);
+ }
+}
+
+static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func)
+{
+ head->func = func;
+ if (llist_add((struct llist_node *)head, &rcu_defer_list))
+ irq_work_queue(&rcu_defer_iw);
+}
+
+/* Register any deferred callbacks so a following rcu_barrier() waits for them. */
+static void rcu_defer_flush(void)
+{
+ if (!IS_ENABLED(CONFIG_RCU_DEFER))
+ return;
+ irq_work_sync(&rcu_defer_iw);
+}
+
void rcu_barrier(void)
{
+ /* Register any deferred callbacks first. */
+ rcu_defer_flush();
wait_rcu_gp(call_rcu_hurry);
}
EXPORT_SYMBOL(rcu_barrier);
@@ -157,29 +234,15 @@ EXPORT_SYMBOL_GPL(synchronize_rcu);
*/
void call_rcu(struct rcu_head *head, rcu_callback_t func)
{
- static atomic_t doublefrees;
- unsigned long flags;
-
- if (debug_rcu_head_queue(head)) {
- if (atomic_inc_return(&doublefrees) < 4) {
- pr_err("%s(): Double-freed CB %p->%pS()!!! ", __func__, head, head->func);
- mem_dump_obj(head);
- }
+ if (should_rcu_defer()) {
+ call_rcu_defer(head, func);
return;
}
- head->func = func;
- head->next = NULL;
-
- local_irq_save(flags);
- *rcu_ctrlblk.curtail = head;
- rcu_ctrlblk.curtail = &head->next;
- local_irq_restore(flags);
+ /* An NMI reaching here entered with irqs enabled, so the enqueue can race. */
+ WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
- if (unlikely(is_idle_task(current))) {
- /* force scheduling for rcu_qs() */
- resched_cpu(0);
- }
+ rcu_do_enqueue(head, func);
}
EXPORT_SYMBOL_GPL(call_rcu);
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index 62bf2bf551f49..7d0ae94bdf39f 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -24,6 +24,7 @@
#include <linux/smp.h>
#include <linux/rcupdate_wait.h>
#include <linux/interrupt.h>
+#include <linux/llist.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/nmi.h>
@@ -3148,21 +3149,27 @@ static void check_cb_ovld(struct rcu_data *rdp)
raw_spin_unlock_rcu_node(rnp);
}
-static void
-__call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)
+/*
+ * The callback list is only ever accessed with interrupts disabled (enqueue,
+ * callback invocation, grace-period work). A call_rcu() with interrupts already
+ * disabled may interrupt one of those, so __call_rcu_common() defers: the
+ * callback is staged on a per-CPU llist that an irq_work re-issues once
+ * interrupts are on. Only CONFIG_RCU_DEFER kernels can hit this.
+ */
+static void rcu_defer_drain(struct irq_work *iw);
+
+/*
+ * Enqueue @head on this CPU's rcu_segcblist. Also called by rcu_defer_drain()
+ * to re-issue a deferred callback, so it must not re-check the deferral
+ * condition. Either caller may have interrupts already disabled.
+ */
+static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func, bool lazy_in)
{
static atomic_t doublefrees;
unsigned long flags;
bool lazy;
struct rcu_data *rdp;
- /* Misaligned rcu_head! */
- WARN_ON_ONCE((unsigned long)head & (sizeof(void *) - 1));
-
- /* Avoid NULL dereference if callback is NULL. */
- if (WARN_ON_ONCE(!func))
- return;
-
if (debug_rcu_head_queue(head)) {
/*
* Probable double call_rcu(), so leak the callback.
@@ -3206,6 +3213,83 @@ __call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)
local_irq_restore(flags);
}
+/*
+ * Re-issue deferred callbacks, going straight to the enqueue so they cannot
+ * defer again. defer_lock is held across llist_del_all() and the re-issue so
+ * the drainers -- this CPU's irq_work, rcu_defer_flush() and
+ * rcutree_migrate_callbacks() -- serialize and never leave a callback off
+ * ->defer_head yet not on a callback list.
+ */
+static void rcu_defer_drain(struct irq_work *iw)
+{
+ struct rcu_data *rdp = container_of(iw, struct rcu_data, defer_work);
+ struct llist_node *node, *next;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&rdp->defer_lock, flags);
+ llist_for_each_safe(node, next, llist_del_all(&rdp->defer_head)) {
+ struct rcu_head *head = (struct rcu_head *)node;
+
+ rcu_do_enqueue(head, head->func, false);
+ }
+ raw_spin_unlock_irqrestore(&rdp->defer_lock, flags);
+}
+
+/* Stage @head for this CPU's irq_work when call_rcu() cannot enqueue now. */
+static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func)
+{
+ struct rcu_data *rdp = this_cpu_ptr(&rcu_data);
+
+ head->func = func;
+ if (llist_add((struct llist_node *)head, &rdp->defer_head))
+ irq_work_queue(&rdp->defer_work);
+}
+
+/*
+ * Register pending deferred callbacks into the callback lists so a following
+ * rcu_barrier() waits for them. This runs before rcu_barrier() scans the
+ * lists. An online CPU's own irq_work re-issues its callbacks, so wait it out;
+ * an offline CPU's irq_work may never run again, so drain its list directly
+ * onto this CPU instead.
+ */
+static void rcu_defer_flush(void)
+{
+ int cpu;
+
+ if (!IS_ENABLED(CONFIG_RCU_DEFER))
+ return;
+
+ for_each_possible_cpu(cpu) {
+ struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);
+
+ if (cpu_online(cpu))
+ irq_work_sync(&rdp->defer_work);
+ else
+ rcu_defer_drain(&rdp->defer_work);
+ }
+}
+
+static void
+__call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in)
+{
+ /* Misaligned rcu_head! */
+ WARN_ON_ONCE((unsigned long)head & (sizeof(void *) - 1));
+
+ /* Avoid NULL dereference if callback is NULL. */
+ if (WARN_ON_ONCE(!func))
+ return;
+
+ if (should_rcu_defer()) {
+ call_rcu_defer(head, func);
+ return;
+ }
+
+ /* An NMI reaching here entered with irqs enabled, so the enqueue can race. */
+ WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
+
+ rcu_do_enqueue(head, func, lazy_in);
+}
+
#ifdef CONFIG_RCU_LAZY
static bool enable_rcu_lazy __read_mostly = !IS_ENABLED(CONFIG_RCU_LAZY_DEFAULT_OFF);
module_param(enable_rcu_lazy, bool, 0444);
@@ -3896,8 +3980,12 @@ void rcu_barrier(void)
unsigned long flags;
unsigned long gseq;
struct rcu_data *rdp;
- unsigned long s = rcu_seq_snap(&rcu_state.barrier_sequence);
+ unsigned long s;
+
+ /* Register any deferred callbacks before snapshotting the sequence. */
+ rcu_defer_flush();
+ s = rcu_seq_snap(&rcu_state.barrier_sequence);
rcu_barrier_trace(TPS("Begin"), -1, s);
/* Take mutex to serialize concurrent rcu_barrier() requests. */
@@ -4231,6 +4319,10 @@ rcu_boot_init_percpu_data(int cpu)
rdp->rcu_onl_gp_state = RCU_GP_CLEANED;
rdp->last_sched_clock = jiffies;
rdp->cpu = cpu;
+ init_llist_head(&rdp->defer_head);
+ raw_spin_lock_init(&rdp->defer_lock);
+ /* Hard irq_work so the re-issue runs promptly. */
+ rdp->defer_work = IRQ_WORK_INIT_HARD(rcu_defer_drain);
rcu_boot_init_nocb_percpu_data(rdp);
}
@@ -4528,6 +4620,16 @@ void rcutree_migrate_callbacks(int cpu)
struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);
bool needwake;
+ /*
+ * Callbacks the outgoing CPU deferred late in the offline path (past the
+ * point its irq_work can run) sit on ->defer_head, which the ->cblist
+ * migration below does not cover. Drain them here, before the early
+ * returns; the re-issue lands on this CPU.
+ */
+ rcu_defer_drain(&rdp->defer_work);
+ /* Likewise for the outgoing CPU's deferred call_srcu() callbacks. */
+ srcu_offline_drain(cpu);
+
if (rcu_rdp_is_offloaded(rdp))
return;
diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h
index 9b85eeaa0071a..41c2b20f0c83e 100644
--- a/kernel/rcu/tree.h
+++ b/kernel/rcu/tree.h
@@ -229,6 +229,11 @@ struct rcu_data {
struct rcu_head barrier_head;
int exp_watching_snap; /* Double-check need for IPI. */
+ /* Deferral of an NMI/reentrant call_rcu(); see __call_rcu_common(). */
+ struct llist_head defer_head;
+ struct irq_work defer_work;
+ raw_spinlock_t defer_lock;
+
/* 5) Callback offloading. */
#ifdef CONFIG_RCU_NOCB_CPU
struct swait_queue_head nocb_cb_wq; /* For nocb kthreads to sleep on. */
diff --git a/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c
new file mode 100644
index 0000000000000..f6ecd93be30f4
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Exercise re-entry into call_srcu() from BPF; see progs/rcu_reentry.c. */
+#define _GNU_SOURCE
+#include <sched.h>
+#include <sys/syscall.h>
+#include <test_progs.h>
+#include "rcu_reentry.skel.h"
+
+static int sys_pidfd_open(pid_t pid, unsigned int flags)
+{
+ return syscall(__NR_pidfd_open, pid, flags);
+}
+
+void test_rcu_reentry(void)
+{
+ struct rcu_reentry *skel;
+ int err, pidfd = -1, map_fd;
+ __u64 val = 1;
+ cpu_set_t set;
+
+ skel = rcu_reentry__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ return;
+
+ err = rcu_reentry__attach(skel);
+ if (!ASSERT_OK(err, "skel_attach"))
+ goto out;
+
+ /* Keep the re-entry on a single CPU. */
+ CPU_ZERO(&set);
+ CPU_SET(0, &set);
+ if (sched_setaffinity(0, sizeof(set), &set))
+ perror("sched_setaffinity");
+
+ pidfd = sys_pidfd_open(getpid(), 0);
+ if (!ASSERT_GE(pidfd, 0, "pidfd_open"))
+ goto out;
+ map_fd = bpf_map__fd(skel->maps.task_stg);
+ err = bpf_map_update_elem(map_fd, &pidfd, &val, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "boot_create"))
+ goto out;
+
+ /* Arm the handler for this thread, then trigger call_rcu_tasks_trace(). */
+ skel->bss->target_pid = syscall(__NR_gettid);
+ err = bpf_map_delete_elem(map_fd, &pidfd);
+ ASSERT_OK(err, "boot_delete");
+
+ /* Only Tree SRCU enqueues via rcu_segcblist_enqueue(); skip elsewhere. */
+ if (!skel->bss->hits) {
+ test__skip();
+ goto out;
+ }
+ ASSERT_EQ(skel->bss->reentered, 1, "reentry_deferred");
+out:
+ if (pidfd >= 0)
+ close(pidfd);
+ rcu_reentry__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/rcu_reentry.c b/tools/testing/selftests/bpf/progs/rcu_reentry.c
new file mode 100644
index 0000000000000..d92a927ff51c0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/rcu_reentry.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Re-enter call_srcu() from a BPF program. fentry on rcu_segcblist_enqueue()
+ * fires inside call_srcu()'s enqueue (reached from srcu_gp_start_if_needed()
+ * with the srcu_data ->lock held); the handler then calls call_rcu_tasks_trace()
+ * -- itself call_srcu() on rcu_tasks_trace_srcu_struct -- re-entering the same
+ * srcu_data on the same CPU.
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __type(key, int);
+ __type(value, __u64);
+} task_stg SEC(".maps");
+
+int target_pid;
+int hits;
+int reentered;
+
+SEC("fentry/rcu_segcblist_enqueue")
+int BPF_PROG(reenter)
+{
+ struct task_struct *cur;
+
+ if (reentered || !target_pid)
+ return 0;
+
+ cur = bpf_get_current_task_btf();
+ if (!cur || cur->pid != target_pid)
+ return 0;
+
+ /* Re-enter via a task-storage delete, which calls call_rcu_tasks_trace(). */
+ __sync_fetch_and_add(&hits, 1);
+ bpf_task_storage_get(&task_stg, cur, 0, BPF_LOCAL_STORAGE_GET_F_CREATE);
+ bpf_task_storage_delete(&task_stg, cur);
+
+ reentered = 1;
+ return 0;
+}
|
| 3/2 | 2026/07/31 01:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16333 output: 27 thoughts: 713 Thoughts: **RCU Deferral Patch Analysis** Okay, so I'm looking at this RCU patch. It's making changes to SRCU and the general RCU deferral mechanisms, specifically with the introduction of `CONFIG_RCU_DEFER`. This new config option is enabled by default if we're dealing with NMI, KPROBES, the function tracer, or tracepoints. Seems like they're adding deferral for `call_rcu()` and `call_srcu()` when interrupts are disabled, which makes sense to avoid corrupting callback lists during manipulation. The patch touches a good chunk of RCU code – `srcutiny.c`, `srcutree.c`, `tiny.c`, `tree.c`, and even the torture test. Plus, they've added a new BPF selftest, `rcu_reentry.c`, which is a good sign of thoroughness. Alright, let's drill down into the specifics. I can see `CONFIG_RCU_DEFER` is a core kernel modification. Pretty reachable too, considering how heavily used `call_rcu` and `call_srcu` are. The deferral logic itself kicks in when `should_rcu_defer()` is true; in other words, when `irqs_disabled()` and `rcu_scheduler_active != RCU_SCHEDULER_INACTIVE`. My immediate thought is to identify key functions for fuzzing. I've got a preliminary list: `srcu_defer_drain`, `rcu_defer_drain`, `call_srcu`, `call_rcu`, `srcu_barrier`, `rcu_barrier`, `srcu_offline_drain`, and `rcutree_migrate_callbacks`. But wait, the instructions say to avoid super hot-path functions to get a more balanced test distribution. That rules out `call_rcu` and `call_srcu`, even though they are tempting targets. I'll need to re-evaluate what is actually a valuable fuzzing target. Let's look at `srcu_defer_drain` and `rcu_defer_drain`, and determine whether they're static or exported. Turns out `srcu_defer_drain` is exported in `srcutiny.c` but static in `srcutree.c`, while `rcu_defer_drain` is static in both `tiny.c` and `tree.c`. Hmm, a slight inconsistency. Now, what about the other functions on my list? Let's check `srcu_offline_drain` and `rcutree_migrate_callbacks`. Neither of these is static. This looks promising. I will confirm that `srcu_offline_drain` and `rcutree_migrate_callbacks` are the right target functions with grepper. |
| 4/2 | 2026/07/31 01:14 | tool | grepper |
0mArgs:{
"Expression": "^(void|int|bool) srcu_offline_drain"
}Results: {
"Output": "kernel/rcu/rcu.h=582=static inline bool should_rcu_defer(void)\n--\nkernel/rcu/rcu.h-590-/* Drain an outgoing CPU's deferred SRCU callbacks; see rcutree_migrate_callbacks(). */\nkernel/rcu/rcu.h:591:void srcu_offline_drain(int cpu);\nkernel/rcu/rcu.h-592-\n--\nkernel/rcu/srcutree.c=1515=static void srcu_defer_drain(struct irq_work *iw)\n--\nkernel/rcu/srcutree.c-1542- */\nkernel/rcu/srcutree.c:1543:void srcu_offline_drain(int cpu)\nkernel/rcu/srcutree.c-1544-{\n"
} |
| 5/2 | 2026/07/31 01:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17899 output: 28 thoughts: 13 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing the `rcutree_migrate_callbacks` Function** Alright, let's dive into this `rcutree_migrate_callbacks` function. I need to refresh my memory on the specifics of how this particular migration is handled within the larger RCU tree context. The name alone tells me it's likely dealing with callback migration during RCU tree modifications, which is a critical area for ensuring data consistency and avoiding races, especially under heavy load. I want to quickly verify if the implementation aligns with my understanding of the RCU guarantees, specifically concerning how grace periods are managed in conjunction with the callbacks being moved. Are we taking into account potential concurrent modifications to the tree while migrating? I need to look closely at the locks or atomic operations involved and see how they interplay with the grace period logic to verify that we are indeed preventing memory corruption and preserving the semantics of RCU's quiescent state. I'm especially interested in whether the callbacks themselves are being handled atomically, and if not, how the system is managing that complexity. I'll focus on potential edge cases, such as recursive callback invocation or the handling of expedited grace periods during a migration. I'm also going to check if there are any subtle inefficiencies here, such as unnecessary locking or unnecessary barriers. |
| 6/2 | 2026/07/31 01:14 | tool | grepper |
0mArgs:{
"Expression": "^(void|int|bool) rcutree_migrate_callbacks"
}Results: {
"Output": "include/linux/rcutree.h=110=int rcutree_offline_cpu(unsigned int cpu);\n--\ninclude/linux/rcutree.h-116-\ninclude/linux/rcutree.h:117:void rcutree_migrate_callbacks(int cpu);\ninclude/linux/rcutree.h-118-\n--\nkernel/rcu/tree.c=4560=void rcutree_report_cpu_dead(void)\n--\nkernel/rcu/tree.c-4614- */\nkernel/rcu/tree.c:4615:void rcutree_migrate_callbacks(int cpu)\nkernel/rcu/tree.c-4616-{\n"
} |
| 7/2 | 2026/07/31 01:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18408 output: 25 thoughts: 16 Thoughts: Okay, here's my attempt at summarizing those thoughts from an expert's perspective, using a first-person perspective: **Barriers, Barriers Everywhere!** Alright, so I'm thinking about memory barriers, particularly in the context of RCU (Read-Copy-Update) and SRCU (Sleepable RCU) synchronization primitives. When I'm working with these, the question of synchronization is always present, but the details change, depending on which RCU flavor I'm dealing with. Specifically, I'm trying to solidify the connection with `rcu_barrier` and `srcu_barrier`. These functions are critical for ensuring that updates performed within an RCU critical section become visible to readers *after* the grace period. Understanding the nuances of their implementations, and how they interact with the underlying memory model (like, considering different architectures), is key. My focus is to ensure that I've accounted for every possible scenario where data races are avoided or handled correctly. I need to make sure I'm not missing anything when using these particular barrier functions. |
| 8/2 | 2026/07/31 01:14 | tool | grepper |
0mArgs:{
"Expression": "^(void|int|bool) rcu_barrier"
}Results: {
"Output": "include/linux/rcupdate.h=51=void call_rcu(struct rcu_head *head, rcu_callback_t func);\ninclude/linux/rcupdate.h:52:void rcu_barrier_tasks(void);\ninclude/linux/rcupdate.h-53-void synchronize_rcu(void);\n--\ninclude/linux/rcutree.h=36=void synchronize_rcu_expedited(void);\ninclude/linux/rcutree.h-37-\ninclude/linux/rcutree.h:38:void rcu_barrier(void);\ninclude/linux/rcutree.h-39-void rcu_momentary_eqs(void);\n--\nkernel/rcu/tasks.h=1222=EXPORT_SYMBOL_GPL(synchronize_rcu_tasks);\n--\nkernel/rcu/tasks.h-1229- */\nkernel/rcu/tasks.h:1230:void rcu_barrier_tasks(void)\nkernel/rcu/tasks.h-1231-{\n--\nkernel/rcu/tiny.c=113=static void rcu_defer_flush(void)\n--\nkernel/rcu/tiny.c-119-\nkernel/rcu/tiny.c:120:void rcu_barrier(void)\nkernel/rcu/tiny.c-121-{\n--\nkernel/rcu/tree.c=3951=static void rcu_barrier_handler(void *cpu_in)\n--\nkernel/rcu/tree.c-3976- */\nkernel/rcu/tree.c:3977:void rcu_barrier(void)\nkernel/rcu/tree.c-3978-{\n"
} |
| 9/2 | 2026/07/31 01:14 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19111 output: 25 thoughts: 8 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **SRCU Barrier Musings** Right, so I'm thinking about `srcu_barrier`... what's really going on with that beast? We've got our grace period logic, the read-side critical sections, and then this barrier function. I'm trying to mentally map out the implications of its usage, particularly how it interacts with the RCU read-side critical sections and what guarantees it *truly* provides. I'm picturing how `srcu_barrier` ensures that all existing readers have exited their read-side critical sections before the function returns. That implies a synchronization point, but the implementation details are the crux. Is it a full memory barrier? Is there any specific ordering enforced? Does it interact with the CPU cache coherency at all, or is that handled implicitly by the RCU infrastructure itself? I'm focusing on the subtle race conditions, thinking about how it prevents data races between readers and writers. I'm wondering if there's any room for optimization there without compromising the overall RCU integrity. Are there potential pitfalls in how it handles nested read-side critical sections or potential for any edge case scenarios that could still lead to issues? I'm assuming that the kernel developers have thought of these things, but I like to review the code to be sure, in my own mind. |
| 10/2 | 2026/07/31 01:15 | tool | grepper |
0mArgs:{
"Expression": "^(void|int|bool) srcu_barrier"
}Results: {
"Output": "include/linux/srcutiny.h=135=static inline void synchronize_srcu_expedited(struct srcu_struct *ssp)\n--\ninclude/linux/srcutiny.h-139-\ninclude/linux/srcutiny.h:140:void srcu_barrier(struct srcu_struct *ssp);\ninclude/linux/srcutiny.h-141-\n--\ninclude/linux/srcutree.h=242=void synchronize_srcu_expedited(struct srcu_struct *ssp);\ninclude/linux/srcutree.h:243:void srcu_barrier(struct srcu_struct *ssp);\ninclude/linux/srcutree.h-244-void srcu_expedite_current(struct srcu_struct *ssp);\n--\nkernel/rcu/srcutiny.c=297=EXPORT_SYMBOL_GPL(synchronize_srcu);\n--\nkernel/rcu/srcutiny.c-299-/* Register any deferred callbacks, then wait for all in-flight ones. */\nkernel/rcu/srcutiny.c:300:void srcu_barrier(struct srcu_struct *ssp)\nkernel/rcu/srcutiny.c-301-{\n--\nkernel/rcu/srcutree.c=1782=static void srcu_barrier_one_cpu(struct srcu_struct *ssp, struct srcu_data *sdp)\n--\nkernel/rcu/srcutree.c-1799- */\nkernel/rcu/srcutree.c:1800:void srcu_barrier(struct srcu_struct *ssp)\nkernel/rcu/srcutree.c-1801-{\n"
} |
| 11/2 | 2026/07/31 01:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19886 output: 20 thoughts: 25 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Examining a New RCU Configuration Option** Okay, so this patch introduces `CONFIG_RCU_DEFER`. My first instinct, as always, is to dive right into the source. I need to understand what this new flag *means* for the RCU implementation. Let's go straight to the `kernel/rcu/Kconfig` file. I'll be looking for the dependencies, the help text, and where this option fits into the broader picture of RCU configuration choices. Is this related to grace periods, expedited RCU, or a specific flavor of deferred reclamation? Is it a compile-time optimization, or something that alters runtime behavior significantly? The Kconfig file is the place to start building a mental model of this new piece of the RCU puzzle. I'll need to see if it makes sense in the context of my current understanding of the RCU subsystem. From there I can determine the impact of enabling or disabling this feature. |
| 12/2 | 2026/07/31 01:15 | tool | grepper |
0mArgs:{
"Expression": "config RCU_DEFER"
}Results: {
"Output": "kernel/rcu/Kconfig=175=config RCU_NEED_SEGCBLIST\n--\nkernel/rcu/Kconfig-179-# call_srcu() can be invoked while a callback-list operation is in flight.\nkernel/rcu/Kconfig:180:config RCU_DEFER\nkernel/rcu/Kconfig-181-\tdef_bool HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS\n"
} |
| 13/2 | 2026/07/31 01:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20269 output: 24 thoughts: 292 Thoughts: **RCU_DEFER Configuration Deep Dive** Okay, so `CONFIG_RCU_DEFER` is automatically set based on some other configurations. Specifically, it's defined as a boolean expression: `def_bool HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS`. Right, so this isn't something I can explicitly enable in a user-facing `.config` file directly, as it has no prompt. It's determined by the presence of `HAVE_NMI`, `KPROBES`, `FUNCTION_TRACER`, or `TRACEPOINTS`. Since `HAVE_NMI` is pretty common on x86, just listing `RCU_DEFER` is redundant. I can enable `KPROBES`, `FUNCTION_TRACER`, or `TRACEPOINTS` to ensure `RCU_DEFER` gets enabled. Wait a second… the instructions say to list items *without* the `CONFIG_` prefix in the output array. If it's `def_bool HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS`, I'm going to list `KPROBES` or `FUNCTION_TRACER` to be safe and avoid issues if `HAVE_NMI` is already enabled. This should be sufficient for the required output. Let's quickly check `rcu_torture_nmi_init` which is in `kernel/rcu/rcutorture.c` to see if it brings in `CONFIG_PERF_EVENTS`. Okay, it uses `CONFIG_PERF_EVENTS`. |
| 14/2 | 2026/07/31 01:15 | tool | grepper |
0mArgs:{
"Expression": "rcu_torture_nmi_init"
}Results: {
"Output": "kernel/rcu/rcutorture.c=2696=static void rcu_torture_nmi_overflow(struct perf_event *event,\n--\nkernel/rcu/rcutorture.c-2707-\nkernel/rcu/rcutorture.c:2708:static void rcu_torture_nmi_init(void)\nkernel/rcu/rcutorture.c-2709-{\n--\nkernel/rcu/rcutorture.c=2729=static void rcu_torture_nmi_cleanup(void)\n--\nkernel/rcu/rcutorture.c-2744-#else /* #ifdef CONFIG_PERF_EVENTS */\nkernel/rcu/rcutorture.c:2745:static void rcu_torture_nmi_init(void) { }\nkernel/rcu/rcutorture.c-2746-static void rcu_torture_nmi_cleanup(void) { }\n--\nkernel/rcu/rcutorture.c=4744=rcu_torture_init(void)\n--\nkernel/rcu/rcutorture.c-4892-\t/* Arm the per-CPU perf counters that drive -\u003ecall() from NMI. */\nkernel/rcu/rcutorture.c:4893:\trcu_torture_nmi_init();\nkernel/rcu/rcutorture.c-4894-\tfor (i = 0; i \u003c nrealreaders; i++) {\n"
} |
| 15/2 | 2026/07/31 01:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21324 output: 24 thoughts: 279 Thoughts: **My Analysis of RCU Torture Testing and Deferral Logic** Okay, so I'm thinking about how to effectively test the Non-Maskable Interrupt (NMI) path in `rcutorture`. It's clear that I need `PERF_EVENTS` enabled, and, of course, the `RCU_TORTURE_TEST` configuration option. However, `rcutorture` itself is a test module; I don't necessarily need to focus on fuzzing *it* directly. The more valuable target for fuzzing, I think, is the core RCU deferral logic. That’s where the real potential vulnerabilities will be. The deferral logic is, of course, central to `call_rcu` and `call_srcu`, and those are called *everywhere*. The specific functions that actually *handle* the deferred callbacks are things like: `srcu_defer_drain`, `rcu_defer_drain`, `srcu_offline_drain`, `rcutree_migrate_callbacks`, `rcu_barrier`, and `srcu_barrier`. Wait a minute... I'm noticing something. `rcu_defer_drain` and `srcu_defer_drain` are *static* within `tree.c` and `srcutree.c`. That’s significant. Hmm, *srcu_defer_drain* is exported from `srcutiny.c`, but it's static in `srcutree.c`. Since `srcutree.c` is the default implementation for SMP systems, it means `srcu_defer_drain` is static in the most common case. That implies limited access for fuzzing. I'll need to check if `srcu_offline_drain` is exported as well. This might influence my fuzzing strategy and test design. |
| 16/2 | 2026/07/31 01:15 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*srcu_offline_drain"
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/07/31 01:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22035 output: 22 thoughts: 64 Thoughts: Okay, here's my take on those thoughts, framed as my own reasoning: **Analyzing Kernel Synchronization Primitives** Alright, so the thing isn't *statically* defined, but it's not simply an internal, static construct either. Intriguing. Let's dig deeper: specifically, `rcutree_migrate_callbacks` isn't static, meaning it's potentially visible beyond its immediate scope. That's a key detail. I need to understand its relationship to the broader system. Now, at least I *do* know that `rcu_barrier` and `srcu_barrier` are, in fact, exported. That simplifies things a bit, providing some concrete points of reference. The next logical step is to verify the export status of `rcu_barrier` and `srcu_barrier` and understand how they are being used. It's time to check their export status and what that implies in terms of kernel design and dependency. Knowing their interface is key to understanding how they're being used. |
| 18/2 | 2026/07/31 01:15 | tool | grepper |
0mArgs:{
"Expression": "EXPORT_SYMBOL.*rcu_barrier"
}Results: {
"Output": "kernel/rcu/srcutiny.c=300=void srcu_barrier(struct srcu_struct *ssp)\n--\nkernel/rcu/srcutiny.c-305-}\nkernel/rcu/srcutiny.c:306:EXPORT_SYMBOL_GPL(srcu_barrier);\nkernel/rcu/srcutiny.c-307-\n--\nkernel/rcu/srcutree.c=1800=void srcu_barrier(struct srcu_struct *ssp)\n--\nkernel/rcu/srcutree.c-1842-}\nkernel/rcu/srcutree.c:1843:EXPORT_SYMBOL_GPL(srcu_barrier);\nkernel/rcu/srcutree.c-1844-\n--\nkernel/rcu/tasks.h=1230=void rcu_barrier_tasks(void)\n--\nkernel/rcu/tasks.h-1233-}\nkernel/rcu/tasks.h:1234:EXPORT_SYMBOL_GPL(rcu_barrier_tasks);\nkernel/rcu/tasks.h-1235-\n--\nkernel/rcu/tiny.c=120=void rcu_barrier(void)\n--\nkernel/rcu/tiny.c-125-}\nkernel/rcu/tiny.c:126:EXPORT_SYMBOL(rcu_barrier);\nkernel/rcu/tiny.c-127-\n--\nkernel/rcu/tree.c=3977=void rcu_barrier(void)\n--\nkernel/rcu/tree.c-4074-}\nkernel/rcu/tree.c:4075:EXPORT_SYMBOL_GPL(rcu_barrier);\nkernel/rcu/tree.c-4076-\n--\nmm/slab_common.c=1343=void kvfree_rcu_barrier(void)\n--\nmm/slab_common.c-1347-}\nmm/slab_common.c:1348:EXPORT_SYMBOL_GPL(kvfree_rcu_barrier);\nmm/slab_common.c-1349-\nmm/slab_common.c=1350=void kvfree_rcu_barrier_on_cache(struct kmem_cache *s)\n--\nmm/slab_common.c-1354-}\nmm/slab_common.c:1355:EXPORT_SYMBOL_GPL(kvfree_rcu_barrier_on_cache);\nmm/slab_common.c-1356-\n--\nmm/slab_common.c=2177=void kvfree_rcu_barrier(void)\n--\nmm/slab_common.c-2181-}\nmm/slab_common.c:2182:EXPORT_SYMBOL_GPL(kvfree_rcu_barrier);\nmm/slab_common.c-2183-\n--\nmm/slab_common.c=2191=void kvfree_rcu_barrier_on_cache(struct kmem_cache *s)\n--\nmm/slab_common.c-2204-}\nmm/slab_common.c:2205:EXPORT_SYMBOL_GPL(kvfree_rcu_barrier_on_cache);\nmm/slab_common.c-2206-\n"
} |
| 19/2 | 2026/07/31 01:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23163 output: 21 thoughts: 437 Thoughts: **My Current Line of Reasoning** Okay, so I'm looking at how to target some RCU deferral logic, specifically related to functions that interact with deferred RCU flushing. My initial thought was that `rcu_barrier` and `srcu_barrier` seemed like good targets since they're exported and I saw they were modified to call `rcu_defer_flush()` and `srcu_defer_flush()`. Wait, that's not right, those `_defer_flush` functions are static, so that approach won't work. But, I see `rcutree_migrate_callbacks`, which is called during CPU hotplug and that's interesting. Also, `srcu_offline_drain` is also called during CPU hotplug, so those two are potential targets. I remember the `rcu_reentry.c` selftest, and it uses BPF to trigger `call_rcu_tasks_trace` from `rcu_segcblist_enqueue` via `bpf_task_storage_delete`. That suggests that BPF can trigger this deferral logic, which means fuzzing BPF syscalls and maps is a good avenue to explore. The good thing is that I don't need to specify BPF functions, just focus on the RCU functions. So, here's the core set of symbols I'm focusing on: `rcu_barrier`, `srcu_barrier`, `rcutree_migrate_callbacks`, and `srcu_offline_drain`. However, now I remember that `rcutree_migrate_callbacks` and `srcu_offline_drain` aren't syscalls; they're internal kernel functions. Syzkaller can't directly call them. However, it *can* trigger CPU hotplug, which, in turn, calls these functions. Syzkaller *can* directly call `rcu_barrier` and `srcu_barrier` via various syscalls or if we add them directly to syzlang, but they're probably already reachable through many other existing paths. The key insight is this: even if I just list `rcu_barrier`, `srcu_barrier`, `rcutree_migrate_callbacks`, and `srcu_offline_drain` as targets, Syzkaller's coverage guidance will then try to reach them. That might be the simplest approach. I need to check how to reach `rcutree_migrate_callbacks` and confirm my assumptions. |
| 20/2 | 2026/07/31 01:15 | tool | grepper |
0mArgs:{
"Expression": "rcutree_migrate_callbacks"
}Results: {
"Output": "Documentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst=294=final code path on the right is taken only in kernels built with\n--\nDocumentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst-296-``rcu_advance_cbs()``, ``rcu_migrate_callbacks``,\nDocumentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst:297:``rcutree_migrate_callbacks()``, and ``takedown_cpu()``, which in turn\nDocumentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst-298-is invoked on a surviving CPU after the outgoing CPU has been completely\n--\nDocumentation/RCU/Design/Memory-Ordering/TreeRCU-callback-registry.svg-565- id=\"text202-7-9-6\"\nDocumentation/RCU/Design/Memory-Ordering/TreeRCU-callback-registry.svg:566: style=\"font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-fam...\nDocumentation/RCU/Design/Memory-Ordering/TreeRCU-callback-registry.svg-567- \u003ctext\n--\nDocumentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg-1447- id=\"text202-7-9-6\"\nDocumentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg:1448: style=\"font-size:192px;font-style:normal;font-weight:bold;text-anchor:start;fill:#000000;stroke-width:0.025in;font-family:Courier\"\u003er...\nDocumentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg-1449- \u003ctext\n--\ninclude/linux/interrupt.h=550=enum\n--\ninclude/linux/interrupt.h-569- * _ RCU:\ninclude/linux/interrupt.h:570: * \t1) rcutree_migrate_callbacks() migrates the queue.\ninclude/linux/interrupt.h-571- * \t2) rcutree_report_cpu_dead() reports the final quiescent states.\n--\ninclude/linux/rcutree.h=110=int rcutree_offline_cpu(unsigned int cpu);\n--\ninclude/linux/rcutree.h-116-\ninclude/linux/rcutree.h:117:void rcutree_migrate_callbacks(int cpu);\ninclude/linux/rcutree.h-118-\n--\nkernel/cpu.c=1302=static int takedown_cpu(unsigned int cpu)\n--\nkernel/cpu.c-1351-\t */\nkernel/cpu.c:1352:\trcutree_migrate_callbacks(cpu);\nkernel/cpu.c-1353-\n--\nkernel/rcu/rcu.h=582=static inline bool should_rcu_defer(void)\n--\nkernel/rcu/rcu.h-589-\nkernel/rcu/rcu.h:590:/* Drain an outgoing CPU's deferred SRCU callbacks; see rcutree_migrate_callbacks(). */\nkernel/rcu/rcu.h-591-void srcu_offline_drain(int cpu);\n--\nkernel/rcu/srcutree.c=1515=static void srcu_defer_drain(struct irq_work *iw)\n--\nkernel/rcu/srcutree.c-1538- * Drain @cpu's deferred call_srcu() callbacks; called from\nkernel/rcu/srcutree.c:1539: * rcutree_migrate_callbacks() once @cpu is dead. One pass covers every\nkernel/rcu/srcutree.c-1540- * srcu_struct, as the per-CPU srcu_defer list chains them all, and the\n--\nkernel/rcu/tree.c=3166=static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func, bool lazy_in)\n--\nkernel/rcu/tree.c-3219- * the drainers -- this CPU's irq_work, rcu_defer_flush() and\nkernel/rcu/tree.c:3220: * rcutree_migrate_callbacks() -- serialize and never leave a callback off\nkernel/rcu/tree.c-3221- * -\u003edefer_head yet not on a callback list.\n--\nkernel/rcu/tree.c=4560=void rcutree_report_cpu_dead(void)\n--\nkernel/rcu/tree.c-4614- */\nkernel/rcu/tree.c:4615:void rcutree_migrate_callbacks(int cpu)\nkernel/rcu/tree.c-4616-{\n"
} |
| 21/2 | 2026/07/31 01:15 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25406 output: 23 thoughts: 469 Thoughts: **Analyzing RCU Code for Syzkaller Testing** Okay, so `rcutree_migrate_callbacks` is definitely called from `takedown_cpu` within `kernel/cpu.c`. That's in the CPU hotplug path, which makes it a solid target. Now, let's explore other suitable functions. `call_rcu_defer` is internal to the deferral logic. And because it's static it's not ideal. Same with `srcu_defer_drain` and `rcu_defer_drain`, they're the irq_work handlers. But, hold on... `rcu_defer_drain` is passed as a function pointer to `IRQ_WORK_INIT_HARD`, which means it isn't inlined! Excellent, that makes it more accessible. Following the instructions, I need to focus on non-static, non-inlined API entrypoints. So, `rcu_barrier`, `srcu_barrier`, `rcutree_migrate_callbacks`, and `srcu_offline_drain` look like good options. Now, about `CONFIG_RCU_DEFER`. It depends on `HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS`. To ensure it's enabled, I'll need to add `KPROBES` and `FUNCTION_TRACER` to the `EnableConfigs`. Also, `PERF_EVENTS` is necessary for `rcu_torture_nmi_init`. And of course, I need `RCU_TORTURE_TEST` to run the torture tests. But, `RCU_TORTURE_TEST` is a module. Syzkaller doesn't load it by default unless it's built-in or explicitly loaded. The instructions say to list configuration flags needed to compile and test the *modified* code. So, the right configuration options are `KPROBES`, `FUNCTION_TRACER`, `PERF_EVENTS`, and `RCU_TORTURE_TEST`. Gotta double-check that `RCU_TORTURE_TEST` in `kernel/rcu/Kconfig`. |
| 22/2 | 2026/07/31 01:15 | tool | grepper |
0mArgs:{
"Expression": "config RCU_TORTURE_TEST"
}Results: {
"Output": "kernel/rcu/Kconfig.debug=26=config RCU_SCALE_TEST\n--\nkernel/rcu/Kconfig.debug-40-\nkernel/rcu/Kconfig.debug:41:config RCU_TORTURE_TEST\nkernel/rcu/Kconfig.debug-42-\ttristate \"torture tests for RCU\"\n--\nkernel/rcu/Kconfig.debug-55-\nkernel/rcu/Kconfig.debug:56:config RCU_TORTURE_TEST_CHK_RDR_STATE\nkernel/rcu/Kconfig.debug-57-\tbool \"Check rcutorture reader state\"\n--\nkernel/rcu/Kconfig.debug-71-\nkernel/rcu/Kconfig.debug:72:config RCU_TORTURE_TEST_LOG_CPU\nkernel/rcu/Kconfig.debug-73-\tbool \"Log CPU for rcutorture failures\"\n--\nkernel/rcu/Kconfig.debug-86-\nkernel/rcu/Kconfig.debug:87:config RCU_TORTURE_TEST_LOG_GP\nkernel/rcu/Kconfig.debug-88-\tbool \"Log grace-period numbers for rcutorture failures\"\n"
} |
| 23/2 | 2026/07/31 01:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26673 output: 19 thoughts: 31 Thoughts: Here's my take on the thought process, as if it were my own: **Investigating a Dependency Chain** Okay, so `RCU_TORTURE_TEST` is flagging a dependency on `TORTURE_TEST`. That's a common pattern in the framework; one test often builds upon another. I need to understand what's happening with `TORTURE_TEST` to figure out why `RCU_TORTURE_TEST` might be failing, or behaving in an unexpected manner. Time to dive into the specifics of `TORTURE_TEST`. Let's see what's going on there. |
| 24/2 | 2026/07/31 01:16 | tool | grepper |
0mArgs:{
"Expression": "config TORTURE_TEST"
}Results: {
"Output": "kernel/rcu/Kconfig.debug=11=config PROVE_RCU_LIST\n--\nkernel/rcu/Kconfig.debug-21-\nkernel/rcu/Kconfig.debug:22:config TORTURE_TEST\nkernel/rcu/Kconfig.debug-23-\ttristate\n"
} |
| 25/2 | 2026/07/31 01:16 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26945 output: 316 thoughts: 2152 Thoughts: **My Analysis of the RCU Deferral Patch** Alright, let's break this down. First, the task is to identify relevant configuration options and key functions affected by this patch. I see that `TORTURE_TEST` is a tristate, and enabled by `RCU_TORTURE_TEST`. So, enabling `RCU_TORTURE_TEST` is the right thing to do. The BPF test utilizes `BPF_SYSCALL` and `BPF_JIT`. The presence of a new BPF self-test, specifically `tools/testing/selftests/bpf/prog_tests/rcu_reentry.c`, screams that BPF is a great avenue for testing this. So, `BPF_SYSCALL` is definitely a key config to enable. Now, let's identify relevant functions. The goal here is to find entry points that trigger the new deferred callback logic but aren't hot paths, to avoid skewed test distributions. My initial thoughts are on `rcu_barrier`, `srcu_barrier`, `rcutree_migrate_callbacks`, and `srcu_offline_drain`. Are they hot paths? No, no, no, and no. This looks promising. `call_rcu` and `call_srcu` are the main entry points, but they are generic hot-path functions, so those are out. The goal is to flush/migrate the new deferral callbacks. I'm thinking: `srcu_defer_drain` is exported in `srcutiny.c`, but static in `srcutree.c`. `srcu_offline_drain` is not static in `srcutree.c`, so it's a better choice. `rcu_barrier` calls `rcu_defer_flush()`, which is static, so `rcu_barrier` is a good target. Likewise, `srcu_barrier` calls static `srcu_defer_flush()`, so `srcu_barrier` is good. `rcutree_migrate_callbacks` calls `rcu_defer_drain` and `srcu_offline_drain`, also looks good. `srcu_offline_drain` itself calls `srcu_defer_drain`. The list: - `rcu_barrier` - `srcu_barrier` - `rcutree_migrate_callbacks` - `srcu_offline_drain` Seems pretty solid. Config-wise, I've got: `KPROBES`, `FUNCTION_TRACER`, `PERF_EVENTS`, `RCU_TORTURE_TEST`, and `BPF_SYSCALL`. Ah, but wait! `CONFIG_RCU_DEFER` is defined in terms of `HAVE_NMI`, `KPROBES`, `FUNCTION_TRACER`, and `TRACEPOINTS`. So, to make sure it's enabled, I need to list those. `HAVE_NMI` is usually architecture-specific, but to be absolutely sure, `KPROBES`, `FUNCTION_TRACER`, and `TRACEPOINTS` cover us. `RCU_TORTURE_TEST` is a module. While it could introduce some noise if it's built-in, the patch *modifies* `rcutorture.c`, adding NMI testing, so enabling it seems correct. And `BPF_SYSCALL` is critical because BPF is a primary driver of this new logic, and it triggers `perf_event_create_kernel_counter` in `rcutorture.c` so `PERF_EVENTS` is needed. The reasoning is pretty straightforward: This patch introduces a deferral mechanism for `call_rcu()` and `call_srcu()` when interrupts are disabled, notably for NMIs and BPF programs. It changes core RCU and SRCU logic, adding new deferred callback lists and irq_work queues. The patch also includes NMI testing in `rcutorture` and a BPF selftest. This is a functional change to core kernel synchronization primitives and warrants fuzzing. `rcu_barrier`, `srcu_barrier`, `rcutree_migrate_callbacks`, and `srcu_offline_drain` are good focus symbols because they're non-hotpath entry points that flush or migrate the new deferred lists. `KPROBES`, `FUNCTION_TRACER`, and `TRACEPOINTS` enable `CONFIG_RCU_DEFER`, `PERF_EVENTS` and `RCU_TORTURE_TEST` cover the NMI tests, and `BPF_SYSCALL` is necessary because BPF is a primary user. Let's quickly refine the focus symbols, which is |
| 26/2 | 2026/07/31 01:16 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"KPROBES",
"FUNCTION_TRACER",
"TRACEPOINTS",
"PERF_EVENTS",
"RCU_TORTURE_TEST",
"BPF_SYSCALL"
],
"FocusSymbols": [
"rcu_barrier",
"srcu_barrier",
"rcutree_migrate_callbacks",
"srcu_offline_drain"
],
"Reasoning": "The patch introduces a deferral mechanism for call_rcu() and call_srcu() to safely handle callback enqueues when interrupts are disabled (e.g., from NMIs or BPF programs). It adds lockless lists and irq_work to defer the actual enqueue until it is safe. It also updates rcu_barrier(), srcu_barrier(), and CPU hotplug paths to flush these deferred callbacks. Additionally, it adds NMI testing to rcutorture using perf events, and a BPF selftest. This is a significant functional change to core RCU logic and should be fuzzed. Focus symbols include the barrier and CPU offline functions that drain the new deferred lists. Required configs include KPROBES/FUNCTION_TRACER/TRACEPOINTS to enable CONFIG_RCU_DEFER, PERF_EVENTS and RCU_TORTURE_TEST for the rcutorture changes, and BPF_SYSCALL as BPF is a primary trigger for this code.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"KPROBES",
"FUNCTION_TRACER",
"TRACEPOINTS",
"PERF_EVENTS",
"RCU_TORTURE_TEST",
"BPF_SYSCALL"
],
"FocusSymbols": [
"rcu_barrier",
"srcu_barrier",
"rcutree_migrate_callbacks",
"srcu_offline_drain"
],
"Reasoning": "The patch introduces a deferral mechanism for call_rcu() and call_srcu() to safely handle callback enqueues when interrupts are disabled (e.g., from NMIs or BPF programs). It adds lockless lists and irq_work to defer the actual enqueue until it is safe. It also updates rcu_barrier(), srcu_barrier(), and CPU hotplug paths to flush these deferred callbacks. Additionally, it adds NMI testing to rcutorture using perf events, and a BPF selftest. This is a significant functional change to core RCU logic and should be fuzzed. Focus symbols include the barrier and CPU offline functions that drain the new deferred lists. Required configs include KPROBES/FUNCTION_TRACER/TRACEPOINTS to enable CONFIG_RCU_DEFER, PERF_EVENTS and RCU_TORTURE_TEST for the rcutorture changes, and BPF_SYSCALL as BPF is a primary trigger for this code.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|