drm/nouveau/core: split lock into list+exec and enable refcount locks
authorBen Skeggs <bskeggs@redhat.com>
Wed, 2 Oct 2013 23:59:16 +0000 (09:59 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Fri, 8 Nov 2013 05:36:06 +0000 (15:36 +1000)
This fixes a reported locking inversion when interacting with the DRM
core's vblank routines.

Reviewed-by: Maarten Lankhorst <maarten.lankhorst@canonical.com>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/core/core/event.c
drivers/gpu/drm/nouveau/core/include/core/event.h

index 313afcc65e0fa5d180727dccb22e0aa54d6dc076..3f3c76581a9e3047f3716943f22c489ffea0c946 100644 (file)
 #include <core/os.h>
 #include <core/event.h>
 
-static void
-nouveau_event_put_locked(struct nouveau_event *event, int index,
-                        struct nouveau_eventh *handler)
-{
-       if (__test_and_clear_bit(NVKM_EVENT_ENABLE, &handler->flags)) {
-               if (!--event->index[index].refs) {
-                       if (event->disable)
-                               event->disable(event, index);
-               }
-               list_del(&handler->head);
-       }
-}
-
 void
 nouveau_event_put(struct nouveau_eventh *handler)
 {
        struct nouveau_event *event = handler->event;
        unsigned long flags;
-       spin_lock_irqsave(&event->lock, flags);
-       nouveau_event_put_locked(handler->event, handler->index, handler);
-       spin_unlock_irqrestore(&event->lock, flags);
+       if (__test_and_clear_bit(NVKM_EVENT_ENABLE, &handler->flags)) {
+               spin_lock_irqsave(&event->refs_lock, flags);
+               if (!--event->index[handler->index].refs) {
+                       if (event->disable)
+                               event->disable(event, handler->index);
+               }
+               spin_unlock_irqrestore(&event->refs_lock, flags);
+       }
 }
 
 void
@@ -51,22 +43,25 @@ nouveau_event_get(struct nouveau_eventh *handler)
 {
        struct nouveau_event *event = handler->event;
        unsigned long flags;
-
-       spin_lock_irqsave(&event->lock, flags);
        if (!__test_and_set_bit(NVKM_EVENT_ENABLE, &handler->flags)) {
-               list_add(&handler->head, &event->index[handler->index].list);
+               spin_lock_irqsave(&event->refs_lock, flags);
                if (!event->index[handler->index].refs++) {
                        if (event->enable)
                                event->enable(event, handler->index);
                }
+               spin_unlock_irqrestore(&event->refs_lock, flags);
        }
-       spin_unlock_irqrestore(&event->lock, flags);
 }
 
 static void
 nouveau_event_fini(struct nouveau_eventh *handler)
 {
+       struct nouveau_event *event = handler->event;
+       unsigned long flags;
        nouveau_event_put(handler);
+       spin_lock_irqsave(&event->list_lock, flags);
+       list_del(&handler->head);
+       spin_unlock_irqrestore(&event->list_lock, flags);
 }
 
 static int
@@ -74,13 +69,20 @@ nouveau_event_init(struct nouveau_event *event, int index,
                   int (*func)(void *, int), void *priv,
                   struct nouveau_eventh *handler)
 {
+       unsigned long flags;
+
        if (index >= event->index_nr)
                return -EINVAL;
+
        handler->event = event;
        handler->flags = 0;
        handler->index = index;
        handler->func = func;
        handler->priv = priv;
+
+       spin_lock_irqsave(&event->list_lock, flags);
+       list_add_tail(&handler->head, &event->index[index].list);
+       spin_unlock_irqrestore(&event->list_lock, flags);
        return 0;
 }
 
@@ -116,19 +118,19 @@ nouveau_event_ref(struct nouveau_eventh *handler, struct nouveau_eventh **ref)
 void
 nouveau_event_trigger(struct nouveau_event *event, int index)
 {
-       struct nouveau_eventh *handler, *temp;
+       struct nouveau_eventh *handler;
        unsigned long flags;
 
-       if (index >= event->index_nr)
+       if (WARN_ON(index >= event->index_nr))
                return;
 
-       spin_lock_irqsave(&event->lock, flags);
-       list_for_each_entry_safe(handler, temp, &event->index[index].list, head) {
-               if (handler->func(handler->priv, index) == NVKM_EVENT_DROP) {
-                       nouveau_event_put_locked(event, index, handler);
-               }
+       spin_lock_irqsave(&event->list_lock, flags);
+       list_for_each_entry(handler, &event->index[index].list, head) {
+               if (test_bit(NVKM_EVENT_ENABLE, &handler->flags) &&
+                   handler->func(handler->priv, index) == NVKM_EVENT_DROP)
+                       nouveau_event_put(handler);
        }
-       spin_unlock_irqrestore(&event->lock, flags);
+       spin_unlock_irqrestore(&event->list_lock, flags);
 }
 
 void
@@ -152,7 +154,8 @@ nouveau_event_create(int index_nr, struct nouveau_event **pevent)
        if (!event)
                return -ENOMEM;
 
-       spin_lock_init(&event->lock);
+       spin_lock_init(&event->list_lock);
+       spin_lock_init(&event->refs_lock);
        for (i = 0; i < index_nr; i++)
                INIT_LIST_HEAD(&event->index[i].list);
        event->index_nr = index_nr;
index b649c75d71985946b292cfc6478064649305f566..5d539ebff3ed3c48d5280820e5875c434d5dcea8 100644 (file)
@@ -18,7 +18,8 @@ struct nouveau_eventh {
 };
 
 struct nouveau_event {
-       spinlock_t lock;
+       spinlock_t list_lock;
+       spinlock_t refs_lock;
 
        void *priv;
        void (*enable)(struct nouveau_event *, int index);