]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
srcu: Permit Tiny SRCU srcu_read_unlock() with interrupts disabled
authorPaul E. McKenney <paulmck@kernel.org>
Wed, 5 Nov 2025 20:32:01 +0000 (12:32 -0800)
committerFrederic Weisbecker <frederic@kernel.org>
Wed, 5 Nov 2025 22:58:11 +0000 (23:58 +0100)
The current Tiny SRCU implementation of srcu_read_unlock() awakens
the grace-period processing when exiting the outermost SRCU read-side
critical section.  However, not all Linux-kernel configurations and
contexts permit swake_up_one() to be invoked while interrupts are
disabled, and this can result in indefinitely extended SRCU grace periods.
This commit therefore only invokes swake_up_one() when interrupts are
enabled, and introduces polling to the grace-period workqueue handler.

Reported-by: kernel test robot <oliver.sang@intel.com>
Reported-by: Zqiang <qiang.zhang@linux.dev>
Closes: https://lore.kernel.org/oe-lkp/202508261642.b15eefbb-lkp@intel.com
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
kernel/rcu/srcutiny.c

index e3b64a5e0ec7e1ea3f1bd501275bc819b4cbd89e..3450c3751ef7ade85bf14f0bae38a0906ca25e4e 100644 (file)
@@ -106,15 +106,15 @@ void __srcu_read_unlock(struct srcu_struct *ssp, int idx)
        newval = READ_ONCE(ssp->srcu_lock_nesting[idx]) - 1;
        WRITE_ONCE(ssp->srcu_lock_nesting[idx], newval);
        preempt_enable();
-       if (!newval && READ_ONCE(ssp->srcu_gp_waiting) && in_task())
+       if (!newval && READ_ONCE(ssp->srcu_gp_waiting) && in_task() && !irqs_disabled())
                swake_up_one(&ssp->srcu_wq);
 }
 EXPORT_SYMBOL_GPL(__srcu_read_unlock);
 
 /*
  * Workqueue handler to drive one grace period and invoke any callbacks
- * that become ready as a result.  Single-CPU and !PREEMPTION operation
- * means that we get away with murder on synchronization.  ;-)
+ * that become ready as a result.  Single-CPU operation and preemption
+ * disabling mean that we get away with murder on synchronization.  ;-)
  */
 void srcu_drive_gp(struct work_struct *wp)
 {
@@ -141,7 +141,12 @@ void srcu_drive_gp(struct work_struct *wp)
        WRITE_ONCE(ssp->srcu_idx, ssp->srcu_idx + 1);
        WRITE_ONCE(ssp->srcu_gp_waiting, true);  /* srcu_read_unlock() wakes! */
        preempt_enable();
-       swait_event_exclusive(ssp->srcu_wq, !READ_ONCE(ssp->srcu_lock_nesting[idx]));
+       do {
+               // Deadlock issues prevent __srcu_read_unlock() from
+               // doing an unconditional wakeup, so polling is required.
+               swait_event_timeout_exclusive(ssp->srcu_wq,
+                                             !READ_ONCE(ssp->srcu_lock_nesting[idx]), HZ / 10);
+       } while (READ_ONCE(ssp->srcu_lock_nesting[idx]));
        preempt_disable();  // Needed for PREEMPT_LAZY
        WRITE_ONCE(ssp->srcu_gp_waiting, false); /* srcu_read_unlock() cheap. */
        WRITE_ONCE(ssp->srcu_idx, ssp->srcu_idx + 1);