Commit 3945e475 authored by Francisco Jerez's avatar Francisco Jerez Committed by Ben Skeggs

drm/nouveau: Refactor context destruction to avoid a lock ordering issue.

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: default avatarMarcin Slusarz <marcin.slusarz@gmail.com>
Signed-off-by: default avatarFrancisco Jerez <currojerez@riseup.net>
Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
parent fcccab2e
...@@ -313,32 +313,20 @@ nouveau_channel_put(struct nouveau_channel **pchan) ...@@ -313,32 +313,20 @@ nouveau_channel_put(struct nouveau_channel **pchan)
/* boot it off the hardware */ /* boot it off the hardware */
pfifo->reassign(dev, false); pfifo->reassign(dev, false);
/* We want to give pgraph a chance to idle and get rid of all potential /* We want to give pgraph a chance to idle and get rid of all
* errors. We need to do this before the lock, otherwise the irq handler * potential errors. We need to do this without the context
* is unable to process them. * switch lock held, otherwise the irq handler is unable to
* process them.
*/ */
if (pgraph->channel(dev) == chan) if (pgraph->channel(dev) == chan)
nouveau_wait_for_idle(dev); nouveau_wait_for_idle(dev);
spin_lock_irqsave(&dev_priv->context_switch_lock, flags); /* destroy the engine specific contexts */
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);
}
pfifo->destroy_context(chan); pfifo->destroy_context(chan);
pgraph->destroy_context(chan);
pfifo->reassign(dev, true); pfifo->reassign(dev, true);
spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
/* aside from its resources, the channel should now be dead, /* aside from its resources, the channel should now be dead,
* remove it from the channel list * remove it from the channel list
*/ */
......
...@@ -998,14 +998,12 @@ extern int nv04_fifo_unload_context(struct drm_device *); ...@@ -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_init(struct drm_device *);
extern int nv10_fifo_channel_id(struct drm_device *); extern int nv10_fifo_channel_id(struct drm_device *);
extern int nv10_fifo_create_context(struct nouveau_channel *); 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_load_context(struct nouveau_channel *);
extern int nv10_fifo_unload_context(struct drm_device *); extern int nv10_fifo_unload_context(struct drm_device *);
/* nv40_fifo.c */ /* nv40_fifo.c */
extern int nv40_fifo_init(struct drm_device *); extern int nv40_fifo_init(struct drm_device *);
extern int nv40_fifo_create_context(struct nouveau_channel *); 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_load_context(struct nouveau_channel *);
extern int nv40_fifo_unload_context(struct drm_device *); extern int nv40_fifo_unload_context(struct drm_device *);
......
...@@ -137,7 +137,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev) ...@@ -137,7 +137,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
engine->fifo.cache_pull = nv04_fifo_cache_pull; engine->fifo.cache_pull = nv04_fifo_cache_pull;
engine->fifo.channel_id = nv10_fifo_channel_id; engine->fifo.channel_id = nv10_fifo_channel_id;
engine->fifo.create_context = nv10_fifo_create_context; 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.load_context = nv10_fifo_load_context;
engine->fifo.unload_context = nv10_fifo_unload_context; engine->fifo.unload_context = nv10_fifo_unload_context;
engine->display.early_init = nv04_display_early_init; engine->display.early_init = nv04_display_early_init;
...@@ -191,7 +191,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev) ...@@ -191,7 +191,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
engine->fifo.cache_pull = nv04_fifo_cache_pull; engine->fifo.cache_pull = nv04_fifo_cache_pull;
engine->fifo.channel_id = nv10_fifo_channel_id; engine->fifo.channel_id = nv10_fifo_channel_id;
engine->fifo.create_context = nv10_fifo_create_context; 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.load_context = nv10_fifo_load_context;
engine->fifo.unload_context = nv10_fifo_unload_context; engine->fifo.unload_context = nv10_fifo_unload_context;
engine->display.early_init = nv04_display_early_init; engine->display.early_init = nv04_display_early_init;
...@@ -245,7 +245,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev) ...@@ -245,7 +245,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
engine->fifo.cache_pull = nv04_fifo_cache_pull; engine->fifo.cache_pull = nv04_fifo_cache_pull;
engine->fifo.channel_id = nv10_fifo_channel_id; engine->fifo.channel_id = nv10_fifo_channel_id;
engine->fifo.create_context = nv10_fifo_create_context; 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.load_context = nv10_fifo_load_context;
engine->fifo.unload_context = nv10_fifo_unload_context; engine->fifo.unload_context = nv10_fifo_unload_context;
engine->display.early_init = nv04_display_early_init; engine->display.early_init = nv04_display_early_init;
...@@ -302,7 +302,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev) ...@@ -302,7 +302,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
engine->fifo.cache_pull = nv04_fifo_cache_pull; engine->fifo.cache_pull = nv04_fifo_cache_pull;
engine->fifo.channel_id = nv10_fifo_channel_id; engine->fifo.channel_id = nv10_fifo_channel_id;
engine->fifo.create_context = nv40_fifo_create_context; 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.load_context = nv40_fifo_load_context;
engine->fifo.unload_context = nv40_fifo_unload_context; engine->fifo.unload_context = nv40_fifo_unload_context;
engine->display.early_init = nv04_display_early_init; engine->display.early_init = nv04_display_early_init;
......
...@@ -151,10 +151,27 @@ void ...@@ -151,10 +151,27 @@ void
nv04_fifo_destroy_context(struct nouveau_channel *chan) nv04_fifo_destroy_context(struct nouveau_channel *chan)
{ {
struct drm_device *dev = chan->dev; 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, spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
nv_rd32(dev, NV04_PFIFO_MODE) & ~(1 << chan->id)); 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); nouveau_gpuobj_ref(NULL, &chan->ramfc);
} }
......
...@@ -412,10 +412,25 @@ int nv04_graph_create_context(struct nouveau_channel *chan) ...@@ -412,10 +412,25 @@ int nv04_graph_create_context(struct nouveau_channel *chan)
void nv04_graph_destroy_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; 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); kfree(pgraph_ctx);
chan->pgraph_ctx = NULL; 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) int nv04_graph_load_context(struct nouveau_channel *chan)
......
...@@ -73,17 +73,6 @@ nv10_fifo_create_context(struct nouveau_channel *chan) ...@@ -73,17 +73,6 @@ nv10_fifo_create_context(struct nouveau_channel *chan)
return 0; 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 static void
nv10_fifo_do_load_context(struct drm_device *dev, int chid) nv10_fifo_do_load_context(struct drm_device *dev, int chid)
{ {
......
...@@ -875,10 +875,25 @@ int nv10_graph_create_context(struct nouveau_channel *chan) ...@@ -875,10 +875,25 @@ int nv10_graph_create_context(struct nouveau_channel *chan)
void nv10_graph_destroy_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; 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); kfree(pgraph_ctx);
chan->pgraph_ctx = NULL; chan->pgraph_ctx = NULL;
pgraph->fifo_access(dev, true);
spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
} }
void void
......
...@@ -425,9 +425,21 @@ nv20_graph_destroy_context(struct nouveau_channel *chan) ...@@ -425,9 +425,21 @@ nv20_graph_destroy_context(struct nouveau_channel *chan)
struct drm_device *dev = chan->dev; struct drm_device *dev = chan->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private; struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph; 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); nv_wo32(pgraph->ctx_table, chan->id * 4, 0);
nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
} }
int int
......
...@@ -70,17 +70,6 @@ nv40_fifo_create_context(struct nouveau_channel *chan) ...@@ -70,17 +70,6 @@ nv40_fifo_create_context(struct nouveau_channel *chan)
return 0; 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 static void
nv40_fifo_do_load_context(struct drm_device *dev, int chid) nv40_fifo_do_load_context(struct drm_device *dev, int chid)
{ {
......
...@@ -79,6 +79,22 @@ nv40_graph_create_context(struct nouveau_channel *chan) ...@@ -79,6 +79,22 @@ nv40_graph_create_context(struct nouveau_channel *chan)
void void
nv40_graph_destroy_context(struct nouveau_channel *chan) 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); nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
} }
......
...@@ -292,10 +292,23 @@ void ...@@ -292,10 +292,23 @@ void
nv50_fifo_destroy_context(struct nouveau_channel *chan) nv50_fifo_destroy_context(struct nouveau_channel *chan)
{ {
struct drm_device *dev = chan->dev; 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; struct nouveau_gpuobj *ramfc = NULL;
unsigned long flags;
NV_DEBUG(dev, "ch%d\n", chan->id); 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. */ /* This will ensure the channel is seen as disabled. */
nouveau_gpuobj_ref(chan->ramfc, &ramfc); nouveau_gpuobj_ref(chan->ramfc, &ramfc);
nouveau_gpuobj_ref(NULL, &chan->ramfc); nouveau_gpuobj_ref(NULL, &chan->ramfc);
...@@ -306,6 +319,10 @@ nv50_fifo_destroy_context(struct nouveau_channel *chan) ...@@ -306,6 +319,10 @@ nv50_fifo_destroy_context(struct nouveau_channel *chan)
nv50_fifo_channel_disable(dev, 127); nv50_fifo_channel_disable(dev, 127);
nv50_fifo_playlist_update(dev); 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, &ramfc);
nouveau_gpuobj_ref(NULL, &chan->cache); nouveau_gpuobj_ref(NULL, &chan->cache);
} }
......
...@@ -242,17 +242,28 @@ nv50_graph_destroy_context(struct nouveau_channel *chan) ...@@ -242,17 +242,28 @@ nv50_graph_destroy_context(struct nouveau_channel *chan)
{ {
struct drm_device *dev = chan->dev; struct drm_device *dev = chan->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private; 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; int i, hdr = (dev_priv->chipset == 0x50) ? 0x200 : 0x20;
unsigned long flags;
NV_DEBUG(dev, "ch%d\n", chan->id); NV_DEBUG(dev, "ch%d\n", chan->id);
if (!chan->ramin) if (!chan->ramin)
return; 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) for (i = hdr; i < hdr + 24; i += 4)
nv_wo32(chan->ramin, i, 0); nv_wo32(chan->ramin, i, 0);
dev_priv->engine.instmem.flush(dev); 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); nouveau_gpuobj_ref(NULL, &chan->ramin_grctx);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment