On 2026-08-06 10:31, William Palacek wrote:
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_SELECT_SYSTEM has nothing to collect and
migrate_vma_setup() returns cpages == 0. By then it has already issued
MMU_NOTIFY_MIGRATE, which svm_range_cpu_invalidate_pagetables() turns
into svm_range_unmap_from_gpus() for that window, so the mapping the
fault needed is torn down to service a migration that moves nothing and
the fault is re-armed rather than resolved. Under XNACK on an
oversubscribed range this becomes a loop: svm_range_restore_pages() and
svm_migrate_to_vram() are entered thousands of times per second,
svm_migrate_copy_to_vram() is never reached, and the per process
migration counters stop advancing while the GPU stays busy.

Why doesn't it get to svm_range_validate_and_map and resolve the fault?

Regards,
  Felix



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..b946ae95ff12 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 retry fault storm cannot re-arm 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 re-arms this fault instead of
+                        * resolving it.
+                        */
+                       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;

Reply via email to