summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Skeggs <skeggsb@gmail.com>2007-12-10 15:16:05 +1100
committerBen Skeggs <skeggsb@gmail.com>2007-12-10 15:17:20 +1100
commit3b2598c70bce098ea8c43ff37862bcce1663514b (patch)
treec6fc30b811f4aa4045562fb271c9426785022e06
parent7d8368790fabc19e51add0fd9f1d1f85b7803cdf (diff)
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.
-rw-r--r--src/mesa/drivers/dri/nouveau_winsys/nouveau_dma.c54
1 files 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);
}