* [PATCH 1/5] rcutorture: Add atomic SRCU lockdep support
2026-09-14 9:34 [PATCH 0/5] srcu: Add lockdep coverage for atomic SRCU and fix IRQ-state bug Kunwu Chan
@ 2026-09-14 9:34 ` Kunwu Chan
2026-09-14 9:34 ` [PATCH 2/5] selftests/rcutorture: Wire atomic SRCU into srcu_lockdep.sh Kunwu Chan
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Kunwu Chan @ 2026-09-14 9:34 UTC (permalink / raw)
To: jiangshanlai, paulmck
Cc: josh, rostedt, mathieu.desnoyers, rcu, linux-kernel, Kunwu Chan, Zqiang
Add three testtypes covering atomic SRCU lockdep behavior:
testtype 4: atomic SRCU same-type deadlock
testtype 5: atomic SRCU + raw spinlock dependency cycle
testtype 6: synchronize_srcu_atomic() inside rcu_read_lock()
Co-developed-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
kernel/rcu/rcutorture.c | 84 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 83 insertions(+), 1 deletion(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index d795e07a12ad..ac9c726cb391 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -4662,6 +4662,17 @@ static DECLARE_RWSEM(rwsem7);
static DECLARE_RWSEM(rwsem8);
static DECLARE_RWSEM(rwsem9);
+static DEFINE_RAW_SPINLOCK(spin0);
+static DEFINE_RAW_SPINLOCK(spin1);
+static DEFINE_RAW_SPINLOCK(spin2);
+static DEFINE_RAW_SPINLOCK(spin3);
+static DEFINE_RAW_SPINLOCK(spin4);
+static DEFINE_RAW_SPINLOCK(spin5);
+static DEFINE_RAW_SPINLOCK(spin6);
+static DEFINE_RAW_SPINLOCK(spin7);
+static DEFINE_RAW_SPINLOCK(spin8);
+static DEFINE_RAW_SPINLOCK(spin9);
+
DEFINE_STATIC_SRCU(srcu0);
DEFINE_STATIC_SRCU(srcu1);
DEFINE_STATIC_SRCU(srcu2);
@@ -4673,6 +4684,17 @@ DEFINE_STATIC_SRCU(srcu7);
DEFINE_STATIC_SRCU(srcu8);
DEFINE_STATIC_SRCU(srcu9);
+DEFINE_STATIC_SRCU_ATOMIC(srcu0_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu1_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu2_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu3_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu4_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu5_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu6_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu7_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu8_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu9_atomic);
+
static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, const char *fu, int i,
int cyclelen, int deadlock)
{
@@ -4702,6 +4724,12 @@ static void rcu_torture_init_srcu_lockdep(void)
&rwsem5, &rwsem6, &rwsem7, &rwsem8, &rwsem9 };
struct srcu_struct *srcus[] = { &srcu0, &srcu1, &srcu2, &srcu3, &srcu4,
&srcu5, &srcu6, &srcu7, &srcu8, &srcu9 };
+ raw_spinlock_t *spins[] = { &spin0, &spin1, &spin2, &spin3, &spin4,
+ &spin5, &spin6, &spin7, &spin8, &spin9 };
+ struct srcu_struct *srcus_atomic[] = { &srcu0_atomic, &srcu1_atomic, &srcu2_atomic,
+ &srcu3_atomic, &srcu4_atomic, &srcu5_atomic,
+ &srcu6_atomic, &srcu7_atomic, &srcu8_atomic,
+ &srcu9_atomic };
int testtype;
if (!test_srcu_lockdep)
@@ -4812,11 +4840,65 @@ static void rcu_torture_init_srcu_lockdep(void)
}
#endif // #ifdef CONFIG_TASKS_TRACE_RCU
+ if (testtype == 4) {
+ pr_info("%s: test_srcu_lockdep = %05d: SRCU_ATOMIC %d-way %sdeadlock.\n",
+ __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-");
+ if (deadlock && cyclelen == 1)
+ pr_info("%s: Expect hang.\n", __func__);
+ for (i = 0; i < cyclelen; i++) {
+ j = srcu_lockdep_next(__func__, "srcu_read_lock_atomic",
+ "synchronize_srcu_atomic",
+ "srcu_read_unlock_atomic", i,
+ cyclelen, deadlock);
+ idx = srcu_read_lock_atomic(srcus_atomic[i]);
+ if (j >= 0)
+ synchronize_srcu_atomic(srcus_atomic[j]);
+ srcu_read_unlock_atomic(srcus_atomic[i], idx);
+ }
+ return;
+ }
+
+ if (testtype == 5) {
+ pr_info("%s: test_srcu_lockdep = %05d: SRCU_ATOMIC/raw_spinlock %d-way %sdeadlock.\n",
+ __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-");
+ for (i = 0; i < cyclelen; i++) {
+ pr_info("%s: srcu_read_lock_atomic(%d), raw_spin_lock(%d), raw_spin_unlock(%d), srcu_read_unlock_atomic(%d)\n",
+ __func__, i, i, i, i);
+ idx = srcu_read_lock_atomic(srcus_atomic[i]);
+ raw_spin_lock(spins[i]);
+ raw_spin_unlock(spins[i]);
+ srcu_read_unlock_atomic(srcus_atomic[i], idx);
+
+ j = srcu_lockdep_next(__func__, "raw_spin_lock",
+ "synchronize_srcu_atomic",
+ "raw_spin_unlock", i, cyclelen,
+ deadlock);
+ raw_spin_lock(spins[i]);
+ if (j >= 0)
+ synchronize_srcu_atomic(srcus_atomic[j]);
+ raw_spin_unlock(spins[i]);
+ }
+ return;
+ }
+
+ if (testtype == 6) {
+ pr_info("%s: test_srcu_lockdep = %05d: synchronize_srcu_atomic() inside rcu_read_lock() %d-way.\n",
+ __func__, test_srcu_lockdep, cyclelen);
+ for (i = 0; i < cyclelen; i++) {
+ rcu_read_lock();
+ synchronize_srcu_atomic(srcus_atomic[i]);
+ rcu_read_unlock();
+ }
+ return;
+ }
+
err_out:
pr_info("%s: test_srcu_lockdep = %05d does nothing.\n", __func__, test_srcu_lockdep);
pr_info("%s: test_srcu_lockdep = DNNL.\n", __func__);
pr_info("%s: D: Deadlock if nonzero.\n", __func__);
- pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU.\n", __func__);
+ pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU, 4=SRCU_ATOMIC, ",
+ __func__);
+ pr_cont("5=SRCU_ATOMIC/raw_spinlock, 6=synchronize_srcu_atomic inside rcu_read_lock.\n");
pr_info("%s: L: Cycle length.\n", __func__);
if (!IS_ENABLED(CONFIG_TASKS_TRACE_RCU))
pr_info("%s: NN=3 disallowed because kernel is built with CONFIG_TASKS_TRACE_RCU=n\n", __func__);
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 2/5] selftests/rcutorture: Wire atomic SRCU into srcu_lockdep.sh
2026-09-14 9:34 [PATCH 0/5] srcu: Add lockdep coverage for atomic SRCU and fix IRQ-state bug Kunwu Chan
2026-09-14 9:34 ` [PATCH 1/5] rcutorture: Add atomic SRCU lockdep support Kunwu Chan
@ 2026-09-14 9:34 ` Kunwu Chan
2026-09-14 9:34 ` [PATCH 3/5] srcutree: Preserve IRQ state in synchronize_srcu_atomic() callchain Kunwu Chan
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Kunwu Chan @ 2026-09-14 9:34 UTC (permalink / raw)
To: jiangshanlai, paulmck
Cc: josh, rostedt, mathieu.desnoyers, rcu, linux-kernel, Kunwu Chan, Zqiang
Add testtypes 4 (atomic SRCU same-type deadlock) and 5 (atomic SRCU
+ raw_spinlock dependency cycle) to the deadlock-detection loop.
Add a separate loop for testtype 6 (rcu_read_lock() →
synchronize_srcu_atomic()), which also verifies via console.log that
no lockdep warning is triggered.
Co-developed-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
.../selftests/rcutorture/bin/srcu_lockdep.sh | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
index 4e98c697def4..72791499dd96 100755
--- a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
+++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
@@ -44,7 +44,7 @@ nerrs=0
# Test lockdep's handling of deadlocks.
for d in 0 1
do
- for t in 0 1 2
+ for t in 0 1 2 4 5
do
for c in 1 2 3
do
@@ -79,6 +79,43 @@ do
done
done
+# Verify that synchronize_srcu_atomic() does not trigger lockdep
+# warnings when called inside rcu_read_lock().
+for c in 1 2 3
+do
+ err=
+ val=$((6*10+c))
+ tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 5s \
+ --configs "SRCU-P" \
+ --kconfig "CONFIG_FORCE_NEED_SRCU_NMI_SAFE=y" \
+ --bootargs "rcutorture.test_srcu_lockdep=$val" \
+ --trust-make --datestamp "$ds/$val" > "$T/kvm.sh.out" 2>&1
+ ret=$?
+ mv "$T/kvm.sh.out" "$RCUTORTURE/res/$ds/$val"
+ if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config
+ then
+ echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING" \
+ "disabled in rcutorture SRCU-P scenario"
+ err=1
+ fi
+ if test "$ret" -ne 0
+ then
+ err=1
+ echo -n Unexpected failure for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ elif grep -qE "WARNING: possible (recursive locking|circular locking dependency)" \
+ "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log"
+ then
+ err=1
+ echo -n Unexpected lockdep warning for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ fi
+ if test -n "$err"
+ then
+ grep "rcu_torture_init_srcu_lockdep: test_srcu_lockdep = " "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" | sed -e 's/^.*rcu_torture_init_srcu_lockdep://' >> "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ cat "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ nerrs=$((nerrs+1))
+ fi
+done
+
# Test lockdep-enabled testing of mixed SRCU readers.
for val in 0x1 0xf
do
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/5] srcutree: Preserve IRQ state in synchronize_srcu_atomic() callchain
2026-09-14 9:34 [PATCH 0/5] srcu: Add lockdep coverage for atomic SRCU and fix IRQ-state bug Kunwu Chan
2026-09-14 9:34 ` [PATCH 1/5] rcutorture: Add atomic SRCU lockdep support Kunwu Chan
2026-09-14 9:34 ` [PATCH 2/5] selftests/rcutorture: Wire atomic SRCU into srcu_lockdep.sh Kunwu Chan
@ 2026-09-14 9:34 ` Kunwu Chan
2026-09-14 9:34 ` [PATCH 4/5] selftests/rcutorture: Fix double nerrs count in srcu_lockdep.sh Kunwu Chan
2026-09-14 9:34 ` [PATCH 5/5] rcutorture: Add atomic SRCU cross-CPU IRQ context mismatch test Kunwu Chan
4 siblings, 0 replies; 6+ messages in thread
From: Kunwu Chan @ 2026-09-14 9:34 UTC (permalink / raw)
To: jiangshanlai, paulmck
Cc: josh, rostedt, mathieu.desnoyers, rcu, linux-kernel, Kunwu Chan, Zqiang
synchronize_srcu_atomic() may be called with interrupts disabled, but
its callchain uses raw_spin_lock_irq_rcu_node() /
raw_spin_unlock_irq_rcu_node(), which unconditionally enables
interrupts on unlock. This can incorrectly change the caller's IRQ
state.
Use the irqsave/irqrestore variants for the locks in this callchain
so that the caller's IRQ state is preserved.
Co-developed-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
kernel/rcu/srcutree.c | 42 +++++++++++++++++++++++-------------------
1 file changed, 23 insertions(+), 19 deletions(-)
diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
index 6c729e805fb3..d32615d54177 100644
--- a/kernel/rcu/srcutree.c
+++ b/kernel/rcu/srcutree.c
@@ -985,6 +985,7 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic)
bool cbs;
bool last_lvl;
int cpu;
+ unsigned long flags;
unsigned long gpseq;
int idx;
unsigned long mask;
@@ -999,7 +1000,7 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic)
mutex_lock(&sup->srcu_cb_mutex);
/* End the current grace period. */
- raw_spin_lock_irq_rcu_node(sup);
+ raw_spin_lock_irqsave_rcu_node(sup, flags);
idx = rcu_seq_state(sup->srcu_gp_seq);
WARN_ON_ONCE(idx != SRCU_STATE_SCAN2);
if (srcu_gp_is_expedited(ssp))
@@ -1010,7 +1011,7 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic)
gpseq = rcu_seq_current(&sup->srcu_gp_seq);
if (ULONG_CMP_LT(sup->srcu_gp_seq_needed_exp, gpseq))
WRITE_ONCE(sup->srcu_gp_seq_needed_exp, gpseq);
- raw_spin_unlock_irq_rcu_node(sup);
+ raw_spin_unlock_irqrestore_rcu_node(sup, flags);
if (!is_atomic)
mutex_unlock(&sup->srcu_gp_mutex);
/* A new grace period can start at this point. But only one. */
@@ -1048,12 +1049,12 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic)
if (!(gpseq & counter_wrap_check))
for_each_possible_cpu(cpu) {
sdp = per_cpu_ptr(ssp->sda, cpu);
- raw_spin_lock_irq_rcu_node(sdp);
+ raw_spin_lock_irqsave_rcu_node(sdp, flags);
if (ULONG_CMP_GE(gpseq, sdp->srcu_gp_seq_needed + 100))
sdp->srcu_gp_seq_needed = gpseq;
if (ULONG_CMP_GE(gpseq, sdp->srcu_gp_seq_needed_exp + 100))
sdp->srcu_gp_seq_needed_exp = gpseq;
- raw_spin_unlock_irq_rcu_node(sdp);
+ raw_spin_unlock_irqrestore_rcu_node(sdp, flags);
}
/* Callback initiation done, allow grace periods after next. */
@@ -1061,16 +1062,16 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic)
mutex_unlock(&sup->srcu_cb_mutex);
/* Start a new grace period if needed. */
- raw_spin_lock_irq_rcu_node(sup);
+ raw_spin_lock_irqsave_rcu_node(sup, flags);
gpseq = rcu_seq_current(&sup->srcu_gp_seq);
if (!rcu_seq_state(gpseq) &&
ULONG_CMP_LT(gpseq, sup->srcu_gp_seq_needed)) {
WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC);
srcu_gp_start(ssp);
- raw_spin_unlock_irq_rcu_node(sup);
+ raw_spin_unlock_irqrestore_rcu_node(sup, flags);
srcu_reschedule(ssp, 0);
} else {
- raw_spin_unlock_irq_rcu_node(sup);
+ raw_spin_unlock_irqrestore_rcu_node(sup, flags);
}
/* Transition to big if needed, but never for atomic SRCU. */
@@ -1221,10 +1222,11 @@ static void srcu_funnel_gp_start(struct srcu_struct *ssp, struct srcu_data *sdp,
static bool try_check_zero(struct srcu_struct *ssp, int idx, int trycount)
{
unsigned long curdelay;
+ unsigned long flags;
- raw_spin_lock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_lock_irqsave_rcu_node(ssp->srcu_sup, flags);
curdelay = !srcu_get_delay(ssp);
- raw_spin_unlock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags);
for (;;) {
if (srcu_readers_active_idx_check(ssp, idx))
@@ -2024,6 +2026,7 @@ EXPORT_SYMBOL_GPL(srcu_batches_completed);
*/
static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic)
{
+ unsigned long flags;
int idx;
if (!is_atomic)
@@ -2041,10 +2044,10 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic)
*/
idx = rcu_seq_state(smp_load_acquire(&ssp->srcu_sup->srcu_gp_seq)); /* ^^^ */
if (idx == SRCU_STATE_IDLE) {
- raw_spin_lock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_lock_irqsave_rcu_node(ssp->srcu_sup, flags);
if (ULONG_CMP_GE(ssp->srcu_sup->srcu_gp_seq, ssp->srcu_sup->srcu_gp_seq_needed)) {
WARN_ON_ONCE(rcu_seq_state(ssp->srcu_sup->srcu_gp_seq));
- raw_spin_unlock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags);
if (!is_atomic)
mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex);
return;
@@ -2054,7 +2057,7 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic)
WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC);
srcu_gp_start(ssp);
}
- raw_spin_unlock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags);
if (idx != SRCU_STATE_IDLE) {
if (!is_atomic)
mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex);
@@ -2070,10 +2073,10 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic)
return; /* readers present, retry later. */
}
srcu_flip(ssp);
- raw_spin_lock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_lock_irqsave_rcu_node(ssp->srcu_sup, flags);
rcu_seq_set_state(&ssp->srcu_sup->srcu_gp_seq, SRCU_STATE_SCAN2);
ssp->srcu_sup->srcu_n_exp_nodelay = 0;
- raw_spin_unlock_irq_rcu_node(ssp->srcu_sup);
+ raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags);
}
if (rcu_seq_state(READ_ONCE(ssp->srcu_sup->srcu_gp_seq)) == SRCU_STATE_SCAN2) {
@@ -2120,6 +2123,7 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp)
{
unsigned long srcu_state;
struct srcu_usage *sup = ssp->srcu_sup;
+ unsigned long flags;
unsigned long rdm0, rdm1;
unsigned long unlocks0, unlocks1;
@@ -2150,9 +2154,9 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp)
}
// One last check for others doing our work for us under the lock.
- raw_spin_lock_irq_rcu_node(sup);
+ raw_spin_lock_irqsave_rcu_node(sup, flags);
if (poll_state_synchronize_srcu(ssp, srcu_state)) {
- raw_spin_unlock_irq_rcu_node(sup);
+ raw_spin_unlock_irqrestore_rcu_node(sup, flags);
atomic_set(&sup->srcu_atomic_gp_flag, 0);
preempt_enable();
return;
@@ -2163,7 +2167,7 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp)
smp_store_release(&sup->srcu_gp_seq_needed, srcu_state); // See srcu_funnel_gp_start().
ASSERT_EXCLUSIVE_WRITER(ssp->srcu_sup->srcu_gp_seq);
srcu_gp_start(ssp);
- raw_spin_unlock_irq_rcu_node(sup);
+ raw_spin_unlock_irqrestore_rcu_node(sup, flags);
//
// Fastpath: If there are no readers at all, neither grace-period
@@ -2201,9 +2205,9 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp)
// poll_state_synchronize_srcu() working, all under ->lock
// and ->srcu_atomic_gp_flag, which excludes concurrent
// sequence-number updates.
- raw_spin_lock_irq_rcu_node(sup);
+ raw_spin_lock_irqsave_rcu_node(sup, flags);
rcu_seq_end(&sup->srcu_gp_seq);
- raw_spin_unlock_irq_rcu_node(sup);
+ raw_spin_unlock_irqrestore_rcu_node(sup, flags);
WARN_ON_ONCE(!poll_state_synchronize_srcu(ssp, srcu_state));
atomic_set_release(&sup->srcu_atomic_gp_flag, 0);
preempt_enable();
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 4/5] selftests/rcutorture: Fix double nerrs count in srcu_lockdep.sh
2026-09-14 9:34 [PATCH 0/5] srcu: Add lockdep coverage for atomic SRCU and fix IRQ-state bug Kunwu Chan
` (2 preceding siblings ...)
2026-09-14 9:34 ` [PATCH 3/5] srcutree: Preserve IRQ state in synchronize_srcu_atomic() callchain Kunwu Chan
@ 2026-09-14 9:34 ` Kunwu Chan
2026-09-14 9:34 ` [PATCH 5/5] rcutorture: Add atomic SRCU cross-CPU IRQ context mismatch test Kunwu Chan
4 siblings, 0 replies; 6+ messages in thread
From: Kunwu Chan @ 2026-09-14 9:34 UTC (permalink / raw)
To: jiangshanlai, paulmck
Cc: josh, rostedt, mathieu.desnoyers, rcu, linux-kernel, Kunwu Chan
When CONFIG_PROVE_LOCKING is missing, the deadlock-detection loop
and the mixed-SRCU-readers section each increment nerrs once in
the configuration check and again in the common error path,
counting the same error twice.
Remove the stray increments so that each error advances nerrs
by one.
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh | 2 --
1 file changed, 2 deletions(-)
diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
index 72791499dd96..3acaffe52c78 100755
--- a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
+++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
@@ -56,7 +56,6 @@ do
if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config
then
echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING disabled in rcutorture SRCU-P scenario"
- nerrs=$((nerrs+1))
err=1
fi
if test "$d" -ne 0 && test "$ret" -eq 0
@@ -126,7 +125,6 @@ do
if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config
then
echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING disabled in rcutorture SRCU-P scenario"
- nerrs=$((nerrs+1))
err=1
fi
if test "$val" = 0xf && test "$ret" -eq 0
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 5/5] rcutorture: Add atomic SRCU cross-CPU IRQ context mismatch test
2026-09-14 9:34 [PATCH 0/5] srcu: Add lockdep coverage for atomic SRCU and fix IRQ-state bug Kunwu Chan
` (3 preceding siblings ...)
2026-09-14 9:34 ` [PATCH 4/5] selftests/rcutorture: Fix double nerrs count in srcu_lockdep.sh Kunwu Chan
@ 2026-09-14 9:34 ` Kunwu Chan
4 siblings, 0 replies; 6+ messages in thread
From: Kunwu Chan @ 2026-09-14 9:34 UTC (permalink / raw)
To: jiangshanlai, paulmck
Cc: josh, rostedt, mathieu.desnoyers, rcu, linux-kernel, Kunwu Chan, Zqiang
Add testtype 7 to verify that lockdep detects an IRQ-context
mismatch when an atomic SRCU read-side critical section is held
with IRQs enabled on one CPU and synchronize_srcu_atomic() is
called from an IPI handler on another CPU.
This covers the cross-CPU case that cannot be detected by
checking the current task's held locks.
Co-developed-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Zqiang <qiang.zhang@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
kernel/rcu/rcutorture.c | 27 +++++++++++++-
.../selftests/rcutorture/bin/srcu_lockdep.sh | 36 +++++++++++++++++++
2 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index ac9c726cb391..6f5a1d635f94 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -4695,6 +4695,8 @@ DEFINE_STATIC_SRCU_ATOMIC(srcu7_atomic);
DEFINE_STATIC_SRCU_ATOMIC(srcu8_atomic);
DEFINE_STATIC_SRCU_ATOMIC(srcu9_atomic);
+DEFINE_STATIC_SRCU_ATOMIC(srcu_irq);
+
static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, const char *fu, int i,
int cyclelen, int deadlock)
{
@@ -4709,6 +4711,11 @@ static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, cons
return j;
}
+static void srcu_sync_irq(void *unused)
+{
+ synchronize_srcu_atomic(&srcu_irq);
+}
+
// Test lockdep on SRCU-based deadlock scenarios.
static void rcu_torture_init_srcu_lockdep(void)
{
@@ -4892,13 +4899,31 @@ static void rcu_torture_init_srcu_lockdep(void)
return;
}
+ if (testtype == 7) {
+ int cpu;
+
+ for (i = 0; i < cyclelen; i++) {
+ idx = srcu_read_lock_atomic(&srcu_irq);
+ cpu = cpumask_any_but(cpu_online_mask,
+ smp_processor_id());
+ if (cpu < nr_cpu_ids) {
+ pr_info("%s: CPU%d sending IPI to CPU%d\n",
+ __func__, smp_processor_id(), cpu);
+ smp_call_function_single(cpu, srcu_sync_irq,
+ NULL, 1);
+ }
+ srcu_read_unlock_atomic(&srcu_irq, idx);
+ }
+ return;
+ }
+
err_out:
pr_info("%s: test_srcu_lockdep = %05d does nothing.\n", __func__, test_srcu_lockdep);
pr_info("%s: test_srcu_lockdep = DNNL.\n", __func__);
pr_info("%s: D: Deadlock if nonzero.\n", __func__);
pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU, 4=SRCU_ATOMIC, ",
__func__);
- pr_cont("5=SRCU_ATOMIC/raw_spinlock, 6=synchronize_srcu_atomic inside rcu_read_lock.\n");
+ pr_cont("5=SRCU_ATOMIC/raw_spinlock, 6=synchronize_srcu_atomic inside rcu_read_lock, 7=atomic SRCU cross-CPU IRQ context mismatch.\n");
pr_info("%s: L: Cycle length.\n", __func__);
if (!IS_ENABLED(CONFIG_TASKS_TRACE_RCU))
pr_info("%s: NN=3 disallowed because kernel is built with CONFIG_TASKS_TRACE_RCU=n\n", __func__);
diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
index 3acaffe52c78..c29a39f7d891 100755
--- a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
+++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh
@@ -115,6 +115,42 @@ do
fi
done
+# Verify that synchronize_srcu_atomic() detects IRQ context mismatch
+# when SRCU reader previously ran with IRQs enabled.
+for c in 1 2 3
+do
+ err=
+ val=$((1000+7*10+c))
+ tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 5s \
+ --configs "SRCU-P" \
+ --kconfig "CONFIG_FORCE_NEED_SRCU_NMI_SAFE=y" \
+ --bootargs "rcutorture.test_srcu_lockdep=$val" \
+ --trust-make --datestamp "$ds/$val" > "$T/kvm.sh.out" 2>&1
+ ret=$?
+ mv "$T/kvm.sh.out" "$RCUTORTURE/res/$ds/$val"
+ if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config
+ then
+ echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING" \
+ "disabled in rcutorture SRCU-P scenario"
+ err=1
+ fi
+ if test "$ret" -eq 0
+ then
+ err=1
+ echo -n Missing lockdep warning for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ elif ! grep -q "inconsistent {HARDIRQ-ON-R}" "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log"
+ then
+ err=1
+ echo -n Missing lockdep warning for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ fi
+ if test -n "$err"
+ then
+ grep "rcu_torture_init_srcu_lockdep: test_srcu_lockdep = " "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" | sed -e 's/^.*rcu_torture_init_srcu_lockdep://' >> "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ cat "$RCUTORTURE/res/$ds/$val/kvm.sh.err"
+ nerrs=$((nerrs+1))
+ fi
+done
+
# Test lockdep-enabled testing of mixed SRCU readers.
for val in 0x1 0xf
do
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread