locking/lockdep: Apply crossrelease to completions
authorByungchul Park <byungchul.park@lge.com>
Mon, 7 Aug 2017 07:12:56 +0000 (16:12 +0900)
committerIngo Molnar <mingo@kernel.org>
Thu, 10 Aug 2017 10:29:10 +0000 (12:29 +0200)
Although wait_for_completion() and its family can cause deadlock, the
lock correctness validator could not be applied to them until now,
because things like complete() are usually called in a different context
from the waiting context, which violates lockdep's assumption.

Thanks to CONFIG_LOCKDEP_CROSSRELEASE, we can now apply the lockdep
detector to those completion operations. Applied it.

Signed-off-by: Byungchul Park <byungchul.park@lge.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: akpm@linux-foundation.org
Cc: boqun.feng@gmail.com
Cc: kernel-team@lge.com
Cc: kirill@shutemov.name
Cc: npiggin@gmail.com
Cc: walken@google.com
Cc: willy@infradead.org
Link: http://lkml.kernel.org/r/1502089981-21272-10-git-send-email-byungchul.park@lge.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
include/linux/completion.h
kernel/sched/completion.c
lib/Kconfig.debug

index 5d5aaae3af433ff62b6e03f107c7dfe88a06b522..9bcebf509b4d3392e643aac47ebf82c8ed2031c9 100644 (file)
@@ -9,6 +9,9 @@
  */
 
 #include <linux/wait.h>
+#ifdef CONFIG_LOCKDEP_COMPLETE
+#include <linux/lockdep.h>
+#endif
 
 /*
  * struct completion - structure used to maintain state for a "completion"
 struct completion {
        unsigned int done;
        wait_queue_head_t wait;
+#ifdef CONFIG_LOCKDEP_COMPLETE
+       struct lockdep_map_cross map;
+#endif
 };
 
+#ifdef CONFIG_LOCKDEP_COMPLETE
+static inline void complete_acquire(struct completion *x)
+{
+       lock_acquire_exclusive((struct lockdep_map *)&x->map, 0, 0, NULL, _RET_IP_);
+}
+
+static inline void complete_release(struct completion *x)
+{
+       lock_release((struct lockdep_map *)&x->map, 0, _RET_IP_);
+}
+
+static inline void complete_release_commit(struct completion *x)
+{
+       lock_commit_crosslock((struct lockdep_map *)&x->map);
+}
+
+#define init_completion(x)                                             \
+do {                                                                   \
+       static struct lock_class_key __key;                             \
+       lockdep_init_map_crosslock((struct lockdep_map *)&(x)->map,     \
+                       "(complete)" #x,                                \
+                       &__key, 0);                                     \
+       __init_completion(x);                                           \
+} while (0)
+#else
+#define init_completion(x) __init_completion(x)
+static inline void complete_acquire(struct completion *x) {}
+static inline void complete_release(struct completion *x) {}
+static inline void complete_release_commit(struct completion *x) {}
+#endif
+
+#ifdef CONFIG_LOCKDEP_COMPLETE
+#define COMPLETION_INITIALIZER(work) \
+       { 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait), \
+       STATIC_CROSS_LOCKDEP_MAP_INIT("(complete)" #work, &(work)) }
+#else
 #define COMPLETION_INITIALIZER(work) \
        { 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }
+#endif
 
 #define COMPLETION_INITIALIZER_ONSTACK(work) \
        ({ init_completion(&work); work; })
@@ -70,7 +113,7 @@ struct completion {
  * This inline function will initialize a dynamically created completion
  * structure.
  */
-static inline void init_completion(struct completion *x)
+static inline void __init_completion(struct completion *x)
 {
        x->done = 0;
        init_waitqueue_head(&x->wait);
index 13fc5ae9bf2f6c96ee82ad140d89e3231ade1c88..566b6ec7b6fe49cfabf6d99666ee041493450321 100644 (file)
@@ -32,6 +32,12 @@ void complete(struct completion *x)
        unsigned long flags;
 
        spin_lock_irqsave(&x->wait.lock, flags);
+
+       /*
+        * Perform commit of crossrelease here.
+        */
+       complete_release_commit(x);
+
        if (x->done != UINT_MAX)
                x->done++;
        __wake_up_locked(&x->wait, TASK_NORMAL, 1);
@@ -92,9 +98,14 @@ __wait_for_common(struct completion *x,
 {
        might_sleep();
 
+       complete_acquire(x);
+
        spin_lock_irq(&x->wait.lock);
        timeout = do_wait_for_common(x, action, timeout, state);
        spin_unlock_irq(&x->wait.lock);
+
+       complete_release(x);
+
        return timeout;
 }
 
index c6038f23bb1a80dfe5acfbd6202257a005236a2a..ebd40d3b0b28ea48acdbf34eb70d8ae0d44f677b 100644 (file)
@@ -1162,6 +1162,15 @@ config LOCKDEP_CROSSRELEASE
         such as page locks or completions can use the lock correctness
         detector, lockdep.
 
+config LOCKDEP_COMPLETE
+       bool "Lock debugging: allow completions to use deadlock detector"
+       depends on PROVE_LOCKING
+       select LOCKDEP_CROSSRELEASE
+       default n
+       help
+        A deadlock caused by wait_for_completion() and complete() can be
+        detected by lockdep using crossrelease feature.
+
 config DEBUG_LOCKDEP
        bool "Lock dependency engine debugging"
        depends on DEBUG_KERNEL && LOCKDEP