From: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: "Paul E . McKenney" <paulmck@kernel.org>
Cc: linux-kernel@vger.kernel.org,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
David Woodhouse <dwmw2@infradead.org>,
Frederic Weisbecker <frederic@kernel.org>,
Neeraj Upadhyay <neeraj.upadhyay@kernel.org>,
Joel Fernandes <joelagnelf@nvidia.com>,
Josh Triplett <josh@joshtriplett.org>,
Boqun Feng <boqun@kernel.org>,
Uladzislau Rezki <urezki@gmail.com>,
Steven Rostedt <rostedt@goodmis.org>,
Lai Jiangshan <jiangshanlai@gmail.com>,
Zqiang <qiang.zhang@linux.dev>,
Davidlohr Bueso <dave@stgolabs.net>,
Kunwu Chan <kunwu.chan@gmail.com>,
Puranjay Mohan <puranjay@kernel.org>,
rcu@vger.kernel.org
Subject: [PATCH 3/4] rcutorture: Run NMI-safe SRCU readers from NMI
Date: Tue, 10 Nov 2026 14:28:43 -0500 [thread overview]
Message-ID: <20261110192844.44713-4-mathieu.desnoyers@efficios.com> (raw)
In-Reply-To: <20261110192844.44713-1-mathieu.desnoyers@efficios.com>
rcutorture exercises ->call() from NMI, but never runs a reader there.
So nothing tests the NMI-safe reader flavors where it matters most: in
an NMI that interrupts another reader during its counter update.
Run a reader from the same per-CPU perf-counter NMI, when every reader
flavor in use is NMI-safe. These flavors are srcu_read_lock_nmisafe(),
srcu_read_lock_fast() and srcu_read_lock_atomic(). A new ops hook,
->nmi_reader_capable(), reports whether the reader flavors in use are
all NMI-safe. A new nmi_readers module parameter, on by default, enables
these NMI readers. The statistics count these readers as nmi-reads.
The reader loads rcu_torture_current and checks its element as the
other readers do. The element must not be freed before the reader ends.
It must not go through two grace periods either.
The NMI may interrupt lockdep, which then does not record the reader.
So the reader loads the pointer with rcu_dereference_raw().
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
---
kernel/rcu/rcutorture.c | 60 +++++++++++++++++++++++++++++++++++++----
1 file changed, 55 insertions(+), 5 deletions(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 968343be4e0d..3b9c08a094cd 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -117,6 +117,7 @@ torture_param(int, n_barrier_cbs, 0, "# of callbacks/kthreads for barrier testin
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(bool, nmi_readers, true, "Run readers from NMI with NMI-safe reader 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");
@@ -220,6 +221,7 @@ 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 atomic_long_t n_rcu_torture_nmi_read;
static long n_barrier_attempts;
static long n_barrier_successes; /* did rcu_barrier test succeed? */
static unsigned long n_read_exits;
@@ -438,6 +440,7 @@ struct rcu_torture_ops {
long cbflood_max;
int irq_capable;
int nmi_capable;
+ bool (*nmi_reader_capable)(void); // Are ->readlock() and ->readunlock() NMI-safe?
int can_boost;
int extendables;
int slow_gps;
@@ -790,6 +793,14 @@ static int srcu_torture_read_lock(void)
return ret;
}
+// Only these reader flavors may be used from NMI.
+static bool srcu_torture_nmi_reader_capable(void)
+{
+ return reader_flavor &&
+ !(reader_flavor & ~(SRCU_READ_FLAVOR_NMI | SRCU_READ_FLAVOR_FAST |
+ SRCU_READ_FLAVOR_ATOMIC));
+}
+
static void
srcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp)
{
@@ -981,6 +992,7 @@ static struct rcu_torture_ops srcu_ops = {
.have_up_down = IS_ENABLED(CONFIG_TINY_SRCU)
? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN,
.nmi_capable = 1,
+ .nmi_reader_capable = srcu_torture_nmi_reader_capable,
.name = "srcu"
};
@@ -1049,6 +1061,7 @@ static struct rcu_torture_ops srcud_ops = {
.have_up_down = IS_ENABLED(CONFIG_TINY_SRCU)
? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN,
.nmi_capable = 1,
+ .nmi_reader_capable = srcu_torture_nmi_reader_capable,
.name = "srcud"
};
@@ -2711,6 +2724,10 @@ static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_timer_rand);
* per-CPU hardware perf counter overflows into an NMI, and its handler submits
* a preallocated callback via ->call(). One callback per CPU is in flight at a
* time (guarded by an atomic) to avoid allocating in NMI.
+ *
+ * The same handler also runs a reader, for flavors whose ->nmi_reader_capable()
+ * says that their readers are NMI-safe. The reader may interrupt another
+ * reader on its CPU, in the middle of its counter update.
*/
#ifdef CONFIG_PERF_EVENTS
static struct perf_event_attr rcu_torture_nmi_attr = {
@@ -2735,6 +2752,8 @@ struct rcu_torture_nmi_cb {
static struct perf_event **rcu_torture_nmi_events;
static int rcu_torture_nmi_hp_state;
+static bool rcu_torture_nmi_do_calls;
+static bool rcu_torture_nmi_do_reads;
static DEFINE_PER_CPU(struct rcu_torture_nmi_cb, rcu_torture_nmi_cb);
static void rcu_torture_nmi_invoked(struct rcu_head *rhp)
@@ -2745,6 +2764,28 @@ static void rcu_torture_nmi_invoked(struct rcu_head *rhp)
atomic_set(&rtncp->inuse, 0);
}
+/*
+ * A reader in NMI. The NMI may interrupt lockdep, which then does not record
+ * the reader, so the pointer is loaded without a lockdep check. The element
+ * must not go through two grace periods before the reader ends.
+ */
+static void rcu_torture_nmi_read(void)
+{
+ struct rcu_torture *p;
+ int idx;
+
+ idx = cur_ops->readlock();
+ p = rcu_dereference_raw(rcu_torture_current);
+ if (p) {
+ if (!READ_ONCE(p->rtort_mbtest))
+ atomic_inc(&n_rcu_torture_mberror);
+ if (READ_ONCE(p->rtort_pipe_count) > 1)
+ atomic_inc(&n_rcu_torture_error);
+ }
+ cur_ops->readunlock(idx);
+ atomic_long_inc(&n_rcu_torture_nmi_read);
+}
+
static void rcu_torture_nmi_overflow(struct perf_event *event,
struct perf_sample_data *data,
struct pt_regs *regs)
@@ -2753,10 +2794,12 @@ static void rcu_torture_nmi_overflow(struct perf_event *event,
if (!in_nmi())
return;
- if (cur_ops->call && !atomic_xchg(&rtncp->inuse, 1)) {
+ if (rcu_torture_nmi_do_calls && !atomic_xchg(&rtncp->inuse, 1)) {
atomic_long_inc(&n_rcu_torture_nmi_call);
cur_ops->call(&rtncp->rh, rcu_torture_nmi_invoked);
}
+ if (rcu_torture_nmi_do_reads)
+ rcu_torture_nmi_read();
}
static int rcu_torture_nmi_online(unsigned int cpu)
@@ -2789,7 +2832,10 @@ static void rcu_torture_nmi_init(void)
{
int ret;
- if (!nmi_calls || !cur_ops->nmi_capable || !cur_ops->call)
+ rcu_torture_nmi_do_calls = nmi_calls && cur_ops->nmi_capable && cur_ops->call;
+ rcu_torture_nmi_do_reads = nmi_readers && cur_ops->nmi_reader_capable &&
+ cur_ops->nmi_reader_capable();
+ if (!rcu_torture_nmi_do_calls && !rcu_torture_nmi_do_reads)
return;
rcu_torture_nmi_events = kcalloc(nr_cpu_ids, sizeof(*rcu_torture_nmi_events),
GFP_KERNEL);
@@ -2815,9 +2861,12 @@ static void rcu_torture_nmi_cleanup(void)
}
kfree(rcu_torture_nmi_events);
rcu_torture_nmi_events = NULL;
- if (!atomic_long_read(&n_rcu_torture_nmi_call))
+ if (rcu_torture_nmi_do_calls && !atomic_long_read(&n_rcu_torture_nmi_call))
pr_alert("%s: nmi_calls set but no ->call() ever issued from NMI, so NMI ->call() went untested (no PMU, or NMIs unavailable here).\n",
__func__);
+ if (rcu_torture_nmi_do_reads && !atomic_long_read(&n_rcu_torture_nmi_read))
+ pr_alert("%s: nmi_readers set but no reader ever ran in NMI, so NMI readers went untested (no PMU, or NMIs unavailable here).\n",
+ __func__);
}
#else /* #ifdef CONFIG_PERF_EVENTS */
static void rcu_torture_nmi_init(void) { }
@@ -3212,9 +3261,10 @@ 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 ",
+ pr_cont("nmi-calls: %ld nmi-cbs: %ld nmi-reads: %ld ",
atomic_long_read(&n_rcu_torture_nmi_call),
- atomic_long_read(&n_rcu_torture_nmi_cb));
+ atomic_long_read(&n_rcu_torture_nmi_cb),
+ atomic_long_read(&n_rcu_torture_nmi_read));
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);
--
2.47.3
next prev parent reply other threads:[~2026-11-10 19:29 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 19:28 [PATCH 0/4] srcu: NMI-safe atomic SRCU readers, and their irqsave forms Mathieu Desnoyers
2026-11-10 19:28 ` [PATCH 1/4] srcu: Make atomic SRCU readers NMI-safe Mathieu Desnoyers
2026-11-10 19:28 ` [PATCH 2/4] srcu: Add irqsave variants of the atomic SRCU readers Mathieu Desnoyers
2026-11-10 19:28 ` Mathieu Desnoyers [this message]
2026-11-10 19:28 ` [PATCH 4/4] rcutorture: Test the irqsave forms of " Mathieu Desnoyers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110192844.44713-4-mathieu.desnoyers@efficios.com \
--to=mathieu.desnoyers@efficios.com \
--cc=boqun@kernel.org \
--cc=dave@stgolabs.net \
--cc=dwmw2@infradead.org \
--cc=frederic@kernel.org \
--cc=jiangshanlai@gmail.com \
--cc=joelagnelf@nvidia.com \
--cc=josh@joshtriplett.org \
--cc=kunwu.chan@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=neeraj.upadhyay@kernel.org \
--cc=paulmck@kernel.org \
--cc=puranjay@kernel.org \
--cc=qiang.zhang@linux.dev \
--cc=rcu@vger.kernel.org \
--cc=rostedt@goodmis.org \
--cc=urezki@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®