The offending edge was code that did a blocking
down_read(&adev->reset_domain->sem) as following, while
holding userq_mutex. Since GPU recovery takes reset_domain
->sem for write and then transitively acquires userq_mutex,
the reverse ordering could deadlock.

.569196]
               other info that might help us debug this:

[  307.569516] Chain exists of:
                 &adev->firmware.mutex --> &userq_mgr->userq_mutex --> 
&reset_domain->sem

[  307.570011]  Possible unsafe locking scenario:

[  307.570250]        CPU0                    CPU1
[  307.570438]        ----                    ----
[  307.570624]   lock(&reset_domain->sem);
[  307.570785]                                lock(&userq_mgr->userq_mutex);
[  307.571061]                                lock(&reset_domain->sem);
[  307.571320]   lock(&adev->firmware.mutex);
[  307.571491]
                *** DEADLOCK ***

Signed-off-by: Prike Liang <[email protected]>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 51 ++++++++++++++++++++---
 1 file changed, 45 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c
index 2534e4a1a530..a7d5ca741a3b 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c
@@ -422,9 +422,12 @@ static void amdgpu_userq_detach_doorbell(struct 
amdgpu_usermode_queue *queue)
 {
        struct amdgpu_device *adev = queue->userq_mgr->adev;
 
-       down_read(&adev->reset_domain->sem);
+       /*
+        * The caller serializes doorbell removal against an in-progress GPU
+        * reset by holding adev->reset_domain->sem for read.
+        */
+       lockdep_assert_held_read(&adev->reset_domain->sem);
        xa_erase_irq(&adev->userq_doorbell_xa, queue->doorbell_index);
-       up_read(&adev->reset_domain->sem);
 }
 
 /**
@@ -544,11 +547,34 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, 
struct amdgpu_usermode_que
 
        cancel_delayed_work_sync(&uq_mgr->resume_work);
 
+       /*
+        * Cancel hang detection before serializing against a GPU reset. Hang
+        * detection triggers recovery, which takes reset_domain->sem for write,
+        * so it must not be canceled while that semaphore is held for read.
+        * A reset IRQ can restart hang detection, so this is repeated on retry.
+        */
+       cancel_delayed_work_sync(&queue->hang_detect_work);
+retry:
        mutex_lock(&uq_mgr->userq_mutex);
        amdgpu_userq_wait_for_last_fence(queue);
 
+       /*
+        * Serialize queue teardown (doorbell detach and MES unmap) against an
+        * in-progress GPU reset. Do not block on the reset semaphore while
+        * holding userq_mutex: recovery takes the semaphore for write and then
+        * (transitively) userq_mutex, so blocking here would invert that order
+        * and deadlock. If the trylock fails, drop userq_mutex, wait for
+        * recovery to finish, and retry.
+        */
+       if (!down_read_trylock(&adev->reset_domain->sem)) {
+               mutex_unlock(&uq_mgr->userq_mutex);
+
+               down_read(&adev->reset_domain->sem);
+               up_read(&adev->reset_domain->sem);
+               goto retry;
+       }
+
        amdgpu_userq_detach_doorbell(queue);
-       cancel_delayed_work_sync(&queue->hang_detect_work);
 
 #if defined(CONFIG_DEBUG_FS)
        debugfs_remove_recursive(queue->debugfs_queue);
@@ -557,6 +583,7 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, 
struct amdgpu_usermode_que
        atomic_dec(&uq_mgr->userq_count[queue->queue_type]);
        amdgpu_userq_fence_driver_free(queue);
        queue->fence_drv = NULL;
+       up_read(&adev->reset_domain->sem);
        mutex_unlock(&uq_mgr->userq_mutex);
 
        /*
@@ -734,16 +761,28 @@ amdgpu_userq_create(struct drm_file *filp, union 
drm_amdgpu_userq *args)
        if (r)
                goto clean_mqd;
 
+map_retry:
        amdgpu_userq_ensure_ev_fence(&fpriv->userq_mgr, &fpriv->evf_mgr);
 
        /* don't map the queue if scheduling is halted */
        if (!adev->userq_halt_for_enforce_isolation ||
            ((queue->queue_type != AMDGPU_HW_IP_GFX) &&
             (queue->queue_type != AMDGPU_HW_IP_COMPUTE))) {
-               /* Serialize the map against an in-progress GPU reset (MES is
-                * unresponsive during recovery), matching 
amdgpu_userq_detach_doorbell().
+               /*
+                * Serialize the map against an in-progress GPU reset (MES is
+                * unresponsive during recovery). Do not block on the reset
+                * semaphore while holding userq_mutex: recovery takes the
+                * semaphore for write and then (transitively) userq_mutex, so
+                * blocking here would invert that order and deadlock. If the
+                * trylock fails, drop userq_mutex, wait for recovery, and 
retry.
                 */
-               down_read(&adev->reset_domain->sem);
+               if (!down_read_trylock(&adev->reset_domain->sem)) {
+                       mutex_unlock(&uq_mgr->userq_mutex);
+
+                       down_read(&adev->reset_domain->sem);
+                       up_read(&adev->reset_domain->sem);
+                       goto map_retry;
+               }
                r = amdgpu_userq_map_helper(queue);
                up_read(&adev->reset_domain->sem);
                if (r) {
-- 
2.34.1

Reply via email to