drm/nouveau: Refactor context destruction to avoid a lock ordering issue.
authorFrancisco Jerez <currojerez@riseup.net>
Mon, 18 Oct 2010 01:53:39 +0000 (03:53 +0200)
committerBen Skeggs <bskeggs@redhat.com>
Fri, 3 Dec 2010 05:06:35 +0000 (15:06 +1000)
The destroy_context() engine hooks call gpuobj management functions to
release the channel resources, these functions use HARDIRQ-unsafe locks
whereas destroy_context() is called with the HARDIRQ-safe
context_switch_lock held, that's a lock ordering violation.

Push the engine-specific channel destruction logic into destroy_context()
and let the hardware-specific code lock and unlock when it's actually
needed. Change the engine destruction order to avoid a race in the small
gap between pgraph and pfifo context uninitialization.

Reported-by: Marcin Slusarz <marcin.slusarz@gmail.com>
Signed-off-by: Francisco Jerez <currojerez@riseup.net>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
12 files changed:
drivers/gpu/drm/nouveau/nouveau_channel.c
drivers/gpu/drm/nouveau/nouveau_drv.h
drivers/gpu/drm/nouveau/nouveau_state.c
drivers/gpu/drm/nouveau/nv04_fifo.c
drivers/gpu/drm/nouveau/nv04_graph.c
drivers/gpu/drm/nouveau/nv10_fifo.c
drivers/gpu/drm/nouveau/nv10_graph.c
drivers/gpu/drm/nouveau/nv20_graph.c
drivers/gpu/drm/nouveau/nv40_fifo.c
drivers/gpu/drm/nouveau/nv40_graph.c
drivers/gpu/drm/nouveau/nv50_fifo.c
drivers/gpu/drm/nouveau/nv50_graph.c

index 0e2414b0ae508df627afea152e64caeca196c013..9a051fafa7c371a607e3501026b30981cf5efe58 100644 (file)
@@ -313,32 +313,20 @@ nouveau_channel_put(struct nouveau_channel **pchan)
        /* boot it off the hardware */
        pfifo->reassign(dev, false);
 
-       /* We want to give pgraph a chance to idle and get rid of all potential
-        * errors. We need to do this before the lock, otherwise the irq handler
-        * is unable to process them.
+       /* We want to give pgraph a chance to idle and get rid of all
+        * potential errors. We need to do this without the context
+        * switch lock held, otherwise the irq handler is unable to
+        * process them.
         */
        if (pgraph->channel(dev) == chan)
                nouveau_wait_for_idle(dev);
 
-       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
-
-       pgraph->fifo_access(dev, false);
-       if (pgraph->channel(dev) == chan)
-               pgraph->unload_context(dev);
-       pgraph->destroy_context(chan);
-       pgraph->fifo_access(dev, true);
-
-       if (pfifo->channel_id(dev) == chan->id) {
-               pfifo->disable(dev);
-               pfifo->unload_context(dev);
-               pfifo->enable(dev);
-       }
+       /* destroy the engine specific contexts */
        pfifo->destroy_context(chan);
+       pgraph->destroy_context(chan);
 
        pfifo->reassign(dev, true);
 
-       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
-
        /* aside from its resources, the channel should now be dead,
         * remove it from the channel list
         */
index 699d546623bd21ac6cb9e635c2386dbb017e99b3..198dabebafb24ced55163b7fa3ee4f952b79ec3f 100644 (file)
@@ -998,14 +998,12 @@ extern int  nv04_fifo_unload_context(struct drm_device *);
 extern int  nv10_fifo_init(struct drm_device *);
 extern int  nv10_fifo_channel_id(struct drm_device *);
 extern int  nv10_fifo_create_context(struct nouveau_channel *);
-extern void nv10_fifo_destroy_context(struct nouveau_channel *);
 extern int  nv10_fifo_load_context(struct nouveau_channel *);
 extern int  nv10_fifo_unload_context(struct drm_device *);
 
 /* nv40_fifo.c */
 extern int  nv40_fifo_init(struct drm_device *);
 extern int  nv40_fifo_create_context(struct nouveau_channel *);
-extern void nv40_fifo_destroy_context(struct nouveau_channel *);
 extern int  nv40_fifo_load_context(struct nouveau_channel *);
 extern int  nv40_fifo_unload_context(struct drm_device *);
 
index 513c1063fb5e6223fc11f2a73e0843f4e4732702..1a4ba6ccafb10c5124d74e9e3979230036946cdd 100644 (file)
@@ -137,7 +137,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
                engine->fifo.cache_pull         = nv04_fifo_cache_pull;
                engine->fifo.channel_id         = nv10_fifo_channel_id;
                engine->fifo.create_context     = nv10_fifo_create_context;
-               engine->fifo.destroy_context    = nv10_fifo_destroy_context;
+               engine->fifo.destroy_context    = nv04_fifo_destroy_context;
                engine->fifo.load_context       = nv10_fifo_load_context;
                engine->fifo.unload_context     = nv10_fifo_unload_context;
                engine->display.early_init      = nv04_display_early_init;
