nouveau: fix elusive dma bug
authorBen Skeggs <skeggsb@gmail.com>
Mon, 10 Dec 2007 04:16:05 +0000 (15:16 +1100)
committerBen Skeggs <skeggsb@gmail.com>
Mon, 10 Dec 2007 04:17:20 +0000 (15:17 +1100)
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

index 6ec390ec769eeecc8be2e2f8812f8fdeb8c431dc..5739e0010d48f6701bb37bd72c989b94b82410b3 100644 (file)
 #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);
 }