| // 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); |
| |
| /* |
| * Register fs_reclaim lock class FIRST, before taking any locks. |
| * |
| * This acquire/release pair does NOT create a static lockdep edge |
| * (no locks are held between acquire and release). It only registers |
| * the fs_reclaim lock class with lockdep. |
| * |
| * The actual fs_reclaim -> notifier_lock dependency is established at |
| * RUNTIME when memory reclaim invokes MMU notifiers: |
| * fs_reclaim (held by reclaim) -> notifier_lock (acquired in callback) |
| */ |
| fs_reclaim_acquire(GFP_KERNEL); |
| fs_reclaim_release(GFP_KERNEL); |
| |
| /* |
| * 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); |
| |
| /* 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); |
| |
| 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 */ |