@@ -191,7 +191,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
                engine->fifo.cache_pull         = nv04_fifo_cache_pull;
                engine->fifo.channel_id         = nv10_fifo_channel_id;
                engine->fifo.create_context     = nv10_fifo_create_context;
-               engine->fifo.destroy_context    = nv10_fifo_destroy_context;
+               engine->fifo.destroy_context    = nv04_fifo_destroy_context;
                engine->fifo.load_context       = nv10_fifo_load_context;
                engine->fifo.unload_context     = nv10_fifo_unload_context;
                engine->display.early_init      = nv04_display_early_init;
@@ -245,7 +245,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
                engine->fifo.cache_pull         = nv04_fifo_cache_pull;
                engine->fifo.channel_id         = nv10_fifo_channel_id;
                engine->fifo.create_context     = nv10_fifo_create_context;
-               engine->fifo.destroy_context    = nv10_fifo_destroy_context;
+               engine->fifo.destroy_context    = nv04_fifo_destroy_context;
                engine->fifo.load_context       = nv10_fifo_load_context;
                engine->fifo.unload_context     = nv10_fifo_unload_context;
                engine->display.early_init      = nv04_display_early_init;
@@ -302,7 +302,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
                engine->fifo.cache_pull         = nv04_fifo_cache_pull;
                engine->fifo.channel_id         = nv10_fifo_channel_id;
                engine->fifo.create_context     = nv40_fifo_create_context;
-               engine->fifo.destroy_context    = nv40_fifo_destroy_context;
+               engine->fifo.destroy_context    = nv04_fifo_destroy_context;
                engine->fifo.load_context       = nv40_fifo_load_context;
                engine->fifo.unload_context     = nv40_fifo_unload_context;
                engine->display.early_init      = nv04_display_early_init;
