drm/nouveau: remove fence wait code from deferred client work handler
authorBen Skeggs <bskeggs@redhat.com>
Tue, 8 May 2018 10:39:47 +0000 (20:39 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Fri, 18 May 2018 05:01:26 +0000 (15:01 +1000)
Fences attached to deferred client work items now originate from channels
belonging to the client, meaning we can be certain they've been signalled
before we destroy a client.

This closes a race that could happen if the dma_fence_wait_timeout() call
didn't succeed.  When the fence was later signalled, a use-after-free was
possible.

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

index 6caece4..64b8fd0 100644 (file)
@@ -113,24 +113,22 @@ nouveau_name(struct drm_device *dev)
 }
 
 static inline bool
-nouveau_cli_work_ready(struct dma_fence *fence, bool wait)
+nouveau_cli_work_ready(struct dma_fence *fence)
 {
-       if (!dma_fence_is_signaled(fence)) {
-               if (!wait)
-                       return false;
-               WARN_ON(dma_fence_wait_timeout(fence, false, 2 * HZ) <= 0);
-       }
+       if (!dma_fence_is_signaled(fence))
+               return false;
        dma_fence_put(fence);
        return true;
 }
 
 static void
-nouveau_cli_work_flush(struct nouveau_cli *cli, bool wait)
+nouveau_cli_work(struct work_struct *w)
 {
+       struct nouveau_cli *cli = container_of(w, typeof(*cli), work);
        struct nouveau_cli_work *work, *wtmp;
        mutex_lock(&cli->lock);
        list_for_each_entry_safe(work, wtmp, &cli->worker, head) {
-               if (!work->fence || nouveau_cli_work_ready(work->fence, wait)) {
+               if (!work->fence || nouveau_cli_work_ready(work->fence)) {
                        list_del(&work->head);
                        work->func(work);
                }
@@ -158,17 +156,17 @@ nouveau_cli_work_queue(struct nouveau_cli *cli, struct dma_fence *fence,
        mutex_unlock(&cli->lock);
 }
 
-static void
-nouveau_cli_work(struct work_struct *w)
-{
-       struct nouveau_cli *cli = container_of(w, typeof(*cli), work);
-       nouveau_cli_work_flush(cli, false);
-}
-
 static void
 nouveau_cli_fini(struct nouveau_cli *cli)
 {
-       nouveau_cli_work_flush(cli, true);
+       /* All our channels are dead now, which means all the fences they
+        * own are signalled, and all callback functions have been called.
+        *
+        * So, after flushing the workqueue, there should be nothing left.
+        */
+       flush_work(&cli->work);
+       WARN_ON(!list_empty(&cli->worker));
+
        usif_client_fini(cli);
        nouveau_vmm_fini(&cli->vmm);
        nvif_mmu_fini(&cli->mmu);