mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
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:
parent
b38f9b6ffd
commit
25f0b322ff
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user