From 15689c3c819d1d659cff464f427f398758a683de Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Thu, 3 Oct 2013 09:59:16 +1000 Subject: [PATCH] drm/nouveau/core: split lock into list+exec and enable refcount locks This fixes a reported locking inversion when interacting with the DRM core's vblank routines. Reviewed-by: Maarten Lankhorst Signed-off-by: Ben Skeggs --- drivers/gpu/drm/nouveau/core/core/event.c | 61 ++++++++++--------- .../gpu/drm/nouveau/core/include/core/event.h | 3 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/nouveau/core/core/event.c b/drivers/gpu/drm/nouveau/core/core/event.c index 313afcc65e0f..3f3c76581a9e 100644 --- a/drivers/gpu/drm/nouveau/core/core/event.c +++ b/drivers/gpu/drm/nouveau/core/core/event.c @@ -23,27 +23,19 @@ #include #include -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; diff --git a/drivers/gpu/drm/nouveau/core/include/core/event.h b/drivers/gpu/drm/nouveau/core/include/core/event.h index b649c75d7198..5d539ebff3ed 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/event.h +++ b/drivers/gpu/drm/nouveau/core/include/core/event.h @@ -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); -- 2.39.5