root/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c
// SPDX-License-Identifier: MIT
/*
 * Copyright 2024 Advanced Micro Devices, Inc.
 *
 * Lockdep annotation for AMDGPU lock ordering
 *
 * This module teaches lockdep the correct lock ordering to catch
 * potential deadlocks at development time rather than runtime.
 *
 * Based on dma-resv lockdep approach from:
 * drivers/dma-buf/dma-resv.c:dma_resv_lockdep()
 */

#include "amdgpu.h"
#include "amdgpu_reset.h"

#ifdef CONFIG_LOCKDEP

struct amdgpu_lockdep_dummy_locks {
        struct mutex reset_lock;
        struct mutex userq_sch_mutex;
        struct mutex userq_mutex;
        struct mutex notifier_lock;
        struct mutex vram_lock;
        struct mutex srbm_mutex;
        struct mutex grbm_idx_mutex;
        spinlock_t mmio_idx_lock;
};

/* Lock class keys for associating with real driver locks */
static struct lock_class_key amdgpu_userq_sch_mutex_key;
static struct lock_class_key amdgpu_userq_mutex_key;
static struct lock_class_key amdgpu_notifier_lock_key;
static struct lock_class_key amdgpu_vram_lock_key;
static struct lock_class_key amdgpu_reset_sem_key;
static struct lock_class_key amdgpu_reset_lock_key;
static struct lock_class_key amdgpu_srbm_lock_key;
static struct lock_class_key amdgpu_grbm_lock_key;
static struct lock_class_key amdgpu_mmio_lock_key;

/**
 * amdgpu_lockdep_set_class - Associate lock class keys with real locks
 * @adev: AMDGPU device
 *
 * Call during device init to associate lock classes with actual locks
 * so lockdep can track them properly.
 */
void amdgpu_lockdep_set_class(struct amdgpu_device *adev)
{
        lockdep_set_class(&adev->gfx.userq_sch_mutex,
                          &amdgpu_userq_sch_mutex_key);
        lockdep_set_class(&adev->notifier_lock, &amdgpu_notifier_lock_key);
        lockdep_set_class(&adev->srbm_mutex, &amdgpu_srbm_lock_key);
        lockdep_set_class(&adev->grbm_idx_mutex, &amdgpu_grbm_lock_key);
        lockdep_set_class(&adev->mmio_idx_lock, &amdgpu_mmio_lock_key);

        if (adev->reset_domain)
                lockdep_set_class(&adev->reset_domain->sem,
                                  &amdgpu_reset_sem_key);
}

/**
 * amdgpu_lockdep_init - Teach lockdep the correct lock ordering
 *
 * Instantiates dummy objects and takes locks in the correct order to
 * train lockdep. This helps catch lock ordering violations during
 * development.
 *
 * Lock ordering hierarchy (outermost to innermost):
 *
 * 1. userq_sch_mutex     - Global userq scheduler (enforce_isolation)
 * 2. userq_mutex         - Per-context userq (held across queue create/destroy)
 * 3. notifier_lock       - MMU notifier lock
 * 4. vram_lock           - VRAM allocator lock
 * 5. reset_domain->sem   - GPU reset synchronization
 * 6. reset_lock          - Reset control lock
 * 7. srbm_mutex          - SRBM register access
 * 8. grbm_idx_mutex      - GRBM index access
 * 9. mmio_idx_lock       - MMIO index access (spinlock)
 *
 * Evidence:
 * - userq_sch_mutex -> userq_mutex: amdgpu_gfx_kfd_sch_ctrl() calls
 *   amdgpu_userq_stop_sched_for_enforce_isolation() which takes userq_mutex
 * - userq_mutex -> notifier_lock: userq paths may trigger MMU notifier
 *   invalidation which acquires notifier_lock
 * - notifier_lock -> reset_domain->sem: HMM invalidation callback holds
 *   notifier_lock and can wait for GPU reset completion, so notifier_lock
 *   must be outer to reset_domain->sem
 * - vram_lock -> reset_domain->sem: VRAM management paths may need to
 *   wait for ongoing reset to complete
 *
 * Note: mmap_lock ordering relative to GPU locks is already taught
 * by dma-resv (drivers/dma-buf/dma-resv.c).
 */
