drm/nv50-: prevent some races between modesetting and page flipping
authorBen Skeggs <bskeggs@redhat.com>
Sat, 2 Mar 2013 03:21:31 +0000 (13:21 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Mon, 4 Mar 2013 01:46:35 +0000 (11:46 +1000)
nexuiz-glx + gnome-shell is able to trigger this a lot of the time.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nv50_display.c

index e26caf63db0cbc1257eea0fa0fb0c6b2f53d3b6d..87a5a56ed358f2dda0098f955d98c6ca0df2104b 100644 (file)
@@ -55,9 +55,9 @@
 
 /* offsets in shared sync bo of various structures */
 #define EVO_SYNC(c, o) ((c) * 0x0100 + (o))
-#define EVO_MAST_NTFY     EVO_SYNC(  0, 0x00)
-#define EVO_FLIP_SEM0(c)  EVO_SYNC((c), 0x00)
-#define EVO_FLIP_SEM1(c)  EVO_SYNC((c), 0x10)
+#define EVO_MAST_NTFY     EVO_SYNC(      0, 0x00)
+#define EVO_FLIP_SEM0(c)  EVO_SYNC((c) + 1, 0x00)
+#define EVO_FLIP_SEM1(c)  EVO_SYNC((c) + 1, 0x10)
 
 #define EVO_CORE_HANDLE      (0xd1500000)
 #define EVO_CHAN_HANDLE(t,i) (0xd15c0000 | (((t) & 0x00ff) << 8) | (i))
@@ -341,10 +341,8 @@ struct nv50_curs {
 
 struct nv50_sync {
        struct nv50_dmac base;
-       struct {
-               u32 offset;
-               u16 value;
-       } sem;
+       u32 addr;
+       u32 data;
 };
 
 struct nv50_ovly {
@@ -471,13 +469,33 @@ nv50_display_crtc_sema(struct drm_device *dev, int crtc)
        return nv50_disp(dev)->sync;
 }
 
+struct nv50_display_flip {
+       struct nv50_disp *disp;
+       struct nv50_sync *chan;
+};
+
+static bool
+nv50_display_flip_wait(void *data)
+{
+       struct nv50_display_flip *flip = data;
+       if (nouveau_bo_rd32(flip->disp->sync, flip->chan->addr / 4) ==
+                                             flip->chan->data);
+               return true;
+       usleep_range(1, 2);
+       return false;
+}
+
 void
 nv50_display_flip_stop(struct drm_crtc *crtc)
 {
-       struct nv50_sync *sync = nv50_sync(crtc);
+       struct nouveau_device *device = nouveau_dev(crtc->dev);
+       struct nv50_display_flip flip = {
+               .disp = nv50_disp(crtc->dev),
+               .chan = nv50_sync(crtc),
+       };
        u32 *push;
 
-       push = evo_wait(sync, 8);
+       push = evo_wait(flip.chan, 8);
        if (push) {
                evo_mthd(push, 0x0084, 1);
                evo_data(push, 0x00000000);
@@ -487,8 +505,10 @@ nv50_display_flip_stop(struct drm_crtc *crtc)
                evo_data(push, 0x00000000);
                evo_mthd(push, 0x0080, 1);
                evo_data(push, 0x00000000);
-               evo_kick(push, sync);
+               evo_kick(push, flip.chan);
        }
+
+       nv_wait_cb(device, nv50_display_flip_wait, &flip);
 }
 
 int
@@ -496,11 +516,10 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,
                       struct nouveau_channel *chan, u32 swap_interval)
 {
        struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb);
-       struct nv50_disp *disp = nv50_disp(crtc->dev);
        struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
        struct nv50_sync *sync = nv50_sync(crtc);
+       int head = nv_crtc->index, ret;
        u32 *push;
-       int ret;
 
        swap_interval <<= 4;
        if (swap_interval == 0)
@@ -510,66 +529,64 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,
        if (unlikely(push == NULL))
                return -EBUSY;
 
-       /* synchronise with the rendering channel, if necessary */
-       if (likely(chan)) {
-               if (nv_mclass(chan->object) < NV84_CHANNEL_IND_CLASS) {
-                       ret = RING_SPACE(chan, 8);
-                       if (ret)
-                               return ret;
-
-                       BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2);
-                       OUT_RING  (chan, NvEvoSema0 + nv_crtc->index);
-                       OUT_RING  (chan, sync->sem.offset);
-                       BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_RELEASE, 1);
-                       OUT_RING  (chan, 0xf00d0000 | sync->sem.value);
-                       BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_OFFSET, 2);
-                       OUT_RING  (chan, sync->sem.offset ^ 0x10);
-                       OUT_RING  (chan, 0x74b1e000);
-               } else
-               if (nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) {
-                       u64 offset = nv84_fence_crtc(chan, nv_crtc->index);
-                       offset += sync->sem.offset;
-
-                       ret = RING_SPACE(chan, 12);
-                       if (ret)
-                               return ret;
-
-                       BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1);
-                       OUT_RING  (chan, chan->vram);
-                       BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
-                       OUT_RING  (chan, upper_32_bits(offset));
-                       OUT_RING  (chan, lower_32_bits(offset));
-                       OUT_RING  (chan, 0xf00d0000 | sync->sem.value);
-                       OUT_RING  (chan, 0x00000002);
-                       BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
-                       OUT_RING  (chan, upper_32_bits(offset));
-                       OUT_RING  (chan, lower_32_bits(offset ^ 0x10));
-                       OUT_RING  (chan, 0x74b1e000);
-                       OUT_RING  (chan, 0x00000001);
-               } else {
-                       u64 offset = nv84_fence_crtc(chan, nv_crtc->index);
-                       offset += sync->sem.offset;
-
-                       ret = RING_SPACE(chan, 10);
-                       if (ret)
-                               return ret;
-
-                       BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
-                       OUT_RING  (chan, upper_32_bits(offset));
-                       OUT_RING  (chan, lower_32_bits(offset));
-                       OUT_RING  (chan, 0xf00d0000 | sync->sem.value);
-                       OUT_RING  (chan, 0x00001002);
-                       BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
-                       OUT_RING  (chan, upper_32_bits(offset));
-                       OUT_RING  (chan, lower_32_bits(offset ^ 0x10));
-                       OUT_RING  (chan, 0x74b1e000);
-                       OUT_RING  (chan, 0x00001001);
-               }
+       if (chan && nv_mclass(chan->object) < NV84_CHANNEL_IND_CLASS) {
+               ret = RING_SPACE(chan, 8);
+               if (ret)
+                       return ret;
+
+               BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2);
+               OUT_RING  (chan, NvEvoSema0 + head);
+               OUT_RING  (chan, sync->addr ^ 0x10);
+               BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_RELEASE, 1);
+               OUT_RING  (chan, sync->data + 1);
+               BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_OFFSET, 2);
+               OUT_RING  (chan, sync->addr);
+               OUT_RING  (chan, sync->data);
+       } else
+       if (chan && nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) {
+               u64 addr = nv84_fence_crtc(chan, head) + sync->addr;
+               ret = RING_SPACE(chan, 12);
+               if (ret)
+                       return ret;
+
+               BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1);
+               OUT_RING  (chan, chan->vram);
+               BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
+               OUT_RING  (chan, upper_32_bits(addr ^ 0x10));
+               OUT_RING  (chan, lower_32_bits(addr ^ 0x10));
+               OUT_RING  (chan, sync->data + 1);
+               OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG);
+               BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
+               OUT_RING  (chan, upper_32_bits(addr));
+               OUT_RING  (chan, lower_32_bits(addr));
+               OUT_RING  (chan, sync->data);
+               OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL);
+       } else
+       if (chan) {
+               u64 addr = nv84_fence_crtc(chan, head) + sync->addr;
+               ret = RING_SPACE(chan, 10);
+               if (ret)
+                       return ret;
+
+               BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
+               OUT_RING  (chan, upper_32_bits(addr ^ 0x10));
+               OUT_RING  (chan, lower_32_bits(addr ^ 0x10));
+               OUT_RING  (chan, sync->data + 1);
+               OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG |
+                                NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD);
+               BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4);
+               OUT_RING  (chan, upper_32_bits(addr));
+               OUT_RING  (chan, lower_32_bits(addr));
+               OUT_RING  (chan, sync->data);
+               OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL |
+                                NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD);
+       }
 
