Skip to content

Commit aba9da0

Browse files
committed
Merge tag 'rcu-fixes.v7.0-20260325a' of git://git.kernel.org/pub/scm/linux/kernel/git/rcu/linux
Pull RCU fixes from Boqun Feng: "Fix a regression introduced by commit c27cea4 ("rcu: Re-implement RCU Tasks Trace in terms of SRCU-fast"): BPF contexts can run with preemption disabled or scheduler locks held, so call_srcu() must work in all such contexts. Fix this by converting SRCU's spinlocks to raw spinlocks and avoiding scheduler lock acquisition in call_srcu() by deferring to an irq_work (similar to call_rcu_tasks_generic()), for both tree SRCU and tiny SRCU. Also fix a follow-on lockdep splat caused by srcu_node allocation under the newly introduced raw spinlock by deferring the allocation to grace-period worker context" * tag 'rcu-fixes.v7.0-20260325a' of git://git.kernel.org/pub/scm/linux/kernel/git/rcu/linux: srcu: Use irq_work to start GP in tiny SRCU rcu: Use an intermediate irq_work to start process_srcu() srcu: Push srcu_node allocation to GP when non-preemptible srcu: Use raw spinlocks so call_srcu() can be used under preempt_disable()
2 parents d2a43e7 + a6fc88b commit aba9da0

5 files changed

Lines changed: 138 additions & 114 deletions

File tree

include/linux/srcutiny.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#ifndef _LINUX_SRCU_TINY_H
1212
#define _LINUX_SRCU_TINY_H
1313

14+
#include <linux/irq_work_types.h>
1415
#include <linux/swait.h>
1516

1617
struct srcu_struct {
@@ -24,18 +25,21 @@ struct srcu_struct {
2425
struct rcu_head *srcu_cb_head; /* Pending callbacks: Head. */
2526
struct rcu_head **srcu_cb_tail; /* Pending callbacks: Tail. */
2627
struct work_struct srcu_work; /* For driving grace periods. */
28+
struct irq_work srcu_irq_work; /* Defer schedule_work() to irq work. */
2729
#ifdef CONFIG_DEBUG_LOCK_ALLOC
2830
struct lockdep_map dep_map;
2931
#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
3032
};
3133

3234
void srcu_drive_gp(struct work_struct *wp);
35+
void srcu_tiny_irq_work(struct irq_work *irq_work);
3336

3437
#define __SRCU_STRUCT_INIT(name, __ignored, ___ignored, ____ignored) \
3538
{ \
3639
.srcu_wq = __SWAIT_QUEUE_HEAD_INITIALIZER(name.srcu_wq), \
3740
.srcu_cb_tail = &name.srcu_cb_head, \
3841
.srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp), \
42+
.srcu_irq_work = { .func = srcu_tiny_irq_work }, \
3943
__SRCU_DEP_MAP_INIT(name) \
4044
}
4145

