svm_range_restore_pages() migrates the faulting granule to best_loc
whenever prange->actual_loc or best_loc is set, without checking whether
that window already lives there.
When it does, migrate_vma_collect() issues MMU_NOTIFY_MIGRATE before it
knows what it can collect, and svm_range_cpu_invalidate_pagetables()
turns that into svm_range_unmap_from_gpus() for the window.
MIGRATE_VMA_SELECT_SYSTEM then finds nothing to collect,
migrate_vma_setup() returns cpages == 0, and svm_range_validate_and_map()
rebuilds the mapping that was just torn down. The fault is serviced, but
the pass destroys and re-creates an unchanged mapping and moves no data.
Under XNACK this dominates: in one capture only 2009 of 98728 migrations
entered from the fault path reached svm_migrate_copy_to_vram(), while the
per process migration counters stopped advancing and the GPU stayed busy.
The notification cannot be filtered by pgmap owner, as it is what
invalidates the PTEs for pages that really do move. Avoid starting a
migration that cannot move anything instead. Per window residency is not
cheaply available beforehand, so record the window of a migration that
collected nothing and do not repeat it there until a short backoff
expires.
Fixes: a546a2768440 ("drm/amdkfd: Use partial migrations/mapping for GPU/CPU
page faults in SVM")
Signed-off-by: William Palacek <[email protected]>
---
drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 23 +++++++++++++++++++++++
drivers/gpu/drm/amd/amdkfd/kfd_svm.h | 4 ++++
2 files changed, 27 insertions(+)
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
index fa4054d51f60..888e8934212b 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
@@ -50,6 +50,11 @@
* page table is updated.
*/
#define AMDGPU_SVM_RANGE_RETRY_FAULT_PENDING (2UL * NSEC_PER_MSEC)
+
+/* Long enough that a burst of retry faults cannot repeat a migration
+ * which has already been found to have nothing to collect.
+ */
+#define AMDGPU_SVM_RANGE_NOOP_MIGRATE_BACKOFF (100UL * NSEC_PER_MSEC)
#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG)
#define dynamic_svm_range_dump(svms) \
_dynamic_func_call_no_desc("svm_range_dump", svm_range_debug_dump, svms)
@@ -3221,8 +3226,25 @@ svm_range_restore_pages(struct amdgpu_device *adev,
unsigned int pasid,
last = min_t(unsigned long, ALIGN(addr + 1, size) - 1, prange->last);
if (prange->actual_loc != 0 || best_loc != 0) {
if (best_loc) {
+ uint64_t vram_pages = prange->vram_pages;
+
+ /* migrate_vma_setup() unmaps the window before it knows
+ * whether it can collect anything, so a migration that
+ * collects nothing still tears down and rebuilds an
+ * unchanged mapping.
+ */
+ if (start == prange->noop_migrate_start &&
+ ktime_before(timestamp,
+
ktime_add_ns(prange->noop_migrate_timestamp,
+
AMDGPU_SVM_RANGE_NOOP_MIGRATE_BACKOFF)))
+ goto skip_migrate;
+
r = svm_migrate_to_vram(prange, best_loc, start, last,
mm, KFD_MIGRATE_TRIGGER_PAGEFAULT_GPU);
+ if (!r && prange->vram_pages == vram_pages) {
+ prange->noop_migrate_start = start;
+ prange->noop_migrate_timestamp =
ktime_get_boottime();
+ }
if (r) {
pr_debug("svm_migrate_to_vram failed (%d) at
%llx, falling back to system memory\n",
r, addr);
@@ -3248,6 +3270,7 @@ svm_range_restore_pages(struct amdgpu_device *adev,
unsigned int pasid,
}
}
+skip_migrate:
r = svm_range_validate_and_map(mm, start, last, prange, gpuidx, false,
false, false);
if (r)
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
index c7d7adae4476..508a086195ac 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
@@ -93,6 +93,8 @@ struct svm_work_list_item {
* @granularity:migration granularity, log2 num pages
* @invalid: not 0 means cpu page table is invalidated
* @validate_timestamp: system timestamp when range is validated
+ * @noop_migrate_start: start of the last migration that collected no pages
+ * @noop_migrate_timestamp: system timestamp of that migration
* @notifier: register mmu interval notifier
* @work_item: deferred work item information
* @deferred_list: list header used to add range to deferred list
@@ -131,6 +133,8 @@ struct svm_range {
uint8_t granularity;
atomic_t invalid;
ktime_t validate_timestamp;
+ unsigned long noop_migrate_start;
+ ktime_t noop_migrate_timestamp;
struct mmu_interval_notifier notifier;
struct svm_work_list_item work_item;
struct list_head deferred_list;
--
2.34.1