+       if (chan) {
+               sync->addr ^= 0x10;
+               sync->data++;
                FIRE_RING (chan);
        } else {
-               nouveau_bo_wr32(disp->sync, sync->sem.offset / 4,
-                               0xf00d0000 | sync->sem.value);
                evo_sync(crtc->dev);
        }
 
@@ -583,9 +600,9 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,
                evo_data(push, 0x40000000);
        }
        evo_mthd(push, 0x0088, 4);
-       evo_data(push, sync->sem.offset);
-       evo_data(push, 0xf00d0000 | sync->sem.value);
-       evo_data(push, 0x74b1e000);
+       evo_data(push, sync->addr);
+       evo_data(push, sync->data++);
+       evo_data(push, sync->data);
        evo_data(push, NvEvoSync);
        evo_mthd(push, 0x00a0, 2);
        evo_data(push, 0x00000000);
@@ -613,9 +630,6 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,
        evo_mthd(push, 0x0080, 1);
        evo_data(push, 0x00000000);
        evo_kick(push, sync);
-
-       sync->sem.offset ^= 0x10;
-       sync->sem.value++;
        return 0;
 }
 
@@ -1387,7 +1401,8 @@ nv50_crtc_create(struct drm_device *dev, struct nouveau_object *core, int index)
        if (ret)
                goto out;
 
-       head->sync.sem.offset = EVO_SYNC(1 + index, 0x00);
+       head->sync.addr = EVO_FLIP_SEM0(index);
+       head->sync.data = 0x00000000;
 
        /* allocate overlay resources */
        ret = nv50_pioc_create(disp->core, NV50_DISP_OIMM_CLASS, index,
@@ -2120,15 +2135,23 @@ nv50_display_fini(struct drm_device *dev)
 int
 nv50_display_init(struct drm_device *dev)
 {
-       u32 *push = evo_wait(nv50_mast(dev), 32);
-       if (push) {
-               evo_mthd(push, 0x0088, 1);
-               evo_data(push, NvEvoSync);
-               evo_kick(push, nv50_mast(dev));
-               return 0;
+       struct nv50_disp *disp = nv50_disp(dev);
+       struct drm_crtc *crtc;
+       u32 *push;
+
+       push = evo_wait(nv50_mast(dev), 32);
+       if (!push)
+               return -EBUSY;
+
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+               struct nv50_sync *sync = nv50_sync(crtc);
+               nouveau_bo_wr32(disp->sync, sync->addr / 4, sync->data);
        }
 
-       return -EBUSY;
+       evo_mthd(push, 0x0088, 1);
+       evo_data(push, NvEvoSync);
+       evo_kick(push, nv50_mast(dev));
+       return 0;
 }
 
 void