include/linux/srcutree.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct srcu_data {
3434
/* Values: SRCU_READ_FLAVOR_.* */
3535

3636
/* Update-side state. */
37-
spinlock_t __private lock ____cacheline_internodealigned_in_smp;
37+
raw_spinlock_t __private lock ____cacheline_internodealigned_in_smp;
3838
struct rcu_segcblist srcu_cblist; /* List of callbacks.*/
3939
unsigned long srcu_gp_seq_needed; /* Furthest future GP needed. */
4040
unsigned long srcu_gp_seq_needed_exp; /* Furthest future exp GP. */
@@ -55,7 +55,7 @@ struct srcu_data {
5555
* Node in SRCU combining tree, similar in function to rcu_data.
5656
*/
5757
struct srcu_node {
58-
spinlock_t __private lock;
58+
raw_spinlock_t __private lock;
5959
unsigned long srcu_have_cbs[4]; /* GP seq for children having CBs, but only */
6060
/* if greater than ->srcu_gp_seq. */
6161
unsigned long srcu_data_have_cbs[4]; /* Which srcu_data structs have CBs for given GP? */
@@ -74,7 +74,7 @@ struct srcu_usage {
7474
/* First node at each level. */
7575
int srcu_size_state; /* Small-to-big transition state. */
7676
struct mutex srcu_cb_mutex; /* Serialize CB preparation. */
77-
spinlock_t __private lock; /* Protect counters and size state. */
77+
raw_spinlock_t __private lock; /* Protect counters and size state. */
7878
struct mutex srcu_gp_mutex; /* Serialize GP work. */
7979
unsigned long srcu_gp_seq; /* Grace-period seq #. */
8080
unsigned long srcu_gp_seq_needed; /* Latest gp_seq needed. */
@@ -95,6 +95,7 @@ struct srcu_usage {
9595
unsigned long reschedule_jiffies;
9696
unsigned long reschedule_count;
9797
struct delayed_work work;
98+
struct irq_work irq_work;
9899
struct srcu_struct *srcu_ssp;
99100
};
100101

@@ -156,7 +157,7 @@ struct srcu_struct {
156157

157158
#define __SRCU_USAGE_INIT(name) \
158159
{ \
159-
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
160+
.lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock), \
160161
.srcu_gp_seq = SRCU_GP_SEQ_INITIAL_VAL, \
161162
.srcu_gp_seq_needed = SRCU_GP_SEQ_INITIAL_VAL_WITH_STATE, \
162163
.srcu_gp_seq_needed_exp = SRCU_GP_SEQ_INITIAL_VAL, \

kernel/rcu/rcu.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,15 @@ do { \
502502
___locked; \
503503
})
504504

505+
#define raw_spin_trylock_irqsave_rcu_node(p, flags) \
506+
({ \
507+
bool ___locked = raw_spin_trylock_irqsave(&ACCESS_PRIVATE(p, lock), flags); \
508+
\
509+
if (___locked) \
510+
smp_mb__after_unlock_lock(); \
511+
___locked; \
512+
})
513+
505514
#define raw_lockdep_assert_held_rcu_node(p) \
506515
lockdep_assert_held(&ACCESS_PRIVATE(p, lock))
507516

kernel/rcu/srcutiny.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
#include <linux/export.h>
12+
#include <linux/irq_work.h>
1213
#include <linux/mutex.h>
1314
#include <linux/preempt.h>
1415
#include <linux/rcupdate_wait.h>
@@ -41,6 +42,7 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp)
4142
ssp->srcu_idx_max = 0;
4243
INIT_WORK(&ssp->srcu_work, srcu_drive_gp);
4344
INIT_LIST_HEAD(&ssp->srcu_work.entry);
45+
init_irq_work(&ssp->srcu_irq_work, srcu_tiny_irq_work);
4446
return 0;
4547
}
4648

@@ -84,6 +86,7 @@ EXPORT_SYMBOL_GPL(init_srcu_struct);
8486
void cleanup_srcu_struct(struct srcu_struct *ssp)
8587
{
8688
WARN_ON(ssp->srcu_lock_nesting[0] || ssp->srcu_lock_nesting[1]);
89+
irq_work_sync(&ssp->srcu_irq_work);
8790
flush_work(&ssp->srcu_work);
8891
WARN_ON(ssp->srcu_gp_running);
8992
WARN_ON(ssp->srcu_gp_waiting);
@@ -177,6 +180,20 @@ void srcu_drive_gp(struct work_struct *wp)
177180
}
178181
EXPORT_SYMBOL_GPL(srcu_drive_gp);
179182

183+
/*
184+
* Use an irq_work to defer schedule_work() to avoid acquiring the workqueue
185+
* pool->lock while the caller might hold scheduler locks, causing lockdep
186+
* splats due to workqueue_init() doing a wakeup.
187+
*/
188+
void srcu_tiny_irq_work(struct irq_work *irq_work)
189+
{
190+
struct srcu_struct *ssp;
191+
192+
ssp = container_of(irq_work, struct srcu_struct, srcu_irq_work);
193+
schedule_work(&ssp->srcu_work);
194+
}
195+
EXPORT_SYMBOL_GPL(srcu_tiny_irq_work);
196+
180197
static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
181198
{
182199
unsigned long cookie;
@@ -189,7 +206,7 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
189206
WRITE_ONCE(ssp->srcu_idx_max, cookie);
190207
if (!READ_ONCE(ssp->srcu_gp_running)) {
191208
if (likely(srcu_init_done))
192-
schedule_work(&ssp->srcu_work);
209+
irq_work_queue(&ssp->srcu_irq_work);
193210
else if (list_empty(&ssp->srcu_work.entry))
194211
list_add(&ssp->srcu_work.entry, &srcu_boot_list);
195212
}

0 commit comments

Comments
 (0)