pvr_remove() destroys the job and free list xarrays, powers the GPU
down and runs pvr_device_fini() before calling drm_dev_unplug(), so an
ioctl that entered its drm_dev_enter() section before the unbind can
still be walking structures that have already been freed.

Call drm_dev_unplug() first. It makes every later drm_dev_enter() fail
and waits for the sections already in flight. The runtime suspend that
follows works on an unplugged device now that the callbacks no longer
gate on drm_dev_enter().

Skip the unplug if pvr_device_lost() has already done it: drm_dev_unplug()
is not idempotent, and the second call oopses in
drm_client_sysrq_unregister() on a node the first one removed. Unbinding
a GPU that a failed reset marked lost hits that deterministically.

Holding an ioctl in its section for 4s on mt8173 while unbinding shows
the change: before, pvr_device_fini() completed 3.1s before the ioctl
left its section; after, drm_dev_unplug() blocks for those 3.1s and the
teardown follows.

Fixes: 1f88f017e649 ("drm/imagination: Get GPU resources")
Fixes: 727538a4bbff ("drm/imagination: Implement power management")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Ryan Brue <[email protected]>
---
 drivers/gpu/drm/imagination/pvr_drv.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/imagination/pvr_drv.c 
b/drivers/gpu/drm/imagination/pvr_drv.c
index 5c965ef0274f..fc92a82a7208 100644
--- a/drivers/gpu/drm/imagination/pvr_drv.c
+++ b/drivers/gpu/drm/imagination/pvr_drv.c
@@ -1469,15 +1469,23 @@ static void pvr_remove(struct platform_device *plat_dev)
        struct drm_device *drm_dev = platform_get_drvdata(plat_dev);
        struct pvr_device *pvr_dev = to_pvr_device(drm_dev);
 
+       /*
+        * Unplug before freeing anything, so no ioctl is still inside
+        * drm_dev_enter(). pvr_device_lost() may already have done it, and
+        * drm_dev_unplug() is not idempotent.
+        */
+       if (!pvr_dev->lost)
+               drm_dev_unplug(drm_dev);
+
        WARN_ON(!xa_empty(&pvr_dev->job_ids));
        WARN_ON(!xa_empty(&pvr_dev->free_list_ids));
 
+       pm_runtime_suspend(drm_dev->dev);
+
        xa_destroy(&pvr_dev->job_ids);
        xa_destroy(&pvr_dev->free_list_ids);
 
-       pm_runtime_suspend(drm_dev->dev);
        pvr_device_fini(pvr_dev);
-       drm_dev_unplug(drm_dev);
        pvr_watchdog_fini(pvr_dev);
        pvr_queue_device_fini(pvr_dev);
        pvr_context_device_fini(pvr_dev);

-- 
2.55.0

Reply via email to