rcu: Annotate SRCU's update-side lockdep dependencies
authorBoqun Feng <boqun.feng@gmail.com>
Fri, 13 Jan 2023 06:59:54 +0000 (22:59 -0800)
committerBoqun Feng <boqun.feng@gmail.com>
Mon, 27 Mar 2023 18:15:59 +0000 (11:15 -0700)
Although all flavors of RCU readers are annotated correctly with
lockdep as recursive read locks, they do not set the lock_acquire
'check' parameter.  This means that RCU read locks are not added to
the lockdep dependency graph, which in turn means that lockdep cannot
detect RCU-based deadlocks.  This is not a problem for RCU flavors having
atomic read-side critical sections because context-based annotations can
catch these deadlocks, see for example the RCU_LOCKDEP_WARN() statement
in synchronize_rcu().  But context-based annotations are not helpful
for sleepable RCU, especially given that it is perfectly legal to do
synchronize_srcu(&srcu1) within an srcu_read_lock(&srcu2).

However, we can detect SRCU-based by: (1) Making srcu_read_lock() a
'check'ed recursive read lock and (2) Making synchronize_srcu() a empty
write lock critical section.  Even better, with the newly introduced
lock_sync(), we can avoid false positives about irq-unsafe/safe.
This commit therefore makes it so.

Note that NMI-safe SRCU read side critical sections are currently not
annotated, but might be annotated in the future.

Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
[ boqun: Add comments for annotation per Waiman's suggestion ]
[ boqun: Fix comment warning reported by Stephen Rothwell ]
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
include/linux/srcu.h
kernel/rcu/srcutiny.c
kernel/rcu/srcutree.c

index 74796cd..41c4b26 100644 (file)
@@ -102,6 +102,32 @@ static inline int srcu_read_lock_held(const struct srcu_struct *ssp)
        return lock_is_held(&ssp->dep_map);
 }
 
+/*
+ * Annotations provide deadlock detection for SRCU.
+ *
+ * Similar to other lockdep annotations, except there is an additional
+ * srcu_lock_sync(), which is basically an empty *write*-side critical section,
+ * see lock_sync() for more information.
+ */
+
+/* Annotates a srcu_read_lock() */
+static inline void srcu_lock_acquire(struct lockdep_map *map)
+{
+       lock_map_acquire_read(map);
+}
+
+/* Annotates a srcu_read_lock() */
+static inline void srcu_lock_release(struct lockdep_map *map)
+{
+       lock_map_release(map);
+}
+
+/* Annotates a synchronize_srcu() */
+static inline void srcu_lock_sync(struct lockdep_map *map)
+{
+       lock_map_sync(map);
+}
+
 #else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
 
 static inline int srcu_read_lock_held(const struct srcu_struct *ssp)
@@ -109,6 +135,10 @@ static inline int srcu_read_lock_held(const struct srcu_struct *ssp)
        return 1;
 }
 
+#define srcu_lock_acquire(m) do { } while (0)
+#define srcu_lock_release(m) do { } while (0)
+#define srcu_lock_sync(m) do { } while (0)
+
 #endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */
 
 #define SRCU_NMI_UNKNOWN       0x0
@@ -182,7 +212,7 @@ static inline int srcu_read_lock(struct srcu_struct *ssp) __acquires(ssp)
 
        srcu_check_nmi_safety(ssp, false);
        retval = __srcu_read_lock(ssp);
-       rcu_lock_acquire(&(ssp)->dep_map);
+       srcu_lock_acquire(&(ssp)->dep_map);
        return retval;
 }
 
@@ -254,7 +284,7 @@ static inline void srcu_read_unlock(struct srcu_struct *ssp, int idx)
 {
        WARN_ON_ONCE(idx & ~0x1);
        srcu_check_nmi_safety(ssp, false);
-       rcu_lock_release(&(ssp)->dep_map);
+       srcu_lock_release(&(ssp)->dep_map);
        __srcu_read_unlock(ssp, idx);
 }
 
index b12fb0c..336af24 100644 (file)
@@ -197,6 +197,8 @@ void synchronize_srcu(struct srcu_struct *ssp)
 {
        struct rcu_synchronize rs;
 
+       srcu_lock_sync(&ssp->dep_map);
+
        RCU_LOCKDEP_WARN(lockdep_is_held(ssp) ||
                        lock_is_held(&rcu_bh_lock_map) ||
                        lock_is_held(&rcu_lock_map) ||
index ab4ee58..c541b82 100644 (file)
@@ -1307,6 +1307,8 @@ static void __synchronize_srcu(struct srcu_struct *ssp, bool do_norm)
 {
        struct rcu_synchronize rcu;
 
+       srcu_lock_sync(&ssp->dep_map);
+
        RCU_LOCKDEP_WARN(lockdep_is_held(ssp) ||
                         lock_is_held(&rcu_bh_lock_map) ||
                         lock_is_held(&rcu_lock_map) ||