drm/amdkfd: Evict SVM BOs synchronously from TTM eviction

svm_range_evict_svm_bo_worker() migrated an SVM BO's pages back to system
memory from a work item that took mmap_read_lock. When an mmap writer was
pending, that read lock blocked behind the writer while the thread
allocating a new migration VRAM BO waited on this BO's eviction fence - a
circular wait that hung the SVM workers.

Evict the SVM BO synchronously from the TTM eviction path
(amdgpu_ttm_bo_eviction_valuable) instead of deferring to a work item.
The BO is already reserved and the lock order is mmap_lock -> BO
reservation, so only trylock the owning process's mmap lock; on
contention return -EBUSY so TTM skips this BO. This removes the eviction
work item and the enable_signaling path, so no worker can block on
mmap_read_lock.

The SVM BO uses AMDGPU_GEM_CREATE_DISCARDABLE, so ttm_bo_evict takes the
pipeline_gutting path and skips allocating a system memory placement.
That would be wasted work, since svm_migrate_vram_to_ram allocates the
system pages and copies the data back itself.

Eviction now migrates ranges directly, so it must serialize with the
owning process: it trylocks migrate_mutex under svm_bo->list_lock before
unlinking the range, and svm_range_free() unlinks the range then waits on
migrate_mutex, so a concurrent eviction cannot free a range under it.

Drop the mm reference with mmput_async so exit_mmap() does not run under
the BO reservation.

Signed-off-by: Philip Yang <Philip.Yang@amd.com>
Reviewed-by: Felix Kuehling <felix.kuehling@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
Philip Yang 2026-07-16 17:39:34 -04:00 committed by Alex Deucher
parent b38f9b6ffd
commit 25f0b322ff
6 changed files with 100 additions and 40 deletions

View File

@ -39,6 +39,7 @@
#if IS_ENABLED(CONFIG_HSA_AMD)
#include "kfd_priv.h"
#endif
#include "kfd_svm.h"
/* Total memory size in system memory and all GPU VRAM. Used to
* estimate worst case amount of memory to reserve for page tables
@ -972,3 +973,24 @@ int amdgpu_amdkfd_reset_mes_queue(struct amdgpu_device *adev,
return kgd2kfd_reset_mes_queue(adev->kfd.dev, node_id, queue_type,
pipe, queue, db);
}
int amdgpu_amdkfd_evict_svm_bo(struct amdgpu_bo *bo)
{
struct dma_resv_iter cursor;
struct dma_fence *fence;
int r = 0;
dma_resv_iter_begin(&cursor, bo->tbo.base.resv, DMA_RESV_USAGE_BOOKKEEP);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
struct amdgpu_amdkfd_fence *f = to_amdgpu_amdkfd_fence(fence);
if (f && f->svm_bo) {
r = svm_range_evict_svm_bo(f->svm_bo);
if (r)
break;
}
}
dma_resv_iter_end(&cursor);
return r;
}

View File

@ -286,6 +286,7 @@ int amdgpu_amdkfd_reset_mes_queue(struct amdgpu_device *adev,
int queue_type,
int pipe, int queue,
unsigned int db);
int amdgpu_amdkfd_evict_svm_bo(struct amdgpu_bo *bo);
/* Read user wptr from a specified user address space with page fault
* disabled. The memory must be pinned and mapped to the hardware when

View File

@ -135,9 +135,6 @@ static bool amdkfd_fence_enable_signaling(struct dma_fence *f)
if (!fence->svm_bo) {
if (!kgd2kfd_schedule_evict_and_restore_process(fence->mm, fence->context_id, f))
return true;
} else {
if (!svm_range_schedule_evict_svm_bo(fence))
return true;
}
return false;
}

View File

@ -1488,6 +1488,7 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo,
const struct ttm_place *place)
{
struct dma_resv_iter resv_cursor;
struct amdgpu_bo *abo;
struct dma_fence *f;
if (!amdgpu_bo_is_amdgpu_bo(bo))
@ -1497,6 +1498,21 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo,
if (bo->resource->mem_type == TTM_PL_SYSTEM)
return true;
abo = ttm_to_amdgpu_bo(bo);
if (abo->flags & AMDGPU_GEM_CREATE_DISCARDABLE) {
/*
* SVM BOs are migrated to system memory synchronously in this
* TTM eviction context. The migration needs the owning
* process's mmap lock, but the normal lock order is
* mmap_lock -> BO reservation and the BO is already reserved
* here. svm_range_evict_svm_bo() only trylocks the mmap lock;
* if the eviction fails for any reason, we return false so TTM
* skips this BO instead of risking a deadlock.
*/
if (amdgpu_amdkfd_evict_svm_bo(abo) < 0)
return false;
}
if (bo->type == ttm_bo_type_kernel &&
!amdgpu_vm_evictable(ttm_to_amdgpu_bo(bo)))
return false;

