From 3b2598c70bce098ea8c43ff37862bcce1663514b Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Mon, 10 Dec 2007 15:16:05 +1100 Subject: nouveau: fix elusive dma bug In some situations WAIT_RING would get called while the GPU was processing data from outside the "master" ring, which caused dma.free to be updated incorrectly and much fun was had. WAIT_RING will now wait until it reads GET values from within the main ring buffer before calculating free space. --- src/mesa/drivers/dri/nouveau_winsys/nouveau_dma.c | 54 ++++++++++++++--------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/mesa/drivers/dri/nouveau_winsys/nouveau_dma.c b/src/mesa/drivers/dri/nouveau_winsys/nouveau_dma.c index 6ec390ec769..5739e0010d4 100644 --- a/src/mesa/drivers/dri/nouveau_winsys/nouveau_dma.c +++ b/src/mesa/drivers/dri/nouveau_winsys/nouveau_dma.c @@ -28,14 +28,29 @@ #include "nouveau_dma.h" #include "nouveau_local.h" -#define READ_GET(ch) ((*(ch)->get - (ch)->dma.base) >> 2) -#define WRITE_PUT(ch, val) do { \ - volatile int dum; \ - NOUVEAU_DMA_BARRIER; \ - dum=READ_GET(ch); \ - *(ch)->put = (((val) << 2) + (ch)->dma.base); \ - NOUVEAU_DMA_BARRIER; \ -} while(0) +static __inline__ uint32_t +READ_GET(struct nouveau_channel_priv *nvchan) +{ + return ((*nvchan->get - nvchan->dma.base) >> 2); +} + +static __inline__ void +WRITE_PUT(struct nouveau_channel_priv *nvchan, uint32_t val) +{ + uint32_t put = ((val << 2) + nvchan->dma.base); + volatile int dum; + + NOUVEAU_DMA_BARRIER; + dum = READ_GET(nvchan); + + *nvchan->put = put; + nvchan->dma.put = val; +#ifdef NOUVEAU_DMA_TRACE + NOUVEAU_MSG("WRITE_PUT %d/0x%08x\n", nvchan->drm.channel, put); +#endif + + NOUVEAU_DMA_BARRIER; +} void nouveau_dma_channel_init(struct nouveau_channel *userchan) @@ -57,6 +72,8 @@ nouveau_dma_channel_init(struct nouveau_channel *userchan) return - EBUSY; \ } while(0) +#define IN_MASTER_RING(chan, ptr) ((ptr) <= (chan)->dma.max) + int nouveau_dma_wait(struct nouveau_channel *userchan, int size) { @@ -67,7 +84,11 @@ nouveau_dma_wait(struct nouveau_channel *userchan, int size) t_start = NOUVEAU_TIME_MSEC(); while (chan->dma.free < size) { + CHECK_TIMEOUT(); + get = READ_GET(chan); + if (!IN_MASTER_RING(chan, get)) + continue; if (chan->dma.put >= get) { chan->dma.free = chan->dma.max - chan->dma.cur; @@ -86,6 +107,8 @@ nouveau_dma_wait(struct nouveau_channel *userchan, int size) do { CHECK_TIMEOUT(); get = READ_GET(chan); + if (!IN_MASTER_RING(chan, get)) + continue; } while (get <= RING_SKIPS); } @@ -96,8 +119,6 @@ nouveau_dma_wait(struct nouveau_channel *userchan, int size) } else { chan->dma.free = get - chan->dma.cur - 1; } - - CHECK_TIMEOUT(); } return 0; @@ -135,9 +156,6 @@ void nouveau_dma_kickoff(struct nouveau_channel *userchan) { struct nouveau_channel_priv *chan = nouveau_channel(userchan); - uint32_t put_offset; - int i; - volatile int dum; if (chan->dma.cur == chan->dma.put) return; @@ -165,13 +183,5 @@ nouveau_dma_kickoff(struct nouveau_channel *userchan) } #endif - put_offset = (chan->dma.cur << 2) + chan->dma.base; -#ifdef NOUVEAU_DMA_TRACE - NOUVEAU_MSG("FIRE_RING %d/0x%08x\n", chan->drm.channel, put_offset); -#endif - chan->dma.put = chan->dma.cur; - NOUVEAU_DMA_BARRIER; - dum = READ_GET(chan); - *chan->put = put_offset; - NOUVEAU_DMA_BARRIER; + WRITE_PUT(chan, chan->dma.cur); } -- cgit v1.2.3