drm/nouveau: Avoid lock dependency between ramht and ramin spinlocks.
authorFrancisco Jerez <currojerez@riseup.net>
Mon, 11 Oct 2010 01:37:32 +0000 (03:37 +0200)
committerBen Skeggs <bskeggs@redhat.com>
Thu, 18 Nov 2010 04:38:22 +0000 (14:38 +1000)
The ramht code called some gpuobj functions with the HARDIRQ-safe
RAMHT spinlock held, this could potentially lead to a dead lock
because ramin_lock is HARDIRQ-unsafe.

Signed-off-by: Francisco Jerez <currojerez@riseup.net>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nouveau_ramht.c

index 7f16697cc96ce6438896414735b5fa4d12a84a34..2d8580927ca4579075cd870e5cfdf6459c44d9c6 100644 (file)
@@ -153,26 +153,42 @@ nouveau_ramht_insert(struct nouveau_channel *chan, u32 handle,
        return -ENOMEM;
 }
 
+static struct nouveau_ramht_entry *
+nouveau_ramht_remove_entry(struct nouveau_channel *chan, u32 handle)
+{
+       struct nouveau_ramht *ramht = chan ? chan->ramht : NULL;
+       struct nouveau_ramht_entry *entry;
+       unsigned long flags;
+
+       if (!ramht)
+               return NULL;
+
+       spin_lock_irqsave(&ramht->lock, flags);
+       list_for_each_entry(entry, &ramht->entries, head) {
+               if (entry->channel == chan &&
+                   (!handle || entry->handle == handle)) {
+                       list_del(&entry->head);
+                       spin_unlock_irqrestore(&ramht->lock, flags);
+
+                       return entry;
+               }
+       }
+       spin_unlock_irqrestore(&ramht->lock, flags);
+
+       return NULL;
+}
+
 static void
-nouveau_ramht_remove_locked(struct nouveau_channel *chan, u32 handle)
+nouveau_ramht_remove_hash(struct nouveau_channel *chan, u32 handle)
 {
        struct drm_device *dev = chan->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct nouveau_instmem_engine *instmem = &dev_priv->engine.instmem;
        struct nouveau_gpuobj *ramht = chan->ramht->gpuobj;
-       struct nouveau_ramht_entry *entry, *tmp;
+       unsigned long flags;
        u32 co, ho;
 
-       list_for_each_entry_safe(entry, tmp, &chan->ramht->entries, head) {
-               if (entry->channel != chan || entry->handle != handle)
-                       continue;
-
-               nouveau_gpuobj_ref(NULL, &entry->gpuobj);
-               list_del(&entry->head);
-               kfree(entry);
-               break;
-       }
-
+       spin_lock_irqsave(&chan->ramht->lock, flags);
        co = ho = nouveau_ramht_hash_handle(chan, handle);
        do {
                if (nouveau_ramht_entry_valid(dev, ramht, co) &&
@@ -184,7 +200,7 @@ nouveau_ramht_remove_locked(struct nouveau_channel *chan, u32 handle)
                        nv_wo32(ramht, co + 0, 0x00000000);
                        nv_wo32(ramht, co + 4, 0x00000000);
                        instmem->flush(dev);
-                       return;
+                       goto out;
                }
 
                co += 8;
@@ -194,17 +210,22 @@ nouveau_ramht_remove_locked(struct nouveau_channel *chan, u32 handle)
 
        NV_ERROR(dev, "RAMHT entry not found. ch=%d, handle=0x%08x\n",
                 chan->id, handle);
+out:
+       spin_unlock_irqrestore(&chan->ramht->lock, flags);
 }
 
 void
 nouveau_ramht_remove(struct nouveau_channel *chan, u32 handle)
 {
-       struct nouveau_ramht *ramht = chan->ramht;
-       unsigned long flags;
+       struct nouveau_ramht_entry *entry;
 
-       spin_lock_irqsave(&ramht->lock, flags);
-       nouveau_ramht_remove_locked(chan, handle);
-       spin_unlock_irqrestore(&ramht->lock, flags);
+       entry = nouveau_ramht_remove_entry(chan, handle);
+       if (!entry)
+               return;
+
+       nouveau_ramht_remove_hash(chan, entry->handle);
+       nouveau_gpuobj_ref(NULL, &entry->gpuobj);
+       kfree(entry);
 }
 
 struct nouveau_gpuobj *
@@ -265,23 +286,19 @@ void
 nouveau_ramht_ref(struct nouveau_ramht *ref, struct nouveau_ramht **ptr,
                  struct nouveau_channel *chan)
 {
-       struct nouveau_ramht_entry *entry, *tmp;
+       struct nouveau_ramht_entry *entry;
        struct nouveau_ramht *ramht;
-       unsigned long flags;
 
        if (ref)
                kref_get(&ref->refcount);
 
        ramht = *ptr;
        if (ramht) {
-               spin_lock_irqsave(&ramht->lock, flags);
-               list_for_each_entry_safe(entry, tmp, &ramht->entries, head) {
-                       if (entry->channel != chan)
-                               continue;
-
-                       nouveau_ramht_remove_locked(chan, entry->handle);
+               while ((entry = nouveau_ramht_remove_entry(chan, 0))) {
+                       nouveau_ramht_remove_hash(chan, entry->handle);
+                       nouveau_gpuobj_ref(NULL, &entry->gpuobj);
+                       kfree(entry);
                }
-               spin_unlock_irqrestore(&ramht->lock, flags);
 
                kref_put(&ramht->refcount, nouveau_ramht_del);
        }