From: Qiang Han <[email protected]>

With XNACK off, svm_range_evict() leaves the GPU page tables in place
and relies on the process's queues being stopped before the MMU
notifier returns.

Only the first eviction (evicted_ranges == 1) calls
kgd2kfd_quiesce_mm(). The quiesce preempts every GPU the process has
opened and takes milliseconds. Any other range invalidated in the
meantime sees evicted_ranges > 1 and returns at once. The core mm then
frees, migrates or reuses the page while the queues still run, and an
in-flight SDMA or blit copy accesses it through the old GPU PTE. The
result is silent corruption of the copy, or of whatever process now
owns the page.

Hold a new svms->evict_lock across the first evictor's quiesce, so that
concurrent evictions return only after the queues have stopped. This is
the scheme amdgpu_amdkfd_evict_userptr() uses with
process_info->notifier_lock. evict_lock nests inside prange->lock and
is taken nowhere else. Under it, the quiesce path takes the dqm lock,
which is already used in MMU notifiers (memalloc_noreclaim).

One way to reach this: a pageable hipMemcpy() larger than 1 MiB. ROCclr
pins the buffer with hsa_amd_memory_lock(), libhsakmt registers it as
a KFD SVM range, and host memory pressure (reclaim, compaction,
allocator purge) invalidates the range.

Fixes: 8a7c184a16d7 ("drm/amdkfd: svm range eviction and restore")
Signed-off-by: Qing Hang <[email protected]>
Signed-off-by: Philip Yang <[email protected]>
---
 drivers/gpu/drm/amd/amdkfd/kfd_priv.h |  2 ++
 drivers/gpu/drm/amd/amdkfd/kfd_svm.c  | 35 ++++++++++++++++++---------
 2 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h 
b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
index 158fa70a1a8e..581fb7ceba40 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
@@ -909,6 +909,8 @@ struct svm_range_list {
        struct list_head                deferred_range_list;
        struct list_head                criu_svm_metadata_list;
        spinlock_t                      deferred_list_lock;
+       /* serializes XNACK-off evictions with the queue quiesce */
+       struct mutex                    evict_lock;
        atomic_t                        evicted_ranges;
        atomic_t                        drain_pagefaults;
        struct delayed_work             restore_work;
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c 
b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
index 3688ae7ba35e..8b84333a85b7 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
@@ -2141,21 +2141,30 @@ svm_range_evict(struct svm_range *prange, struct 
mm_struct *mm,
                if (prange->start <= last && prange->last >= start)
                        atomic_inc(&prange->invalid);
 
+               /* The MMU notifier must not return before the GPU has stopped
+                * accessing the invalidated pages. Only the first eviction 
stops
+                * the queues, which takes milliseconds (HWS preemption on every
+                * GPU of the process). Serialize all evictions of this process 
so
+                * that a concurrent invalidation of another range waits until
+                * that quiesce has completed instead of returning while queues
+                * still run on stale PTEs.
+                */
+               mutex_lock(&svms->evict_lock);
                evicted_ranges = atomic_inc_return(&svms->evicted_ranges);
-               if (evicted_ranges != 1)
-                       return r;
-
-               pr_debug("evicting svms 0x%p range [0x%lx 0x%lx]\n",
-                        prange->svms, prange->start, prange->last);
+               if (evicted_ranges == 1) {
+                       pr_debug("evicting svms 0x%p range [0x%lx 0x%lx]\n",
+                                prange->svms, prange->start, prange->last);
 
-               /* First eviction, stop the queues */
-               r = kgd2kfd_quiesce_mm(mm, KFD_QUEUE_EVICTION_TRIGGER_SVM);
-               if (r)
-                       pr_debug("failed to quiesce KFD\n");
+                       /* First eviction, stop the queues */
+                       r = kgd2kfd_quiesce_mm(mm, 
KFD_QUEUE_EVICTION_TRIGGER_SVM);
+                       if (r)
+                               pr_debug("failed to quiesce KFD\n");
 
-               pr_debug("schedule to restore svm %p ranges\n", svms);
-               queue_delayed_work(system_freezable_wq, &svms->restore_work,
-                       msecs_to_jiffies(AMDGPU_SVM_RANGE_RESTORE_DELAY_MS));
+                       pr_debug("schedule to restore svm %p ranges\n", svms);
+                       queue_delayed_work(system_freezable_wq, 
&svms->restore_work,
+                                          
msecs_to_jiffies(AMDGPU_SVM_RANGE_RESTORE_DELAY_MS));
+               }
+               mutex_unlock(&svms->evict_lock);
        } else {
                unsigned long s, l;
                uint32_t trigger;
@@ -3437,6 +3446,7 @@ void svm_range_list_fini(struct kfd_process *p)
                svm_range_free(prange, true);
        }
 
+       mutex_destroy(&p->svms.evict_lock);
        mutex_destroy(&p->svms.lock);
 
        pr_debug("process pid %d svms 0x%p done\n",
@@ -3450,6 +3460,7 @@ int svm_range_list_init(struct kfd_process *p)
 
        svms->objects = RB_ROOT_CACHED;
        mutex_init(&svms->lock);
+       mutex_init(&svms->evict_lock);
        INIT_LIST_HEAD(&svms->list);
        atomic_set(&svms->evicted_ranges, 0);
        atomic_set(&svms->drain_pagefaults, 0);
-- 
2.50.1

Reply via email to