Commit aba9da09 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

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 c27cea44 ("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()
parents d2a43e7f a6fc88b2
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#ifndef _LINUX_SRCU_TINY_H
#define _LINUX_SRCU_TINY_H

#include <linux/irq_work_types.h>
#include <linux/swait.h>

struct srcu_struct {
@@ -24,18 +25,21 @@ struct srcu_struct {
	struct rcu_head *srcu_cb_head;	/* Pending callbacks: Head. */
	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. */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
};

void srcu_drive_gp(struct work_struct *wp);
void srcu_tiny_irq_work(struct irq_work *irq_work);

#define __SRCU_STRUCT_INIT(name, __ignored, ___ignored, ____ignored)	\
{									\
	.srcu_wq = __SWAIT_QUEUE_HEAD_INITIALIZER(name.srcu_wq),	\
	.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 },		\
	__SRCU_DEP_MAP_INIT(name)					\
}

+5 −4
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ struct srcu_data {
						/* Values: SRCU_READ_FLAVOR_.*  */

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

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

#define __SRCU_USAGE_INIT(name)									\
{												\
	.lock = __SPIN_LOCK_UNLOCKED(name.lock),						\
	.lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock),						\
	.srcu_gp_seq = SRCU_GP_SEQ_INITIAL_VAL,							\
	.srcu_gp_seq_needed = SRCU_GP_SEQ_INITIAL_VAL_WITH_STATE,				\
	.srcu_gp_seq_needed_exp = SRCU_GP_SEQ_INITIAL_VAL,					\
+9 −0
Original line number Diff line number Diff line
@@ -502,6 +502,15 @@ do { \
	___locked;							\
})

#define raw_spin_trylock_irqsave_rcu_node(p, flags)			\
({									\
	bool ___locked = raw_spin_trylock_irqsave(&ACCESS_PRIVATE(p, lock), flags); \
									\
	if (___locked)							\
		smp_mb__after_unlock_lock();				\
	___locked;							\
})

#define raw_lockdep_assert_held_rcu_node(p)				\
	lockdep_assert_held(&ACCESS_PRIVATE(p, lock))

+18 −1
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
 */

#include <linux/export.h>
#include <linux/irq_work.h>
#include <linux/mutex.h>
#include <linux/preempt.h>
#include <linux/rcupdate_wait.h>
@@ -41,6 +42,7 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp)
	ssp->srcu_idx_max = 0;
	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);
	return 0;
}

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

/*
 * Use an irq_work to defer schedule_work() to avoid acquiring the workqueue
 * pool->lock while the caller might hold scheduler locks, causing lockdep
 * splats due to workqueue_init() doing a wakeup.
 */
void srcu_tiny_irq_work(struct irq_work *irq_work)
{
	struct srcu_struct *ssp;

	ssp = container_of(irq_work, struct srcu_struct, srcu_irq_work);
	schedule_work(&ssp->srcu_work);
}
EXPORT_SYMBOL_GPL(srcu_tiny_irq_work);

static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
{
	unsigned long cookie;
@@ -189,7 +206,7 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
	WRITE_ONCE(ssp->srcu_idx_max, cookie);
	if (!READ_ONCE(ssp->srcu_gp_running)) {
		if (likely(srcu_init_done))
			schedule_work(&ssp->srcu_work);
			irq_work_queue(&ssp->srcu_irq_work);
		else if (list_empty(&ssp->srcu_work.entry))
			list_add(&ssp->srcu_work.entry, &srcu_boot_list);
	}
+102 −109

File changed.

Preview size limit exceeded, changes collapsed.