View File

@ -69,7 +69,6 @@ struct criu_svm_metadata {
struct kfd_criu_svm_range_priv_data data;
};
static void svm_range_evict_svm_bo_worker(struct work_struct *work);
static bool
svm_range_cpu_invalidate_pagetables(struct mmu_interval_notifier *mni,
const struct mmu_notifier_range *range,
@ -97,7 +96,7 @@ static void svm_range_unlink(struct svm_range *prange)
if (prange->svm_bo) {
spin_lock(&prange->svm_bo->list_lock);
list_del(&prange->svm_bo_list);
list_del_init(&prange->svm_bo_list);
spin_unlock(&prange->svm_bo->list_lock);
}
@ -286,6 +285,17 @@ static void svm_range_free(struct svm_range *prange, bool do_unmap)
pr_debug("svms 0x%p prange 0x%p [0x%lx 0x%lx]\n", prange->svms, prange,
prange->start, prange->last);
/* Unlink from range_list; no-op if already unlinked. */
if (prange->svm_bo) {
spin_lock(&prange->svm_bo->list_lock);
list_del_init(&prange->svm_bo_list);
spin_unlock(&prange->svm_bo->list_lock);
}
/* Wait for any in-flight eviction of this range to finish. */
mutex_lock(&prange->migrate_mutex);
mutex_unlock(&prange->migrate_mutex);
svm_range_vram_node_free(prange);
if (do_unmap)
svm_range_dma_unmap(prange);
@ -588,7 +598,6 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange,
mm,
svm_bo, p->context_id);
mmput(mm);
INIT_WORK(&svm_bo->eviction_work, svm_range_evict_svm_bo_worker);
svm_bo->evicting = 0;
memset(&bp, 0, sizeof(bp));
bp.size = prange->npages * PAGE_SIZE;
@ -3631,39 +3640,36 @@ svm_range_trigger_migration(struct mm_struct *mm, struct svm_range *prange,
return 0;
}
int svm_range_schedule_evict_svm_bo(struct amdgpu_amdkfd_fence *fence)
int svm_range_evict_svm_bo(struct svm_range_bo *svm_bo)
{
/* Dereferencing fence->svm_bo is safe here because the fence hasn't
* signaled yet and we're under the protection of the fence->lock.
* After the fence is signaled in svm_range_bo_release, we cannot get
* here any more.
*
* Reference is dropped in svm_range_evict_svm_bo_worker.
*/
if (svm_bo_ref_unless_zero(fence->svm_bo)) {
WRITE_ONCE(fence->svm_bo->evicting, 1);
schedule_work(&fence->svm_bo->eviction_work);
}
return 0;
}
static void svm_range_evict_svm_bo_worker(struct work_struct *work)
{
struct svm_range_bo *svm_bo;
struct mm_struct *mm;
int r = 0;
svm_bo = container_of(work, struct svm_range_bo, eviction_work);
if (!svm_bo_ref_unless_zero(svm_bo))
return 0;
if (mmget_not_zero(svm_bo->eviction_fence->mm)) {
mm = svm_bo->eviction_fence->mm;
} else {
if (!mmget_not_zero(svm_bo->eviction_fence->mm)) {
svm_range_bo_unref(svm_bo);
return;
return 0;
}
mm = svm_bo->eviction_fence->mm;
/*
* Called with the BO reserved; lock order is mmap_lock -> BO
* reservation. Only trylock mmap to invert that order safely: a
* trylock never blocks, so it cannot deadlock against the reservation
* and lockdep records no reverse dependency. On contention return
* -EBUSY so TTM skips this BO.
*/
if (!mmap_read_trylock(mm)) {
pr_debug("skip eviction, contended to take mmap_read lock\n");
mmput_async(mm);
svm_range_bo_unref(svm_bo);
return -EBUSY;
}
mmap_read_lock(mm);
WRITE_ONCE(svm_bo->evicting, 1);
spin_lock(&svm_bo->list_lock);
while (!list_empty(&svm_bo->range_list) && !r) {
struct svm_range *prange =
@ -3671,13 +3677,24 @@ static void svm_range_evict_svm_bo_worker(struct work_struct *work)
struct svm_range, svm_bo_list);
int retries = 3;
/*
* Trylock migrate_mutex under list_lock, before unlinking the
* range, so svm_range_free() cannot free it under us. On
* contention the owner is migrating this range; skip the BO.
*/
if (!mutex_trylock(&prange->migrate_mutex)) {
pr_debug("skip eviction, contended migrate_mutex\n");
/* Clear evicting so the BO keeps being reused. */
WRITE_ONCE(svm_bo->evicting, 0);
r = -EBUSY;
break;
}
list_del_init(&prange->svm_bo_list);
spin_unlock(&svm_bo->list_lock);
pr_debug("svms 0x%p [0x%lx 0x%lx]\n", prange->svms,
prange->start, prange->last);
mutex_lock(&prange->migrate_mutex);
do {
/* migrate all vram pages in this prange to sys ram
* after that prange->actual_loc should be zero
@ -3701,15 +3718,24 @@ static void svm_range_evict_svm_bo_worker(struct work_struct *work)
}
spin_unlock(&svm_bo->list_lock);
mmap_read_unlock(mm);
mmput(mm);
/* Defer mmput: exit_mmap() must not run under the BO reservation. */
mmput_async(mm);
dma_fence_signal(&svm_bo->eviction_fence->base);
/*
* Only signal the eviction fence once the ranges have been processed.
* On -EBUSY we bailed out without migrating; leave the BO in VRAM and
* let TTM retry later.
*/
if (r != -EBUSY)
dma_fence_signal(&svm_bo->eviction_fence->base);
/* This is the last reference to svm_bo, after svm_range_vram_node_free
* has been called in svm_migrate_vram_to_ram
*/
WARN_ONCE(!r && kref_read(&svm_bo->kref) != 1, "This was not the last reference\n");
svm_range_bo_unref(svm_bo);
return r;
}
static int

View File

@ -44,7 +44,6 @@ struct svm_range_bo {
struct list_head range_list; /* all svm ranges shared this bo */
spinlock_t list_lock;
struct amdgpu_amdkfd_fence *eviction_fence;
struct work_struct eviction_work;
uint32_t evicting;
struct work_struct release_work;
struct kfd_node *node;
@ -175,7 +174,8 @@ void svm_range_vram_node_free(struct svm_range *prange);
int svm_range_restore_pages(struct amdgpu_device *adev, unsigned int pasid,
uint32_t vmid, uint32_t node_id, uint64_t addr, uint64_t ts,
bool write_fault);
int svm_range_schedule_evict_svm_bo(struct amdgpu_amdkfd_fence *fence);
int svm_range_evict_svm_bo(struct svm_range_bo *svm_bo);
void svm_range_add_list_work(struct svm_range_list *svms,
struct svm_range *prange, struct mm_struct *mm,
enum svm_work_list_ops op);
@ -229,11 +229,9 @@ static inline int svm_range_restore_pages(struct amdgpu_device *adev,
return -EFAULT;
}
static inline int svm_range_schedule_evict_svm_bo(
struct amdgpu_amdkfd_fence *fence)
static inline int svm_range_evict_svm_bo(struct svm_range_bo *svm_bo)
{
WARN_ONCE(1, "SVM eviction fence triggered, but SVM is disabled");
return -EINVAL;
return 0;
}
static inline void svm_range_get_info(struct kfd_process *p,