index 25c439dcdfd932118ed7fcd8378a384feb56318f..4c0d3a8fca68e5d8ff214ed79101ce1bb8163356 100644 (file)
@@ -151,10 +151,27 @@ void
 nv04_fifo_destroy_context(struct nouveau_channel *chan)
 {
        struct drm_device *dev = chan->dev;
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo;
+       unsigned long flags;
 
-       nv_wr32(dev, NV04_PFIFO_MODE,
-               nv_rd32(dev, NV04_PFIFO_MODE) & ~(1 << chan->id));
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pfifo->reassign(dev, false);
+
+       /* Unload the context if it's the currently active one */
+       if (pfifo->channel_id(dev) == chan->id) {
+               pfifo->disable(dev);
+               pfifo->unload_context(dev);
+               pfifo->enable(dev);
+       }
+
+       /* Keep it from being rescheduled */
+       nv_mask(dev, NV04_PFIFO_MODE, 1 << chan->id, 0);
+
+       pfifo->reassign(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
 
+       /* Free the channel resources */
        nouveau_gpuobj_ref(NULL, &chan->ramfc);
 }
 
index 98b9525c1ebdd019d0f55b393627c72a5c265d5e..1e2ad39423304b77ad1d2abc072383485cc9aa38 100644 (file)
@@ -412,10 +412,25 @@ int nv04_graph_create_context(struct nouveau_channel *chan)
 
 void nv04_graph_destroy_context(struct nouveau_channel *chan)
 {
+       struct drm_device *dev = chan->dev;
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
        struct graph_state *pgraph_ctx = chan->pgraph_ctx;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pgraph->fifo_access(dev, false);
+
+       /* Unload the context if it's the currently active one */
+       if (pgraph->channel(dev) == chan)
+               pgraph->unload_context(dev);
 
+       /* Free the context resources */
        kfree(pgraph_ctx);
        chan->pgraph_ctx = NULL;
+
+       pgraph->fifo_access(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
 }
 
 int nv04_graph_load_context(struct nouveau_channel *chan)
index 39328fcce70957e055f312103fcb2b3677f49acc..912556f2e33cd238fda2577b96172631166e44bc 100644 (file)
@@ -73,17 +73,6 @@ nv10_fifo_create_context(struct nouveau_channel *chan)
        return 0;
 }
 
-void
-nv10_fifo_destroy_context(struct nouveau_channel *chan)
-{
-       struct drm_device *dev = chan->dev;
-
-       nv_wr32(dev, NV04_PFIFO_MODE,
-                       nv_rd32(dev, NV04_PFIFO_MODE) & ~(1 << chan->id));
-
-       nouveau_gpuobj_ref(NULL, &chan->ramfc);
-}
-
 static void
 nv10_fifo_do_load_context(struct drm_device *dev, int chid)
 {
index cd931b57cf057c515d4233541bddbafe3d283cc1..e3a87a64c164751948935b7e607b00562f5f971f 100644 (file)
@@ -875,10 +875,25 @@ int nv10_graph_create_context(struct nouveau_channel *chan)
 
 void nv10_graph_destroy_context(struct nouveau_channel *chan)
 {
+       struct drm_device *dev = chan->dev;
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
        struct graph_state *pgraph_ctx = chan->pgraph_ctx;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pgraph->fifo_access(dev, false);
+
+       /* Unload the context if it's the currently active one */
+       if (pgraph->channel(dev) == chan)
+               pgraph->unload_context(dev);
 
+       /* Free the context resources */
        kfree(pgraph_ctx);
        chan->pgraph_ctx = NULL;
+
+       pgraph->fifo_access(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
 }
 
 void
index 12ab9cd56ecad286440d9d60d9fe97dd3be0fb7c..8a0402012557bff82f8df5f3bd15716d55a58acd 100644 (file)
@@ -425,9 +425,21 @@ nv20_graph_destroy_context(struct nouveau_channel *chan)
        struct drm_device *dev = chan->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
+       unsigned long flags;
 
-       nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pgraph->fifo_access(dev, false);
+
+       /* Unload the context if it's the currently active one */
+       if (pgraph->channel(dev) == chan)
+               pgraph->unload_context(dev);
+
+       pgraph->fifo_access(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
+
+       /* Free the context resources */
        nv_wo32(pgraph->ctx_table, chan->id * 4, 0);
+       nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
 }
 
 int
index 3c7be3dc8b884834ff9e923752f6e1d575c1bca4..311ac9ea5d53e7a98fe1b19f4b30289586c48a3d 100644 (file)
@@ -70,17 +70,6 @@ nv40_fifo_create_context(struct nouveau_channel *chan)
        return 0;
 }
 
-void
-nv40_fifo_destroy_context(struct nouveau_channel *chan)
-{
-       struct drm_device *dev = chan->dev;
-
-       nv_wr32(dev, NV04_PFIFO_MODE,
-               nv_rd32(dev, NV04_PFIFO_MODE) & ~(1 << chan->id));
-
-       nouveau_gpuobj_ref(NULL, &chan->ramfc);
-}
-
 static void
 nv40_fifo_do_load_context(struct drm_device *dev, int chid)
 {
index e0b41a26447f53890200d69dca894a2d187eb7bc..70d97cde49d08a030680ec98be059960b6412702 100644 (file)
@@ -79,6 +79,22 @@ nv40_graph_create_context(struct nouveau_channel *chan)
 void
 nv40_graph_destroy_context(struct nouveau_channel *chan)
 {
+       struct drm_device *dev = chan->dev;
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pgraph->fifo_access(dev, false);
+
+       /* Unload the context if it's the currently active one */
+       if (pgraph->channel(dev) == chan)
+               pgraph->unload_context(dev);
+
+       pgraph->fifo_access(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
+
+       /* Free the context resources */
        nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
 }
 
index 815960fe4f436d72b4cb4b2ac6b5706b4d204f33..d3295aae0c4ed60e46383214f74a4ec12f9c3937 100644 (file)
@@ -292,10 +292,23 @@ void
 nv50_fifo_destroy_context(struct nouveau_channel *chan)
 {
        struct drm_device *dev = chan->dev;
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo;
        struct nouveau_gpuobj *ramfc = NULL;
+       unsigned long flags;
 
        NV_DEBUG(dev, "ch%d\n", chan->id);
 
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pfifo->reassign(dev, false);
+
+       /* Unload the context if it's the currently active one */
+       if (pfifo->channel_id(dev) == chan->id) {
+               pfifo->disable(dev);
+               pfifo->unload_context(dev);
+               pfifo->enable(dev);
+       }
+
        /* This will ensure the channel is seen as disabled. */
        nouveau_gpuobj_ref(chan->ramfc, &ramfc);
        nouveau_gpuobj_ref(NULL, &chan->ramfc);
@@ -306,6 +319,10 @@ nv50_fifo_destroy_context(struct nouveau_channel *chan)
                nv50_fifo_channel_disable(dev, 127);
        nv50_fifo_playlist_update(dev);
 
+       pfifo->reassign(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
+
+       /* Free the channel resources */
        nouveau_gpuobj_ref(NULL, &ramfc);
        nouveau_gpuobj_ref(NULL, &chan->cache);
 }
index 24a3f848757982426659208143832bf76931ff1d..dcc9175fb7945ba970eafa8be6531a6ae5a87f6f 100644 (file)
@@ -242,17 +242,28 @@ nv50_graph_destroy_context(struct nouveau_channel *chan)
 {
        struct drm_device *dev = chan->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
        int i, hdr = (dev_priv->chipset == 0x50) ? 0x200 : 0x20;
+       unsigned long flags;
 
        NV_DEBUG(dev, "ch%d\n", chan->id);
 
        if (!chan->ramin)
                return;
 
+       spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+       pgraph->fifo_access(dev, false);
+
+       if (pgraph->channel(dev) == chan)
+               pgraph->unload_context(dev);
+
        for (i = hdr; i < hdr + 24; i += 4)
                nv_wo32(chan->ramin, i, 0);
        dev_priv->engine.instmem.flush(dev);
 
+       pgraph->fifo_access(dev, true);
+       spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
+
        nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
 }