drm: Use lockmgr locks with Linux wait queues
authorFrançois Tigeot <ftigeot@wolfpond.org>
Tue, 18 Nov 2014 21:18:03 +0000 (22:18 +0100)
committerFrançois Tigeot <ftigeot@wolfpond.org>
Tue, 18 Nov 2014 21:33:15 +0000 (22:33 +0100)
* On Linux, it is possible to grab a second spinlock in an already
  spinlock-protected code section.

* The wait_event() macro is used in such a situation in the i915 driver.
  One of the event checks itself tries to grab a second lock. What's more,
  this second lock is a lockmgr lock in the DragonFly kernel. This results
  in the following situation:

    spinlock
    lockmgr LK_EXCLUSIVE
    spinunlock

* Unfortunately if the lockmgr lock can't be acquired, the thread is put
  to sleep and a thread sleeping with a spinlock held leads to a general
  system freeze or a kernel panic.

* For that reason, we can't use a spinlock in Linux wait queues. Change
  the internal wait_queue_head_t lock to a lockmgr lock.

Thanks go to Imre Vadász for spotting this horrible issue.

sys/dev/drm/i915/i915_gem.c
sys/dev/drm/include/linux/completion.h
sys/dev/drm/include/linux/wait.h

index 5bcd51e..681516f 100644 (file)
@@ -153,9 +153,9 @@ i915_gem_wait_for_error(struct drm_device *dev)
                 * end up waiting upon a subsequent completion event that
                 * will never happen.
                 */
-               spin_lock(&x->wait.lock);
+               lockmgr(&x->wait.lock, LK_EXCLUSIVE);
                x->done++;
-               spin_unlock(&x->wait.lock);
+               lockmgr(&x->wait.lock, LK_RELEASE);
        }
        return 0;
 }
@@ -587,9 +587,9 @@ i915_gem_check_wedge(struct drm_i915_private *dev_priv,
                bool recovery_complete;
 
                /* Give the error handler a chance to run. */
-               spin_lock(&x->wait.lock);
+               lockmgr(&x->wait.lock, LK_EXCLUSIVE);
                recovery_complete = x->done > 0;
-               spin_unlock(&x->wait.lock);
+               lockmgr(&x->wait.lock, LK_RELEASE);
 
                /* Non-interruptible callers can't handle -EAGAIN, hence return
                 * -EIO unconditionally for these. */
index 9129605..eba19cb 100644 (file)
@@ -50,23 +50,23 @@ init_completion(struct completion *c)
 static inline void
 complete(struct completion *c)
 {
-       spin_lock(&c->wait.lock);
+       lockmgr(&c->wait.lock, LK_EXCLUSIVE);
        c->done++;
-       spin_unlock(&c->wait.lock);
+       lockmgr(&c->wait.lock, LK_RELEASE);
        wakeup_one(&c->wait);
 }
 
 static inline void
 complete_all(struct completion *c)
 {
-       spin_lock(&c->wait.lock);
+       lockmgr(&c->wait.lock, LK_EXCLUSIVE);
        c->done++;
-       spin_unlock(&c->wait.lock);
+       lockmgr(&c->wait.lock, LK_RELEASE);
        wakeup(&c->wait);
 }
 
 static inline long
-wait_for_completion_interruptible_timeout(struct completion *x,
+wait_for_completion_interruptible_timeout(struct completion *c,
                unsigned long timeout)
 {
        int start_jiffies, elapsed_jiffies, remaining_jiffies;
@@ -75,9 +75,9 @@ wait_for_completion_interruptible_timeout(struct completion *x,
 
        start_jiffies = ticks;
 
-       spin_lock(&x->wait.lock);
-       while (x->done == 0 && !timeout_expired) {
-               ret = ssleep(&x->wait, &x->wait.lock, PCATCH, "wfcit", timeout);
+       lockmgr(&c->wait.lock, LK_EXCLUSIVE);
+       while (c->done == 0 && !timeout_expired) {
+               ret = lksleep(&c->wait, &c->wait.lock, PCATCH, "wfcit", timeout);
                switch(ret) {
                case EWOULDBLOCK:
                        timeout_expired = true;
@@ -91,7 +91,7 @@ wait_for_completion_interruptible_timeout(struct completion *x,
                        break;
                }
        }
-       spin_unlock(&x->wait.lock);
+       lockmgr(&c->wait.lock, LK_RELEASE);
 
        if (awakened) {
                elapsed_jiffies = ticks - start_jiffies;
index ba55390..c10f89b 100644 (file)
 #include <sys/param.h>
 
 typedef struct {
-       struct spinlock lock;
+       struct lock     lock;
 } wait_queue_head_t;
 
 static inline void
 init_waitqueue_head(wait_queue_head_t *eq)
 {
-       spin_init(&eq->lock, "linux_waitqueue");
+       lockinit(&eq->lock, "lwq", 0, LK_CANRECURSE);
 }
 
 #define wake_up(eq)            wakeup_one(eq)
@@ -67,12 +67,12 @@ init_waitqueue_head(wait_queue_head_t *eq)
                                                                        \
        start_jiffies = ticks;                                          \
                                                                        \
-       spin_lock(&wq.lock);                                            \
+       lockmgr(&wq.lock, LK_EXCLUSIVE);                                \
        while (1) {                                                     \
                if (condition)                                          \
                        break;                                          \
                                                                        \
-               ret = ssleep(&wq, &wq.lock, flags,                      \
+               ret = lksleep(&wq, &wq.lock, flags,                     \
                                        "lwe", timeout_jiffies);        \
                if (ret == EINTR || ret == ERESTART) {                  \
                        interrupted = true;                             \
@@ -83,7 +83,7 @@ init_waitqueue_head(wait_queue_head_t *eq)
                        break;                                          \
                }                                                       \
        }                                                               \
-       spin_unlock(&wq.lock);                                          \
+       lockmgr(&wq.lock, LK_RELEASE);                                  \
                                                                        \
        elapsed_jiffies = ticks - start_jiffies;                        \
        remaining_jiffies = timeout_jiffies - elapsed_jiffies;          \