int amdgpu_lockdep_init(void)
{
        struct amdgpu_reset_domain *reset_domain = NULL;
        struct amdgpu_lockdep_dummy_locks *locks;
        unsigned long flags;

        locks = kzalloc(sizeof(*locks), GFP_KERNEL);
        if (!locks)
                return -ENOMEM;

        /*
         * Initialize dummy reset domain
         */
        reset_domain = amdgpu_reset_create_reset_domain(SINGLE_DEVICE,
                                                        "lockdep_test");
        if (!reset_domain) {
                kfree(locks);
                return -ENOMEM;
        }
        /* Initialize dummy locks */
        mutex_init(&locks->userq_sch_mutex);
        mutex_init(&locks->userq_mutex);
        mutex_init(&locks->notifier_lock);
        mutex_init(&locks->vram_lock);
        mutex_init(&locks->reset_lock);
        mutex_init(&locks->srbm_mutex);
        mutex_init(&locks->grbm_idx_mutex);
        spin_lock_init(&locks->mmio_idx_lock);

        /*
         * Associate dummy locks with the same class keys used for real
         * driver locks. This ensures lockdep connects the ordering learned
         * here with the actual locks used at runtime.
         */
        lockdep_set_class(&locks->userq_sch_mutex, &amdgpu_userq_sch_mutex_key);
        lockdep_set_class(&locks->userq_mutex, &amdgpu_userq_mutex_key);
        lockdep_set_class(&locks->notifier_lock, &amdgpu_notifier_lock_key);
        lockdep_set_class(&locks->vram_lock, &amdgpu_vram_lock_key);
        lockdep_set_class(&reset_domain->sem, &amdgpu_reset_sem_key);
        lockdep_set_class(&locks->reset_lock, &amdgpu_reset_lock_key);
        lockdep_set_class(&locks->srbm_mutex, &amdgpu_srbm_lock_key);
        lockdep_set_class(&locks->grbm_idx_mutex, &amdgpu_grbm_lock_key);
        lockdep_set_class(&locks->mmio_idx_lock, &amdgpu_mmio_lock_key);
        /*
         * Take locks in the correct order to train lockdep.
         * This establishes the dependency chain.
         */

        /* Level 1: Global userq scheduler mutex (outermost) */
        mutex_lock(&locks->userq_sch_mutex);

        /* Level 2: Per-context userq mutex */
        mutex_lock(&locks->userq_mutex);
        /* Level 3: MMU notifier lock */
        mutex_lock(&locks->notifier_lock);
        /* Level 4: VRAM allocator lock */
        mutex_lock(&locks->vram_lock);
        /* Level 5: Reset domain semaphore */
        down_read(&reset_domain->sem);

        /* Level 6: Reset control lock */
        mutex_lock(&locks->reset_lock);
        /*
         * Mark potential memory reclaim boundary.
         * GPU operations might trigger memory allocation/reclaim.
         */
        fs_reclaim_acquire(GFP_KERNEL);

        /* Level 7: SRBM register access */
        mutex_lock(&locks->srbm_mutex);
        /* Level 8: GRBM index access */
        mutex_lock(&locks->grbm_idx_mutex);

        /* Level 9: MMIO index access (innermost lock, spinlock) */
        spin_lock_irqsave(&locks->mmio_idx_lock, flags);
        /*
         * All locks acquired in order.
         * Lockdep has now learned the valid dependency chain.
         */

        /* Release in reverse order */
        spin_unlock_irqrestore(&locks->mmio_idx_lock, flags);
        mutex_unlock(&locks->grbm_idx_mutex);
        mutex_unlock(&locks->srbm_mutex);
        fs_reclaim_release(GFP_KERNEL);

        mutex_unlock(&locks->reset_lock);
        up_read(&reset_domain->sem);

        mutex_unlock(&locks->vram_lock);
        mutex_unlock(&locks->notifier_lock);
        mutex_unlock(&locks->userq_mutex);
        mutex_unlock(&locks->userq_sch_mutex);

        /* Cleanup */
        amdgpu_reset_put_reset_domain(reset_domain);

        kfree(locks);
        pr_info("AMDGPU: Lockdep annotations initialized (9 lock levels)\n");

        return 0;
}

#endif /* CONFIG_LOCKDEP */