mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
@ 2026-11-06 18:42 Lizhi Hou
  2026-11-06 18:42 ` [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking Lizhi Hou
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Lizhi Hou @ 2026-11-06 18:42 UTC (permalink / raw)
  To: ogabbay, quic_jhugo, dri-devel, mario.limonciello,
	karol.wachowski, max.zhen, thomas.hellstrom,
	himal.prasad.ghimiray, honghuan
  Cc: Matthew Brost, linux-kernel, sonal.santan, Lizhi Hou

From: Matthew Brost <matthew.brost@intel.com>

Drivers which only use the drm_gpusvm_pages API (no drm_gpusvm_range)
have no way to mark pages unmapped from their MMU notifier, as
drm_gpusvm_range_set_unmapped() requires a range. They also have to open
code the mmu_interval_read_retry() check on the recorded notifier_seq
to commit pages faulted with &drm_gpusvm_ctx.no_dma_map.

Split the pages part of drm_gpusvm_range_set_unmapped() into
drm_gpusvm_pages_set_unmapped(), and add drm_gpusvm_pages_read_retry()
which performs the commit side check with a lockdep assert on the
notifier lock.

Assisted-by: LLM
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
---
 drivers/gpu/drm/drm_gpusvm.c | 49 +++++++++++++++++++++++++-----------
 include/drm/drm_gpusvm.h     | 29 +++++++++++++++++++++
 2 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c
index 03dbe304e4b8f..63a26919f59db 100644
--- a/drivers/gpu/drm/drm_gpusvm.c
+++ b/drivers/gpu/drm/drm_gpusvm.c
@@ -1761,9 +1761,8 @@ static int drm_gpusvm_dma_map_pages(struct drm_gpusvm *gpusvm,
  *
  * With &drm_gpusvm_ctx.no_dma_map no mapping state is recorded, so
  * drm_gpusvm_pages_valid() never returns true and success is only a snapshot:
- * the caller must recheck mmu_interval_read_retry() against the recorded
- * &drm_gpusvm_pages.notifier_seq under the notifier lock, and hold it until
- * its work is visible to invalidation.
+ * the caller must recheck drm_gpusvm_pages_read_retry() under the notifier
+ * lock, and hold it until its work is visible to invalidation.
  *
  * Return: 0 on success, negative error code on failure.
  */
@@ -2021,6 +2020,38 @@ bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start,
 }
 EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping);
 
+/**
+ * drm_gpusvm_pages_set_unmapped() - Mark GPU SVM pages as unmapped
+ * @gpusvm: Pointer to the GPU SVM structure.
+ * @pages: Pointer to the GPU SVM pages structure(s).
+ * @pages_count: Number of GPU SVM pages structure(s) passed in.
+ *
+ * Mark @pages as unmapped, after which drm_gpusvm_get_pages() fails with
+ * -EFAULT for them. Intended to be called from the driver's MMU notifier on
+ * MMU_NOTIFY_UNMAP, and usable without a &drm_gpusvm_range by drivers that
+ * only use the drm_gpusvm_pages API. The caller must hold the notifier lock
+ * in write mode.
+ */
+void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
+				   struct drm_gpusvm_pages *pages,
+				   unsigned int pages_count)
+{
+	unsigned int i;
+
+	lockdep_assert_held_write(&gpusvm->notifier_lock);
+
+	for (i = 0; i < pages_count; ++i) {
+		struct drm_gpusvm_pages_flags flags = {
+			.__flags = pages[i].flags.__flags,
+		};
+
+		flags.unmapped = true;
+		/* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
+		WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
+	}
+}
+EXPORT_SYMBOL_GPL(drm_gpusvm_pages_set_unmapped);
+
 /**
  * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped
  * @range: Pointer to the GPU SVM range structure.
@@ -2039,20 +2070,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
 	struct drm_gpusvm_range_flags range_flags = {
 		.__flags = range->flags.__flags,
 	};
-	unsigned int i;
 
-	lockdep_assert_held_write(&range->gpusvm->notifier_lock);
+	drm_gpusvm_pages_set_unmapped(range->gpusvm, pages, pages_count);
 
 	range_flags.unmapped = true;
-	for (i = 0; i < pages_count; ++i) {
-		struct drm_gpusvm_pages_flags flags = {
-			.__flags = pages[i].flags.__flags,
-		};
-
-		flags.unmapped = true;
-		/* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
-		WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
-	}
 	if (drm_gpusvm_range_start(range) < mmu_range->start ||
 	    drm_gpusvm_range_end(range) > mmu_range->end)
 		range_flags.partial_unmap = true;
diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h
index 4d31c6bba7455..b43432af56377 100644
--- a/include/drm/drm_gpusvm.h
+++ b/include/drm/drm_gpusvm.h
@@ -349,6 +349,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
 				   unsigned int pages_count,
 				   const struct mmu_notifier_range *mmu_range);
 
+void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
+				   struct drm_gpusvm_pages *pages,
+				   unsigned int pages_count);
+
 int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
 			 struct drm_gpusvm_pages *svm_pages,
 			 unsigned int num_pages,
@@ -383,6 +387,31 @@ static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages,
 	svm_pages->notifier_seq = LONG_MAX;
 }
 
+/**
+ * drm_gpusvm_pages_read_retry() - Check whether GPU SVM pages went stale
+ * @gpusvm: Pointer to the GPU SVM structure.
+ * @svm_pages: Pointer to the drm_gpusvm_pages.
+ * @notifier: The notifier passed to drm_gpusvm_get_pages() for @svm_pages.
+ *
+ * Check whether @notifier was invalidated since the last successful
+ * drm_gpusvm_get_pages() for @svm_pages. This is the commit side check for
+ * pages faulted with &drm_gpusvm_ctx.no_dma_map, where no mapping state is
+ * tracked. Pages that were never faulted always report as stale. The caller
+ * must hold the notifier lock and keep holding it until its use of the pages
+ * is visible to the invalidation path.
+ *
+ * Return: True if the pages must be faulted again, false otherwise.
+ */
+static inline bool
+drm_gpusvm_pages_read_retry(struct drm_gpusvm *gpusvm,
+			    struct drm_gpusvm_pages *svm_pages,
+			    struct mmu_interval_notifier *notifier)
+{
+	lockdep_assert_held(&gpusvm->notifier_lock);
+
+	return mmu_interval_read_retry(notifier, svm_pages->notifier_seq);
+}
+
 /**
  * drm_gpusvm_pages_first_dma() - Resolve the device address array
  * @svm_pages: Pointer to the drm_gpusvm_pages.

base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking
  2026-11-06 18:42 [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Lizhi Hou
@ 2026-11-06 18:42 ` Lizhi Hou
  2026-11-09 19:44   ` Max Zhen
  2026-11-09  8:56 ` [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Huang, Honglei1
  2026-11-09 22:34 ` Matthew Brost
  2 siblings, 1 reply; 7+ messages in thread
From: Lizhi Hou @ 2026-11-06 18:42 UTC (permalink / raw)
  To: ogabbay, quic_jhugo, dri-devel, mario.limonciello,
	karol.wachowski, max.zhen, thomas.hellstrom,
	himal.prasad.ghimiray, honghuan
  Cc: Matthew Brost, linux-kernel, sonal.santan, Lizhi Hou

From: Matthew Brost <matthew.brost@intel.com>

In PASID mode the device accesses user mappings of a BO through the
IOMMU, but cannot take page faults, so the driver faults the CPU pages
in with HMM before submitting a job and waits for the job from the MMU
notifier. The HMM PFNs themselves are never used.

The open coded implementation of this has a few problems:

- Each mapping embeds a struct hmm_range, and keeps the PFN array it
  points at allocated for the lifetime of the mapping, although the
  PFNs are dropped as soon as the fault returns.
- The per mapping invalid flag duplicates what the notifier sequence
  number already tracks, and must be kept in sync with it.
- The notifier lock is taken in write mode on the populate path.
- Submission locks all reservations, finds the first BO with an
  invalidated mapping, unlocks everything, faults that single BO in and
  starts over, once per invalidated BO.

drm_gpusvm already implements the fault and retry loop, including
faulting without a DMA mapping (&drm_gpusvm_ctx.no_dma_map), and its
pages only mode does not require the driver to use drm_gpusvm ranges.
Use it:

- Replace the device notifier_lock with a pages only struct drm_gpusvm
  and its notifier lock.
- Replace the embedded hmm_range and the invalid/unmapped flags with a
  struct drm_gpusvm_pages. A mapping is stale when
  drm_gpusvm_pages_read_retry() says so and it is not unmapped.
- Fault in all stale mappings with drm_gpusvm_get_pages() before
  locking the reservations, then check that none went stale again
  under the notifier lock, which is held until the job fences are
  published, as before.
- The invalidate callback only bumps the sequence number and marks the
  mapping unmapped on MMU_NOTIFY_UNMAP under the write lock, before
  waiting for the jobs.

A temporary PFN array is now allocated per fault instead of keeping one
per mapping, which also removes the race above.

Assisted-by: LLM
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
[Lizhi: Merge with latest code]
Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
---
 drivers/accel/amdxdna/Kconfig           |   2 +-
 drivers/accel/amdxdna/aie2_ctx.c        |  31 +--
 drivers/accel/amdxdna/aie4_ctx.c        |  29 +--
 drivers/accel/amdxdna/amdxdna_gem.c     | 312 +++++++++++++-----------
 drivers/accel/amdxdna/amdxdna_gem.h     |  23 +-
 drivers/accel/amdxdna/amdxdna_pci_drv.c |  15 +-
 drivers/accel/amdxdna/amdxdna_pci_drv.h |   3 +-
 7 files changed, 213 insertions(+), 202 deletions(-)

diff --git a/drivers/accel/amdxdna/Kconfig b/drivers/accel/amdxdna/Kconfig
index eddff490b4f75..3721eb580b71a 100644
--- a/drivers/accel/amdxdna/Kconfig
+++ b/drivers/accel/amdxdna/Kconfig
@@ -7,10 +7,10 @@ config DRM_ACCEL_AMDXDNA
 	depends on PCI && HAS_IOMEM
 	depends on X86_64
 	depends on AMD_PMF || !AMD_PMF
+	select DRM_GPUSVM
 	select DRM_SCHED
 	select DRM_GEM_SHMEM_HELPER
 	select FW_LOADER
-	select HMM_MIRROR
 	help
 	  Choose this option to enable support for NPU integrated into AMD
 	  client CPUs like AMD Ryzen AI 300 Series. AMD NPU can be used to
diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c
index e1c0e0689b12e..552c30ea6e387 100644
--- a/drivers/accel/amdxdna/aie2_ctx.c
+++ b/drivers/accel/amdxdna/aie2_ctx.c
@@ -1116,8 +1116,7 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
 	struct amdxdna_dev *xdna = hwctx->client->xdna;
 	struct ww_acquire_ctx acquire_ctx;
 	struct dma_fence_chain *chain;
-	struct amdxdna_gem_obj *abo;
-	unsigned long timeout = 0;
+	unsigned long deadline;
 	int ret, i;
 
 	ret = down_interruptible(&hwctx->priv->job_sem);
@@ -1140,7 +1139,15 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
 		goto free_chain;
 	}
 
+	deadline = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 retry:
+	for (i = 0; i < job->bo_cnt; i++) {
+		ret = amdxdna_gem_umap_populate(to_xdna_obj(job->bos[i]),
+						deadline);
+		if (ret)
+			goto cleanup_job;
+	}
+
 	ret = drm_gem_lock_reservations(job->bos, job->bo_cnt, &acquire_ctx);
 	if (ret) {
 		XDNA_WARN(xdna, "Failed to lock BOs, ret %d", ret);
@@ -1156,23 +1163,11 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
 		}
 	}
 
-	down_read(&xdna->notifier_lock);
+	drm_gpusvm_notifier_lock(&xdna->gpusvm);
 	for (i = 0; i < job->bo_cnt; i++) {
-		abo = to_xdna_obj(job->bos[i]);
-		if (abo->mem.map_invalid) {
-			up_read(&xdna->notifier_lock);
+		if (amdxdna_gem_umap_stale(to_xdna_obj(job->bos[i]))) {
+			drm_gpusvm_notifier_unlock(&xdna->gpusvm);
 			drm_gem_unlock_reservations(job->bos, job->bo_cnt, &acquire_ctx);
-			if (!timeout) {
-				timeout = jiffies +
-					msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
-			} else if (time_after(jiffies, timeout)) {
-				ret = -ETIME;
-				goto cleanup_job;
-			}
-
-			ret = amdxdna_populate_range(abo);
-			if (ret)
-				goto cleanup_job;
 			goto retry;
 		}
 	}
@@ -1192,7 +1187,7 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
 	drm_syncobj_add_point(hwctx->priv->syncobj, chain, job->out_fence, *seq);
 	mutex_unlock(&hwctx->priv->io_lock);
 
-	up_read(&xdna->notifier_lock);
+	drm_gpusvm_notifier_unlock(&xdna->gpusvm);
 	drm_gem_unlock_reservations(job->bos, job->bo_cnt, &acquire_ctx);
 
 	aie2_job_put(job);
diff --git a/drivers/accel/amdxdna/aie4_ctx.c b/drivers/accel/amdxdna/aie4_ctx.c
index a3f0a69b93b75..0db7dcdf106ba 100644
--- a/drivers/accel/amdxdna/aie4_ctx.c
+++ b/drivers/accel/amdxdna/aie4_ctx.c
@@ -1059,8 +1059,7 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
 {
 	struct ww_acquire_ctx acquire_ctx;
 	struct drm_gem_object **objs;
-	struct amdxdna_gem_obj *abo;
-	unsigned long timeout = 0;
+	unsigned long deadline;
 	u32 cnt = 0;
 	int ret;
 	u32 i;
@@ -1076,7 +1075,13 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
 	for (i = 0; i < job->aie4_cmd_bo_cnt; i++)
 		add_lock_obj(objs, &cnt, to_gobj(job->aie4_cmd_bos[i]));
 
+	deadline = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 retry:
+	for (i = 0; i < cnt; i++) {
+		ret = amdxdna_gem_umap_populate(to_xdna_obj(objs[i]), deadline);
+		if (ret)
+			goto free_objs;
+	}
 	ret = drm_gem_lock_reservations(objs, cnt, &acquire_ctx);
 	if (ret) {
 		XDNA_WARN(xdna, "Failed to lock BOs, ret %d", ret);
@@ -1091,23 +1096,11 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
 		}
 	}
 
-	down_read(&xdna->notifier_lock);
+	drm_gpusvm_notifier_lock(&xdna->gpusvm);
 	for (i = 0; i < cnt; i++) {
-		abo = to_xdna_obj(objs[i]);
-		if (abo->mem.map_invalid) {
-			up_read(&xdna->notifier_lock);
+		if (amdxdna_gem_umap_stale(to_xdna_obj(objs[i]))) {
+			drm_gpusvm_notifier_unlock(&xdna->gpusvm);
 			drm_gem_unlock_reservations(objs, cnt, &acquire_ctx);
-			if (!timeout) {
-				timeout = jiffies +
-					msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
-			} else if (time_after(jiffies, timeout)) {
-				ret = -ETIME;
-				goto free_objs;
-			}
-
-			ret = amdxdna_populate_range(abo);
-			if (ret)
-				goto free_objs;
 			goto retry;
 		}
 	}
@@ -1115,7 +1108,7 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
 	job->out_fence = dma_fence_get(job->fence);
 	for (i = 0; i < cnt; i++)
 		dma_resv_add_fence(objs[i]->resv, job->out_fence, DMA_RESV_USAGE_WRITE);
-	up_read(&xdna->notifier_lock);
+	drm_gpusvm_notifier_unlock(&xdna->gpusvm);
 
 unlock:
 	drm_gem_unlock_reservations(objs, cnt, &acquire_ctx);
diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c
index 09e48c794e1b0..57d87fa98d671 100644
--- a/drivers/accel/amdxdna/amdxdna_gem.c
+++ b/drivers/accel/amdxdna/amdxdna_gem.c
@@ -232,37 +232,46 @@ static bool amdxdna_hmm_invalidate(struct mmu_interval_notifier *mni,
 {
 	struct amdxdna_umap *mapp = container_of(mni, struct amdxdna_umap, notifier);
 	struct amdxdna_gem_obj *abo = mapp->abo;
-	struct amdxdna_dev *xdna;
+	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
+	bool unreg;
 
 	if (!mmu_notifier_range_blockable(range))
 		return false;
 
-	if (mapp->unmapped)
-		return true;
-
-	xdna = to_xdna_dev(to_gobj(abo)->dev);
 	XDNA_DBG(xdna, "Invalidating range 0x%lx, 0x%lx, type %d",
-		 mapp->range.start, mapp->range.end, abo->type);
+		 mapp->start, mapp->end, abo->type);
 
-	down_write(&xdna->notifier_lock);
-	abo->mem.map_invalid = true;
-	mapp->invalid = true;
-	mmu_interval_set_seq(&mapp->notifier, cur_seq);
-	up_write(&xdna->notifier_lock);
+	down_write(&xdna->gpusvm.notifier_lock);
+	mmu_interval_set_seq(mni, cur_seq);
+	/*
+	 * Flag the mapping as gone before the wait below, so that a submission
+	 * racing with the unmap does not try to fault it in again.
+	 */
+	unreg = !mapp->pages.flags.unmapped && range->event == MMU_NOTIFY_UNMAP;
+	if (unreg)
+		drm_gpusvm_pages_set_unmapped(&xdna->gpusvm, &mapp->pages, 1);
+	up_write(&xdna->gpusvm.notifier_lock);
 
 	/*
 	 * The pages must not be released while the device may still access
 	 * them, so this wait must be neither interruptible nor time limited.
+	 *
+	 * Every invalidation of the mapping has to wait, including the ones
+	 * which find it already flagged: the flag is set above before the wait,
+	 * so it does not tell whether the jobs of a concurrent invalidation
+	 * have finished.
 	 */
 	dma_resv_wait_timeout(to_gobj(abo)->resv, DMA_RESV_USAGE_BOOKKEEP,
 			      false, MAX_SCHEDULE_TIMEOUT);
 
-	if (range->event == MMU_NOTIFY_UNMAP) {
-		down_write(&xdna->notifier_lock);
-		mapp->unmapped = true;
+	/*
+	 * Queue the unregistration only once the jobs are done: it drops the
+	 * last reference of the mapping, and mmu_interval_notifier_remove()
+	 * waits for this callback, which would pin the single worker of the
+	 * ordered WQ_MEM_RECLAIM notifier_wq for as long as a job runs.
+	 */
+	if (unreg)
 		queue_work(xdna->notifier_wq, &abo->hmm_unreg_work);
-		up_write(&xdna->notifier_lock);
-	}
 
 	return true;
 }
@@ -271,12 +280,25 @@ static const struct mmu_interval_notifier_ops amdxdna_hmm_ops = {
 	.invalidate = amdxdna_hmm_invalidate,
 };
 
-static inline bool compare_range(struct amdxdna_umap *mapp,
-				 struct mm_struct *mm,
-				 unsigned long start, unsigned long end)
+static bool amdxdna_umap_match(struct amdxdna_umap *mapp,
+			       struct mm_struct *mm,
+			       unsigned long start, unsigned long end)
 {
-	return (!mapp->unmapped && mapp->notifier.mm == mm &&
-		mapp->range.start == start && mapp->range.end == end);
+	return !mapp->pages.flags.unmapped && mapp->notifier.mm == mm &&
+		mapp->start == start && mapp->end == end;
+}
+
+static bool amdxdna_umap_stale(struct drm_gpusvm *gpusvm,
+			       struct amdxdna_umap *mapp)
+{
+	/*
+	 * The pages of a BO whose mappings mmap() populated upfront are never
+	 * faulted in. Neither are the ones of a mapping which is gone: device
+	 * accesses through it are rejected by the IOMMU, so there is nothing
+	 * to fault in for it.
+	 */
+	return mapp->abo->mem.map_invalid && !mapp->pages.flags.unmapped &&
+		drm_gpusvm_pages_read_retry(gpusvm, &mapp->pages, &mapp->notifier);
 }
 
 static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo,
@@ -285,15 +307,28 @@ static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo,
 	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
 	struct amdxdna_umap *mapp;
 
-	down_write(&xdna->notifier_lock);
+	down_write(&xdna->gpusvm.notifier_lock);
 	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
-		if (!compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end))
+		if (!amdxdna_umap_match(mapp, vma->vm_mm, vma->vm_start, vma->vm_end))
 			continue;
 
-		mapp->unmapped = true;
+		drm_gpusvm_pages_set_unmapped(&xdna->gpusvm, &mapp->pages, 1);
 		queue_work(xdna->notifier_wq, &abo->hmm_unreg_work);
 	}
-	up_write(&xdna->notifier_lock);
+	up_write(&xdna->gpusvm.notifier_lock);
+}
+
+static void amdxdna_umap_release(struct kref *ref)
+{
+	struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt);
+
+	mmu_interval_notifier_remove(&mapp->notifier);
+	kfree(mapp);
+}
+
+static void amdxdna_umap_put(struct amdxdna_umap *mapp)
+{
+	kref_put(&mapp->refcnt, amdxdna_umap_release);
 }
 
 static void amdxdna_hmm_unreg_umaps(struct amdxdna_gem_obj *abo, bool force)
@@ -302,121 +337,119 @@ static void amdxdna_hmm_unreg_umaps(struct amdxdna_gem_obj *abo, bool force)
 	struct amdxdna_umap *mapp, *tmp;
 	LIST_HEAD(dead);
 
-	down_write(&xdna->notifier_lock);
+	down_write(&xdna->gpusvm.notifier_lock);
 	list_for_each_entry_safe(mapp, tmp, &abo->mem.umap_list, node) {
-		if (!force && !mapp->unmapped)
+		if (!force && !mapp->pages.flags.unmapped)
 			continue;
 
-		mapp->unmapped = true;
+		drm_gpusvm_pages_set_unmapped(&xdna->gpusvm, &mapp->pages, 1);
 		list_move(&mapp->node, &dead);
 	}
 	if (list_empty(&abo->mem.umap_list))
 		abo->mem.uva = AMDXDNA_INVALID_ADDR;
-	up_write(&xdna->notifier_lock);
+	up_write(&xdna->gpusvm.notifier_lock);
 
 	list_for_each_entry_safe(mapp, tmp, &dead, node)
 		amdxdna_umap_put(mapp);
 }
 
-static void amdxdna_umap_release(struct kref *ref)
+static void amdxdna_hmm_unreg_work(struct work_struct *work)
 {
-	struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt);
-
-	mmu_interval_notifier_remove(&mapp->notifier);
-	mutex_destroy(&mapp->lock);
-	kvfree(mapp->range.hmm_pfns);
-	kfree(mapp);
-}
+	struct amdxdna_gem_obj *abo = container_of(work, struct amdxdna_gem_obj,
+						   hmm_unreg_work);
 
-void amdxdna_umap_put(struct amdxdna_umap *mapp)
-{
-	kref_put(&mapp->refcnt, amdxdna_umap_release);
+	amdxdna_hmm_unreg_umaps(abo, false);
 }
 
-int amdxdna_populate_range(struct amdxdna_gem_obj *abo)
+/**
+ * amdxdna_gem_umap_populate() - Fault in the invalidated user mappings of a BO
+ * @abo: The BO.
+ * @deadline: Jiffies after which to give up.
+ *
+ * The device accesses user mappings through the PASID and cannot take page
+ * faults, so the CPU pages of every mapping of @abo invalidated since it was
+ * last faulted are faulted in again here. This is only a snapshot, the caller
+ * must check amdxdna_gem_umap_stale() under the notifier lock before using
+ * the mappings and retry if it fails.
+ *
+ * Two hardware contexts may fault the same mapping at the same time. That is
+ * left unserialized: with &drm_gpusvm_ctx.no_dma_map the only state recorded
+ * for a mapping is its notifier sequence, and the loser of such a race can
+ * only store an older sequence, which makes amdxdna_gem_umap_stale() report
+ * stale and the caller fault again.
+ *
+ * Return: 0 on success, -ETIME if @deadline passed, negative error code on
+ * other failures.
+ */
+int amdxdna_gem_umap_populate(struct amdxdna_gem_obj *abo,
+			      unsigned long deadline)
 {
 	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
+	struct drm_gpusvm *gpusvm = &xdna->gpusvm;
 	struct amdxdna_umap *mapp;
-	unsigned long timeout;
-	struct mm_struct *mm;
-	bool found;
+	bool found, unmapped;
 	int ret;
 
-	timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
-again:
-	found = false;
-	down_write(&xdna->notifier_lock);
-	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
-		/*
-		 * Skip entries that have already been unmapped.
-		 *
-		 * If userspace unmaps the address and later submits I/O using
-		 * it, the IOMMU will reject the access and report a fault.
-		 * Ignore such entries here.
-		 */
-		if (mapp->unmapped)
-			continue;
+	for (;;) {
+		struct drm_gpusvm_ctx ctx = { .no_dma_map = 1 };
 
-		if (mapp->invalid && kref_get_unless_zero(&mapp->refcnt)) {
-			found = true;
-			break;
+		found = false;
+		drm_gpusvm_notifier_lock(gpusvm);
+		list_for_each_entry(mapp, &abo->mem.umap_list, node) {
+			if (amdxdna_umap_stale(gpusvm, mapp) &&
+			    kref_get_unless_zero(&mapp->refcnt)) {
+				found = true;
+				break;
+			}
 		}
-	}
-
-	if (!found) {
-		/*
-		 * This also covers the case where all mappings have been
-		 * removed. There are no invalid mappings left to process.
-		 * Any subsequent I/O using the unmapped address will be
-		 * rejected by the IOMMU.
-		 */
-		abo->mem.map_invalid = false;
-		up_write(&xdna->notifier_lock);
-		return 0;
-	}
-
-	up_write(&xdna->notifier_lock);
-
-	mm = mapp->notifier.mm;
-	if (!mmget_not_zero(mm)) {
-		amdxdna_umap_put(mapp);
-		return -EFAULT;
-	}
+		drm_gpusvm_notifier_unlock(gpusvm);
+		if (!found)
+			return 0;
 
-	mutex_lock(&mapp->lock);
-	ret = hmm_range_fault_unlocked_timeout(&mapp->range, timeout);
-	if (ret) {
-		mutex_unlock(&mapp->lock);
-		goto put_mm;
-	}
+		if (time_after(jiffies, deadline)) {
+			amdxdna_umap_put(mapp);
+			return -ETIME;
+		}
 
-	down_write(&xdna->notifier_lock);
-	if (mmu_interval_read_retry(&mapp->notifier, mapp->range.notifier_seq)) {
-		up_write(&xdna->notifier_lock);
-		mutex_unlock(&mapp->lock);
+		ctx.read_only = mapp->read_only;
+		ret = drm_gpusvm_get_pages(gpusvm, &mapp->pages, 1,
+					   mapp->notifier.mm, &mapp->notifier,
+					   mapp->start, mapp->end, &ctx);
+		if (ret == -EFAULT) {
+			drm_gpusvm_notifier_lock(gpusvm);
+			unmapped = mapp->pages.flags.unmapped;
+			drm_gpusvm_notifier_unlock(gpusvm);
+			if (unmapped)
+				ret = 0;
+		}
 		amdxdna_umap_put(mapp);
-		mmput(mm);
-		goto again;
+		if (ret)
+			return ret == -EBUSY ? -ETIME : ret;
 	}
-	mapp->invalid = false;
-	up_write(&xdna->notifier_lock);
-	mutex_unlock(&mapp->lock);
-	amdxdna_umap_put(mapp);
-	mmput(mm);
-	goto again;
-
-put_mm:
-	amdxdna_umap_put(mapp);
-	mmput(mm);
-	return ret == -EBUSY ? -ETIME : ret;
 }
 
-static void amdxdna_hmm_unreg_work(struct work_struct *work)
+/**
+ * amdxdna_gem_umap_stale() - Check whether a BO needs its user mappings faulted
+ * @abo: The BO.
+ *
+ * Must be called with the notifier lock held, which must be kept held until
+ * the fences of the jobs using @abo are published in its reservation object.
+ *
+ * Return: True if amdxdna_gem_umap_populate() must be called for @abo.
+ */
+bool amdxdna_gem_umap_stale(struct amdxdna_gem_obj *abo)
 {
-	struct amdxdna_gem_obj *abo = container_of(work, struct amdxdna_gem_obj,
-						   hmm_unreg_work);
+	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
+	struct amdxdna_umap *mapp;
 
-	amdxdna_hmm_unreg_umaps(abo, false);
+	lockdep_assert_held(&xdna->gpusvm.notifier_lock);
+
+	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
+		if (amdxdna_umap_stale(&xdna->gpusvm, mapp))
+			return true;
+	}
+
+	return false;
 }
 
 static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
@@ -426,7 +459,6 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
 	unsigned long len = vma->vm_end - vma->vm_start;
 	unsigned long addr = vma->vm_start;
 	struct amdxdna_umap *mapp;
-	unsigned long nr_pages;
 	int ret;
 
 	/*
@@ -441,38 +473,34 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
 		return 0;
 	}
 
-	down_read(&xdna->notifier_lock);
+	down_read(&xdna->gpusvm.notifier_lock);
 	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
-		if (compare_range(mapp, current->mm, addr, addr + len)) {
-			up_read(&xdna->notifier_lock);
+		if (amdxdna_umap_match(mapp, current->mm, addr, addr + len)) {
+			up_read(&xdna->gpusvm.notifier_lock);
 			return 0;
 		}
 	}
-	up_read(&xdna->notifier_lock);
+	up_read(&xdna->gpusvm.notifier_lock);
 
 	mapp = kzalloc_obj(*mapp);
 	if (!mapp)
 		return -ENOMEM;
 
-	nr_pages = (PAGE_ALIGN(addr + len) - (addr & PAGE_MASK)) >> PAGE_SHIFT;
-	mapp->range.hmm_pfns = kvzalloc_objs(*mapp->range.hmm_pfns, nr_pages);
-	if (!mapp->range.hmm_pfns) {
-		ret = -ENOMEM;
-		goto free_map;
-	}
+	/*
+	 * Never faulted, so stale until the first amdxdna_gem_umap_populate(),
+	 * should the pages of the BO have to be faulted in at all.
+	 */
+	drm_gpusvm_init_pages(&mapp->pages, &xdna->ddev);
+	mapp->start = addr;
+	mapp->end = addr + len;
 
-	mapp->range.notifier = &mapp->notifier;
-	mapp->range.start = vma->vm_start;
-	mapp->range.end = vma->vm_end;
 	/*
 	 * Access permissions are fixed at mmap() time. Changing them later
-	 * with mprotect() is not supported: the range keeps requesting the
+	 * with mprotect() is not supported: the mapping keeps requesting the
 	 * original permissions, so the application may see a fault failure
 	 * or an IOMMU fault.
 	 */
-	mapp->range.default_flags = HMM_PFN_REQ_FAULT;
-	if (vma->vm_flags & VM_WRITE)
-		mapp->range.default_flags |= HMM_PFN_REQ_WRITE;
+	mapp->read_only = !(vma->vm_flags & VM_WRITE);
 	mapp->abo = abo;
 	kref_init(&mapp->refcnt);
 
@@ -483,23 +511,17 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
 						  &amdxdna_hmm_ops);
 	if (ret) {
 		XDNA_ERR(xdna, "Insert mmu notifier failed, ret %d", ret);
-		goto free_pfns;
+		kfree(mapp);
+		return ret;
 	}
 
-	mutex_init(&mapp->lock);
-	down_write(&xdna->notifier_lock);
+	down_write(&xdna->gpusvm.notifier_lock);
 	if (list_empty(&abo->mem.umap_list))
 		abo->mem.uva = addr;
 	list_add_tail(&mapp->node, &abo->mem.umap_list);
-	up_write(&xdna->notifier_lock);
+	up_write(&xdna->gpusvm.notifier_lock);
 
 	return 0;
-
-free_pfns:
-	kvfree(mapp->range.hmm_pfns);
-free_map:
-	kfree(mapp);
-	return ret;
 }
 
 static struct amdxdna_gem_obj *
@@ -547,21 +569,13 @@ static void amdxdna_gem_dev_obj_free(struct drm_gem_object *gobj)
 	amdxdna_gem_destroy_obj(abo);
 }
 
-static void amdxdna_mark_mapp_invalid(struct amdxdna_gem_obj *abo,
-				      struct vm_area_struct *vma)
+static void amdxdna_mark_map_invalid(struct amdxdna_gem_obj *abo)
 {
 	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
-	struct amdxdna_umap *mapp;
 
-	down_write(&xdna->notifier_lock);
+	down_write(&xdna->gpusvm.notifier_lock);
 	abo->mem.map_invalid = true;
-	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
-		if (compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end)) {
-			mapp->invalid = true;
-			break;
-		}
-	}
-	up_write(&xdna->notifier_lock);
+	up_write(&xdna->gpusvm.notifier_lock);
 }
 
 static int amdxdna_remap_pages(struct amdxdna_gem_obj *abo,
@@ -628,7 +642,7 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo,
 		}
 
 		if (!(vma->vm_flags & VM_PFNMAP))
-			amdxdna_mark_mapp_invalid(abo, vma);
+			amdxdna_mark_map_invalid(abo);
 
 		/* Drop the reference drm_gem_mmap_obj() acquired.*/
 		drm_gem_object_put(to_gobj(abo));
diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h
index 5c429869992e8..2deead107d6b1 100644
--- a/drivers/accel/amdxdna/amdxdna_gem.h
+++ b/drivers/accel/amdxdna/amdxdna_gem.h
@@ -7,19 +7,19 @@
 #define _AMDXDNA_GEM_H_
 
 #include <drm/drm_gem_shmem_helper.h>
-#include <linux/hmm.h>
+#include <drm/drm_gpusvm.h>
 #include <linux/iova.h>
 #include "amdxdna_pci_drv.h"
 
 struct amdxdna_umap {
 	struct mmu_interval_notifier	notifier;
-	struct hmm_range		range;
+	struct drm_gpusvm_pages		pages;
+	unsigned long			start;
+	unsigned long			end;
+	bool				read_only;
 	struct amdxdna_gem_obj		*abo;
 	struct list_head		node;
 	struct kref			refcnt;
-	bool				invalid;
-	bool				unmapped;
-	struct mutex			lock; /* protect range */
 };
 
 struct amdxdna_mem {
@@ -27,10 +27,16 @@ struct amdxdna_mem {
 	u64				dma_addr;
 	size_t				size;
 	struct list_head		umap_list;
+	/*
+	 * True, if the pages of the user mappings have to be faulted in before
+	 * the device accesses them. A mapping which mmap() populated upfront,
+	 * which is a VM_PFNMAP one, must not be faulted in: hmm_range_fault()
+	 * rejects such a VMA.
+	 */
 	bool				map_invalid;
 	/*
 	 * Cache the first mmap uva as PASID addr, which can be accessed by driver
-	 * without taking notifier_lock.
+	 * without taking the gpusvm notifier lock.
 	 */
 	u64				uva;
 };
@@ -103,8 +109,9 @@ static inline u64 amdxdna_obj_dma_addr(struct amdxdna_gem_obj *abo)
 		abo->mem.dma_addr : amdxdna_gem_uva(abo);
 }
 
-void amdxdna_umap_put(struct amdxdna_umap *mapp);
-int amdxdna_populate_range(struct amdxdna_gem_obj *abo);
+int amdxdna_gem_umap_populate(struct amdxdna_gem_obj *abo,
+			      unsigned long deadline);
+bool amdxdna_gem_umap_stale(struct amdxdna_gem_obj *abo);
 void amdxdna_gem_heap_free(struct amdxdna_client *client, struct amdxdna_gem_obj *abo);
 
 struct drm_gem_object *
diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c
index f233eb70a2957..18bd7bdd12969 100644
--- a/drivers/accel/amdxdna/amdxdna_pci_drv.c
+++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c
@@ -363,6 +363,7 @@ static void amdxdna_xdna_drm_release(struct drm_device *drm, void *res)
 	struct amdxdna_dev *xdna = res;
 
 	amdxdna_carveout_fini(xdna);
+	drm_gpusvm_fini(&xdna->gpusvm);
 }
 
 static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id)
@@ -389,18 +390,18 @@ static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	if (ret)
 		return ret;
 
-	init_rwsem(&xdna->notifier_lock);
+	ret = drm_gpusvm_init(&xdna->gpusvm, "amdxdna", NULL, 0, 0, 0, NULL,
+			      NULL, 0);
+	if (ret)
+		return ret;
+
 	INIT_LIST_HEAD(&xdna->client_list);
 	pci_set_drvdata(pdev, xdna);
 
 	ret = drmm_add_action(ddev, amdxdna_xdna_drm_release, xdna);
-	if (ret)
+	if (ret) {
+		drm_gpusvm_fini(&xdna->gpusvm);
 		return ret;
-
-	if (IS_ENABLED(CONFIG_LOCKDEP)) {
-		fs_reclaim_acquire(GFP_KERNEL);
-		might_lock(&xdna->notifier_lock);
-		fs_reclaim_release(GFP_KERNEL);
 	}
 
 	ret = amdxdna_iommu_init(xdna);
diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.h b/drivers/accel/amdxdna/amdxdna_pci_drv.h
index 27593190dcf67..7663b883d4f8b 100644
--- a/drivers/accel/amdxdna/amdxdna_pci_drv.h
+++ b/drivers/accel/amdxdna/amdxdna_pci_drv.h
@@ -7,6 +7,7 @@
 #define _AMDXDNA_PCI_DRV_H_
 
 #include <drm/amdxdna_accel.h>
+#include <drm/drm_gpusvm.h>
 #include <drm/drm_mm.h>
 #include <drm/drm_print.h>
 #include <linux/iommu.h>
@@ -118,7 +119,7 @@ struct amdxdna_dev {
 	struct list_head		client_list;
 	struct mutex			client_lock; /* client_list */
 	struct amdxdna_drm_query_firmware_version fw_ver;
-	struct rw_semaphore		notifier_lock; /* for mmu notifier*/
+	struct drm_gpusvm		gpusvm; /* notifier lock for user mappings */
 	struct workqueue_struct		*notifier_wq;
 
 	struct iommu_group		*group;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* RE: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
  2026-11-06 18:42 [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Lizhi Hou
  2026-11-06 18:42 ` [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking Lizhi Hou
@ 2026-11-09  8:56 ` Huang, Honglei1
  2026-11-09 19:53   ` Lizhi Hou
  2026-11-09 22:34 ` Matthew Brost
  2 siblings, 1 reply; 7+ messages in thread
From: Huang, Honglei1 @ 2026-11-09  8:56 UTC (permalink / raw)
  To: Hou, Lizhi
  Cc: Matthew Brost, linux-kernel, Santan, Sonal, Hou, Lizhi, ogabbay,
	quic_jhugo, dri-devel, Limonciello, Mario, karol.wachowski, Zhen,
	Max, thomas.hellstrom, himal.prasad.ghimiray

AMD General

Reviewed-by: Honglei Huang <honghuan@amd.com>

-----Original Message-----
From: Lizhi Hou <lizhi.hou@amd.com>
Sent: Saturday, November 7, 2026 2:42 AM
To: ogabbay@kernel.org; quic_jhugo@quicinc.com; dri-devel@lists.freedesktop.org; Limonciello, Mario <Mario.Limonciello@amd.com>; karol.wachowski@linux.intel.com; Zhen, Max <Max.Zhen@amd.com>; thomas.hellstrom@linux.intel.com; himal.prasad.ghimiray@intel.com; Huang, Honglei1 <Honglei1.Huang@amd.com>
Cc: Matthew Brost <matthew.brost@intel.com>; linux-kernel@vger.kernel.org; Santan, Sonal <sonal.santan@amd.com>; Hou, Lizhi <lizhi.hou@amd.com>
Subject: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers

From: Matthew Brost <matthew.brost@intel.com>

Drivers which only use the drm_gpusvm_pages API (no drm_gpusvm_range) have no way to mark pages unmapped from their MMU notifier, as
drm_gpusvm_range_set_unmapped() requires a range. They also have to open code the mmu_interval_read_retry() check on the recorded notifier_seq to commit pages faulted with &drm_gpusvm_ctx.no_dma_map.

Split the pages part of drm_gpusvm_range_set_unmapped() into drm_gpusvm_pages_set_unmapped(), and add drm_gpusvm_pages_read_retry() which performs the commit side check with a lockdep assert on the notifier lock.

Assisted-by: LLM
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
---
 drivers/gpu/drm/drm_gpusvm.c | 49 +++++++++++++++++++++++++-----------
 include/drm/drm_gpusvm.h     | 29 +++++++++++++++++++++
 2 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 03dbe304e4b8f..63a26919f59db 100644
--- a/drivers/gpu/drm/drm_gpusvm.c
+++ b/drivers/gpu/drm/drm_gpusvm.c
@@ -1761,9 +1761,8 @@ static int drm_gpusvm_dma_map_pages(struct drm_gpusvm *gpusvm,
  *
  * With &drm_gpusvm_ctx.no_dma_map no mapping state is recorded, so
  * drm_gpusvm_pages_valid() never returns true and success is only a snapshot:
- * the caller must recheck mmu_interval_read_retry() against the recorded
- * &drm_gpusvm_pages.notifier_seq under the notifier lock, and hold it until
- * its work is visible to invalidation.
+ * the caller must recheck drm_gpusvm_pages_read_retry() under the
+ notifier
+ * lock, and hold it until its work is visible to invalidation.
  *
  * Return: 0 on success, negative error code on failure.
  */
@@ -2021,6 +2020,38 @@ bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start,  }  EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping);

+/**
+ * drm_gpusvm_pages_set_unmapped() - Mark GPU SVM pages as unmapped
+ * @gpusvm: Pointer to the GPU SVM structure.
+ * @pages: Pointer to the GPU SVM pages structure(s).
+ * @pages_count: Number of GPU SVM pages structure(s) passed in.
+ *
+ * Mark @pages as unmapped, after which drm_gpusvm_get_pages() fails
+with
+ * -EFAULT for them. Intended to be called from the driver's MMU
+notifier on
+ * MMU_NOTIFY_UNMAP, and usable without a &drm_gpusvm_range by drivers
+that
+ * only use the drm_gpusvm_pages API. The caller must hold the notifier
+lock
+ * in write mode.
+ */
+void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
+                                  struct drm_gpusvm_pages *pages,
+                                  unsigned int pages_count)
+{
+       unsigned int i;
+
+       lockdep_assert_held_write(&gpusvm->notifier_lock);
+
+       for (i = 0; i < pages_count; ++i) {
+               struct drm_gpusvm_pages_flags flags = {
+                       .__flags = pages[i].flags.__flags,
+               };
+
+               flags.unmapped = true;
+               /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
+               WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
+       }
+}
+EXPORT_SYMBOL_GPL(drm_gpusvm_pages_set_unmapped);
+
 /**
  * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped
  * @range: Pointer to the GPU SVM range structure.
@@ -2039,20 +2070,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
        struct drm_gpusvm_range_flags range_flags = {
                .__flags = range->flags.__flags,
        };
-       unsigned int i;

-       lockdep_assert_held_write(&range->gpusvm->notifier_lock);
+       drm_gpusvm_pages_set_unmapped(range->gpusvm, pages, pages_count);

        range_flags.unmapped = true;
-       for (i = 0; i < pages_count; ++i) {
-               struct drm_gpusvm_pages_flags flags = {
-                       .__flags = pages[i].flags.__flags,
-               };
-
-               flags.unmapped = true;
-               /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
-               WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
-       }
        if (drm_gpusvm_range_start(range) < mmu_range->start ||
            drm_gpusvm_range_end(range) > mmu_range->end)
                range_flags.partial_unmap = true;
diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 4d31c6bba7455..b43432af56377 100644
--- a/include/drm/drm_gpusvm.h
+++ b/include/drm/drm_gpusvm.h
@@ -349,6 +349,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
                                   unsigned int pages_count,
                                   const struct mmu_notifier_range *mmu_range);

+void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
+                                  struct drm_gpusvm_pages *pages,
+                                  unsigned int pages_count);
+
 int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
                         struct drm_gpusvm_pages *svm_pages,
                         unsigned int num_pages,
@@ -383,6 +387,31 @@ static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages,
        svm_pages->notifier_seq = LONG_MAX;
 }

+/**
+ * drm_gpusvm_pages_read_retry() - Check whether GPU SVM pages went
+stale
+ * @gpusvm: Pointer to the GPU SVM structure.
+ * @svm_pages: Pointer to the drm_gpusvm_pages.
+ * @notifier: The notifier passed to drm_gpusvm_get_pages() for @svm_pages.
+ *
+ * Check whether @notifier was invalidated since the last successful
+ * drm_gpusvm_get_pages() for @svm_pages. This is the commit side check
+for
+ * pages faulted with &drm_gpusvm_ctx.no_dma_map, where no mapping
+state is
+ * tracked. Pages that were never faulted always report as stale. The
+caller
+ * must hold the notifier lock and keep holding it until its use of the
+pages
+ * is visible to the invalidation path.
+ *
+ * Return: True if the pages must be faulted again, false otherwise.
+ */
+static inline bool
+drm_gpusvm_pages_read_retry(struct drm_gpusvm *gpusvm,
+                           struct drm_gpusvm_pages *svm_pages,
+                           struct mmu_interval_notifier *notifier) {
+       lockdep_assert_held(&gpusvm->notifier_lock);
+
+       return mmu_interval_read_retry(notifier, svm_pages->notifier_seq); }
+
 /**
  * drm_gpusvm_pages_first_dma() - Resolve the device address array
  * @svm_pages: Pointer to the drm_gpusvm_pages.

base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
--
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking
  2026-11-06 18:42 ` [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking Lizhi Hou
@ 2026-11-09 19:44   ` Max Zhen
  0 siblings, 0 replies; 7+ messages in thread
From: Max Zhen @ 2026-11-09 19:44 UTC (permalink / raw)
  To: Lizhi Hou, ogabbay, quic_jhugo, dri-devel, mario.limonciello,
	karol.wachowski, thomas.hellstrom, himal.prasad.ghimiray,
	honghuan
  Cc: Matthew Brost, linux-kernel, sonal.santan



On 11/6/2026 Fri 10:42, Lizhi Hou wrote:
> From: Matthew Brost <matthew.brost@intel.com>
> 
> In PASID mode the device accesses user mappings of a BO through the
> IOMMU, but cannot take page faults, so the driver faults the CPU pages
> in with HMM before submitting a job and waits for the job from the MMU
> notifier. The HMM PFNs themselves are never used.
> 
> The open coded implementation of this has a few problems:
> 
> - Each mapping embeds a struct hmm_range, and keeps the PFN array it
>    points at allocated for the lifetime of the mapping, although the
>    PFNs are dropped as soon as the fault returns.
> - The per mapping invalid flag duplicates what the notifier sequence
>    number already tracks, and must be kept in sync with it.
> - The notifier lock is taken in write mode on the populate path.
> - Submission locks all reservations, finds the first BO with an
>    invalidated mapping, unlocks everything, faults that single BO in and
>    starts over, once per invalidated BO.
> 
> drm_gpusvm already implements the fault and retry loop, including
> faulting without a DMA mapping (&drm_gpusvm_ctx.no_dma_map), and its
> pages only mode does not require the driver to use drm_gpusvm ranges.
> Use it:
> 
> - Replace the device notifier_lock with a pages only struct drm_gpusvm
>    and its notifier lock.
> - Replace the embedded hmm_range and the invalid/unmapped flags with a
>    struct drm_gpusvm_pages. A mapping is stale when
>    drm_gpusvm_pages_read_retry() says so and it is not unmapped.
> - Fault in all stale mappings with drm_gpusvm_get_pages() before
>    locking the reservations, then check that none went stale again
>    under the notifier lock, which is held until the job fences are
>    published, as before.
> - The invalidate callback only bumps the sequence number and marks the
>    mapping unmapped on MMU_NOTIFY_UNMAP under the write lock, before
>    waiting for the jobs.
> 
> A temporary PFN array is now allocated per fault instead of keeping one
> per mapping, which also removes the race above.
> 
> Assisted-by: LLM
> Signed-off-by: Matthew Brost <matthew.brost@intel.com>
> [Lizhi: Merge with latest code]
> Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
Reviewed-by: Max Zhen <max.zhen@amd.com>
> ---
>   drivers/accel/amdxdna/Kconfig           |   2 +-
>   drivers/accel/amdxdna/aie2_ctx.c        |  31 +--
>   drivers/accel/amdxdna/aie4_ctx.c        |  29 +--
>   drivers/accel/amdxdna/amdxdna_gem.c     | 312 +++++++++++++-----------
>   drivers/accel/amdxdna/amdxdna_gem.h     |  23 +-
>   drivers/accel/amdxdna/amdxdna_pci_drv.c |  15 +-
>   drivers/accel/amdxdna/amdxdna_pci_drv.h |   3 +-
>   7 files changed, 213 insertions(+), 202 deletions(-)
> 
> diff --git a/drivers/accel/amdxdna/Kconfig b/drivers/accel/amdxdna/Kconfig
> index eddff490b4f75..3721eb580b71a 100644
> --- a/drivers/accel/amdxdna/Kconfig
> +++ b/drivers/accel/amdxdna/Kconfig
> @@ -7,10 +7,10 @@ config DRM_ACCEL_AMDXDNA
>   	depends on PCI && HAS_IOMEM
>   	depends on X86_64
>   	depends on AMD_PMF || !AMD_PMF
> +	select DRM_GPUSVM
>   	select DRM_SCHED
>   	select DRM_GEM_SHMEM_HELPER
>   	select FW_LOADER
> -	select HMM_MIRROR
>   	help
>   	  Choose this option to enable support for NPU integrated into AMD
>   	  client CPUs like AMD Ryzen AI 300 Series. AMD NPU can be used to
> diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c
> index e1c0e0689b12e..552c30ea6e387 100644
> --- a/drivers/accel/amdxdna/aie2_ctx.c
> +++ b/drivers/accel/amdxdna/aie2_ctx.c
> @@ -1116,8 +1116,7 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
>   	struct amdxdna_dev *xdna = hwctx->client->xdna;
>   	struct ww_acquire_ctx acquire_ctx;
>   	struct dma_fence_chain *chain;
> -	struct amdxdna_gem_obj *abo;
> -	unsigned long timeout = 0;
> +	unsigned long deadline;
>   	int ret, i;
>   
>   	ret = down_interruptible(&hwctx->priv->job_sem);
> @@ -1140,7 +1139,15 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
>   		goto free_chain;
>   	}
>   
> +	deadline = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
>   retry:
> +	for (i = 0; i < job->bo_cnt; i++) {
> +		ret = amdxdna_gem_umap_populate(to_xdna_obj(job->bos[i]),
> +						deadline);
> +		if (ret)
> +			goto cleanup_job;
> +	}
> +
>   	ret = drm_gem_lock_reservations(job->bos, job->bo_cnt, &acquire_ctx);
>   	if (ret) {
>   		XDNA_WARN(xdna, "Failed to lock BOs, ret %d", ret);
> @@ -1156,23 +1163,11 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
>   		}
>   	}
>   
> -	down_read(&xdna->notifier_lock);
> +	drm_gpusvm_notifier_lock(&xdna->gpusvm);
>   	for (i = 0; i < job->bo_cnt; i++) {
> -		abo = to_xdna_obj(job->bos[i]);
> -		if (abo->mem.map_invalid) {
> -			up_read(&xdna->notifier_lock);
> +		if (amdxdna_gem_umap_stale(to_xdna_obj(job->bos[i]))) {
> +			drm_gpusvm_notifier_unlock(&xdna->gpusvm);
>   			drm_gem_unlock_reservations(job->bos, job->bo_cnt, &acquire_ctx);
> -			if (!timeout) {
> -				timeout = jiffies +
> -					msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> -			} else if (time_after(jiffies, timeout)) {
> -				ret = -ETIME;
> -				goto cleanup_job;
> -			}
> -
> -			ret = amdxdna_populate_range(abo);
> -			if (ret)
> -				goto cleanup_job;
>   			goto retry;
>   		}
>   	}
> @@ -1192,7 +1187,7 @@ int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
>   	drm_syncobj_add_point(hwctx->priv->syncobj, chain, job->out_fence, *seq);
>   	mutex_unlock(&hwctx->priv->io_lock);
>   
> -	up_read(&xdna->notifier_lock);
> +	drm_gpusvm_notifier_unlock(&xdna->gpusvm);
>   	drm_gem_unlock_reservations(job->bos, job->bo_cnt, &acquire_ctx);
>   
>   	aie2_job_put(job);
> diff --git a/drivers/accel/amdxdna/aie4_ctx.c b/drivers/accel/amdxdna/aie4_ctx.c
> index a3f0a69b93b75..0db7dcdf106ba 100644
> --- a/drivers/accel/amdxdna/aie4_ctx.c
> +++ b/drivers/accel/amdxdna/aie4_ctx.c
> @@ -1059,8 +1059,7 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
>   {
>   	struct ww_acquire_ctx acquire_ctx;
>   	struct drm_gem_object **objs;
> -	struct amdxdna_gem_obj *abo;
> -	unsigned long timeout = 0;
> +	unsigned long deadline;
>   	u32 cnt = 0;
>   	int ret;
>   	u32 i;
> @@ -1076,7 +1075,13 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
>   	for (i = 0; i < job->aie4_cmd_bo_cnt; i++)
>   		add_lock_obj(objs, &cnt, to_gobj(job->aie4_cmd_bos[i]));
>   
> +	deadline = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
>   retry:
> +	for (i = 0; i < cnt; i++) {
> +		ret = amdxdna_gem_umap_populate(to_xdna_obj(objs[i]), deadline);
> +		if (ret)
> +			goto free_objs;
> +	}
>   	ret = drm_gem_lock_reservations(objs, cnt, &acquire_ctx);
>   	if (ret) {
>   		XDNA_WARN(xdna, "Failed to lock BOs, ret %d", ret);
> @@ -1091,23 +1096,11 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
>   		}
>   	}
>   
> -	down_read(&xdna->notifier_lock);
> +	drm_gpusvm_notifier_lock(&xdna->gpusvm);
>   	for (i = 0; i < cnt; i++) {
> -		abo = to_xdna_obj(objs[i]);
> -		if (abo->mem.map_invalid) {
> -			up_read(&xdna->notifier_lock);
> +		if (amdxdna_gem_umap_stale(to_xdna_obj(objs[i]))) {
> +			drm_gpusvm_notifier_unlock(&xdna->gpusvm);
>   			drm_gem_unlock_reservations(objs, cnt, &acquire_ctx);
> -			if (!timeout) {
> -				timeout = jiffies +
> -					msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> -			} else if (time_after(jiffies, timeout)) {
> -				ret = -ETIME;
> -				goto free_objs;
> -			}
> -
> -			ret = amdxdna_populate_range(abo);
> -			if (ret)
> -				goto free_objs;
>   			goto retry;
>   		}
>   	}
> @@ -1115,7 +1108,7 @@ static int fence_job_bos(struct amdxdna_dev *xdna, struct amdxdna_sched_job *job
>   	job->out_fence = dma_fence_get(job->fence);
>   	for (i = 0; i < cnt; i++)
>   		dma_resv_add_fence(objs[i]->resv, job->out_fence, DMA_RESV_USAGE_WRITE);
> -	up_read(&xdna->notifier_lock);
> +	drm_gpusvm_notifier_unlock(&xdna->gpusvm);
>   
>   unlock:
>   	drm_gem_unlock_reservations(objs, cnt, &acquire_ctx);
> diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c
> index 09e48c794e1b0..57d87fa98d671 100644
> --- a/drivers/accel/amdxdna/amdxdna_gem.c
> +++ b/drivers/accel/amdxdna/amdxdna_gem.c
> @@ -232,37 +232,46 @@ static bool amdxdna_hmm_invalidate(struct mmu_interval_notifier *mni,
>   {
>   	struct amdxdna_umap *mapp = container_of(mni, struct amdxdna_umap, notifier);
>   	struct amdxdna_gem_obj *abo = mapp->abo;
> -	struct amdxdna_dev *xdna;
> +	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
> +	bool unreg;
>   
>   	if (!mmu_notifier_range_blockable(range))
>   		return false;
>   
> -	if (mapp->unmapped)
> -		return true;
> -
> -	xdna = to_xdna_dev(to_gobj(abo)->dev);
>   	XDNA_DBG(xdna, "Invalidating range 0x%lx, 0x%lx, type %d",
> -		 mapp->range.start, mapp->range.end, abo->type);
> +		 mapp->start, mapp->end, abo->type);
>   
> -	down_write(&xdna->notifier_lock);
> -	abo->mem.map_invalid = true;
> -	mapp->invalid = true;
> -	mmu_interval_set_seq(&mapp->notifier, cur_seq);
> -	up_write(&xdna->notifier_lock);
> +	down_write(&xdna->gpusvm.notifier_lock);
> +	mmu_interval_set_seq(mni, cur_seq);
> +	/*
> +	 * Flag the mapping as gone before the wait below, so that a submission
> +	 * racing with the unmap does not try to fault it in again.
> +	 */
> +	unreg = !mapp->pages.flags.unmapped && range->event == MMU_NOTIFY_UNMAP;
> +	if (unreg)
> +		drm_gpusvm_pages_set_unmapped(&xdna->gpusvm, &mapp->pages, 1);
> +	up_write(&xdna->gpusvm.notifier_lock);
>   
>   	/*
>   	 * The pages must not be released while the device may still access
>   	 * them, so this wait must be neither interruptible nor time limited.
> +	 *
> +	 * Every invalidation of the mapping has to wait, including the ones
> +	 * which find it already flagged: the flag is set above before the wait,
> +	 * so it does not tell whether the jobs of a concurrent invalidation
> +	 * have finished.
>   	 */
>   	dma_resv_wait_timeout(to_gobj(abo)->resv, DMA_RESV_USAGE_BOOKKEEP,
>   			      false, MAX_SCHEDULE_TIMEOUT);
>   
> -	if (range->event == MMU_NOTIFY_UNMAP) {
> -		down_write(&xdna->notifier_lock);
> -		mapp->unmapped = true;
> +	/*
> +	 * Queue the unregistration only once the jobs are done: it drops the
> +	 * last reference of the mapping, and mmu_interval_notifier_remove()
> +	 * waits for this callback, which would pin the single worker of the
> +	 * ordered WQ_MEM_RECLAIM notifier_wq for as long as a job runs.
> +	 */
> +	if (unreg)
>   		queue_work(xdna->notifier_wq, &abo->hmm_unreg_work);
> -		up_write(&xdna->notifier_lock);
> -	}
>   
>   	return true;
>   }
> @@ -271,12 +280,25 @@ static const struct mmu_interval_notifier_ops amdxdna_hmm_ops = {
>   	.invalidate = amdxdna_hmm_invalidate,
>   };
>   
> -static inline bool compare_range(struct amdxdna_umap *mapp,
> -				 struct mm_struct *mm,
> -				 unsigned long start, unsigned long end)
> +static bool amdxdna_umap_match(struct amdxdna_umap *mapp,
> +			       struct mm_struct *mm,
> +			       unsigned long start, unsigned long end)
>   {
> -	return (!mapp->unmapped && mapp->notifier.mm == mm &&
> -		mapp->range.start == start && mapp->range.end == end);
> +	return !mapp->pages.flags.unmapped && mapp->notifier.mm == mm &&
> +		mapp->start == start && mapp->end == end;
> +}
> +
> +static bool amdxdna_umap_stale(struct drm_gpusvm *gpusvm,
> +			       struct amdxdna_umap *mapp)
> +{
> +	/*
> +	 * The pages of a BO whose mappings mmap() populated upfront are never
> +	 * faulted in. Neither are the ones of a mapping which is gone: device
> +	 * accesses through it are rejected by the IOMMU, so there is nothing
> +	 * to fault in for it.
> +	 */
> +	return mapp->abo->mem.map_invalid && !mapp->pages.flags.unmapped &&
> +		drm_gpusvm_pages_read_retry(gpusvm, &mapp->pages, &mapp->notifier);
>   }
>   
>   static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo,
> @@ -285,15 +307,28 @@ static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo,
>   	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
>   	struct amdxdna_umap *mapp;
>   
> -	down_write(&xdna->notifier_lock);
> +	down_write(&xdna->gpusvm.notifier_lock);
>   	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
> -		if (!compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end))
> +		if (!amdxdna_umap_match(mapp, vma->vm_mm, vma->vm_start, vma->vm_end))
>   			continue;
>   
> -		mapp->unmapped = true;
> +		drm_gpusvm_pages_set_unmapped(&xdna->gpusvm, &mapp->pages, 1);
>   		queue_work(xdna->notifier_wq, &abo->hmm_unreg_work);
>   	}
> -	up_write(&xdna->notifier_lock);
> +	up_write(&xdna->gpusvm.notifier_lock);
> +}
> +
> +static void amdxdna_umap_release(struct kref *ref)
> +{
> +	struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt);
> +
> +	mmu_interval_notifier_remove(&mapp->notifier);
> +	kfree(mapp);
> +}
> +
> +static void amdxdna_umap_put(struct amdxdna_umap *mapp)
> +{
> +	kref_put(&mapp->refcnt, amdxdna_umap_release);
>   }
>   
>   static void amdxdna_hmm_unreg_umaps(struct amdxdna_gem_obj *abo, bool force)
> @@ -302,121 +337,119 @@ static void amdxdna_hmm_unreg_umaps(struct amdxdna_gem_obj *abo, bool force)
>   	struct amdxdna_umap *mapp, *tmp;
>   	LIST_HEAD(dead);
>   
> -	down_write(&xdna->notifier_lock);
> +	down_write(&xdna->gpusvm.notifier_lock);
>   	list_for_each_entry_safe(mapp, tmp, &abo->mem.umap_list, node) {
> -		if (!force && !mapp->unmapped)
> +		if (!force && !mapp->pages.flags.unmapped)
>   			continue;
>   
> -		mapp->unmapped = true;
> +		drm_gpusvm_pages_set_unmapped(&xdna->gpusvm, &mapp->pages, 1);
>   		list_move(&mapp->node, &dead);
>   	}
>   	if (list_empty(&abo->mem.umap_list))
>   		abo->mem.uva = AMDXDNA_INVALID_ADDR;
> -	up_write(&xdna->notifier_lock);
> +	up_write(&xdna->gpusvm.notifier_lock);
>   
>   	list_for_each_entry_safe(mapp, tmp, &dead, node)
>   		amdxdna_umap_put(mapp);
>   }
>   
> -static void amdxdna_umap_release(struct kref *ref)
> +static void amdxdna_hmm_unreg_work(struct work_struct *work)
>   {
> -	struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt);
> -
> -	mmu_interval_notifier_remove(&mapp->notifier);
> -	mutex_destroy(&mapp->lock);
> -	kvfree(mapp->range.hmm_pfns);
> -	kfree(mapp);
> -}
> +	struct amdxdna_gem_obj *abo = container_of(work, struct amdxdna_gem_obj,
> +						   hmm_unreg_work);
>   
> -void amdxdna_umap_put(struct amdxdna_umap *mapp)
> -{
> -	kref_put(&mapp->refcnt, amdxdna_umap_release);
> +	amdxdna_hmm_unreg_umaps(abo, false);
>   }
>   
> -int amdxdna_populate_range(struct amdxdna_gem_obj *abo)
> +/**
> + * amdxdna_gem_umap_populate() - Fault in the invalidated user mappings of a BO
> + * @abo: The BO.
> + * @deadline: Jiffies after which to give up.
> + *
> + * The device accesses user mappings through the PASID and cannot take page
> + * faults, so the CPU pages of every mapping of @abo invalidated since it was
> + * last faulted are faulted in again here. This is only a snapshot, the caller
> + * must check amdxdna_gem_umap_stale() under the notifier lock before using
> + * the mappings and retry if it fails.
> + *
> + * Two hardware contexts may fault the same mapping at the same time. That is
> + * left unserialized: with &drm_gpusvm_ctx.no_dma_map the only state recorded
> + * for a mapping is its notifier sequence, and the loser of such a race can
> + * only store an older sequence, which makes amdxdna_gem_umap_stale() report
> + * stale and the caller fault again.
> + *
> + * Return: 0 on success, -ETIME if @deadline passed, negative error code on
> + * other failures.
> + */
> +int amdxdna_gem_umap_populate(struct amdxdna_gem_obj *abo,
> +			      unsigned long deadline)
>   {
>   	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
> +	struct drm_gpusvm *gpusvm = &xdna->gpusvm;
>   	struct amdxdna_umap *mapp;
> -	unsigned long timeout;
> -	struct mm_struct *mm;
> -	bool found;
> +	bool found, unmapped;
>   	int ret;
>   
> -	timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> -again:
> -	found = false;
> -	down_write(&xdna->notifier_lock);
> -	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
> -		/*
> -		 * Skip entries that have already been unmapped.
> -		 *
> -		 * If userspace unmaps the address and later submits I/O using
> -		 * it, the IOMMU will reject the access and report a fault.
> -		 * Ignore such entries here.
> -		 */
> -		if (mapp->unmapped)
> -			continue;
> +	for (;;) {
> +		struct drm_gpusvm_ctx ctx = { .no_dma_map = 1 };
>   
> -		if (mapp->invalid && kref_get_unless_zero(&mapp->refcnt)) {
> -			found = true;
> -			break;
> +		found = false;
> +		drm_gpusvm_notifier_lock(gpusvm);
> +		list_for_each_entry(mapp, &abo->mem.umap_list, node) {
> +			if (amdxdna_umap_stale(gpusvm, mapp) &&
> +			    kref_get_unless_zero(&mapp->refcnt)) {
> +				found = true;
> +				break;
> +			}
>   		}
> -	}
> -
> -	if (!found) {
> -		/*
> -		 * This also covers the case where all mappings have been
> -		 * removed. There are no invalid mappings left to process.
> -		 * Any subsequent I/O using the unmapped address will be
> -		 * rejected by the IOMMU.
> -		 */
> -		abo->mem.map_invalid = false;
> -		up_write(&xdna->notifier_lock);
> -		return 0;
> -	}
> -
> -	up_write(&xdna->notifier_lock);
> -
> -	mm = mapp->notifier.mm;
> -	if (!mmget_not_zero(mm)) {
> -		amdxdna_umap_put(mapp);
> -		return -EFAULT;
> -	}
> +		drm_gpusvm_notifier_unlock(gpusvm);
> +		if (!found)
> +			return 0;
>   
> -	mutex_lock(&mapp->lock);
> -	ret = hmm_range_fault_unlocked_timeout(&mapp->range, timeout);
> -	if (ret) {
> -		mutex_unlock(&mapp->lock);
> -		goto put_mm;
> -	}
> +		if (time_after(jiffies, deadline)) {
> +			amdxdna_umap_put(mapp);
> +			return -ETIME;
> +		}
>   
> -	down_write(&xdna->notifier_lock);
> -	if (mmu_interval_read_retry(&mapp->notifier, mapp->range.notifier_seq)) {
> -		up_write(&xdna->notifier_lock);
> -		mutex_unlock(&mapp->lock);
> +		ctx.read_only = mapp->read_only;
> +		ret = drm_gpusvm_get_pages(gpusvm, &mapp->pages, 1,
> +					   mapp->notifier.mm, &mapp->notifier,
> +					   mapp->start, mapp->end, &ctx);
> +		if (ret == -EFAULT) {
> +			drm_gpusvm_notifier_lock(gpusvm);
> +			unmapped = mapp->pages.flags.unmapped;
> +			drm_gpusvm_notifier_unlock(gpusvm);
> +			if (unmapped)
> +				ret = 0;
> +		}
>   		amdxdna_umap_put(mapp);
> -		mmput(mm);
> -		goto again;
> +		if (ret)
> +			return ret == -EBUSY ? -ETIME : ret;
>   	}
> -	mapp->invalid = false;
> -	up_write(&xdna->notifier_lock);
> -	mutex_unlock(&mapp->lock);
> -	amdxdna_umap_put(mapp);
> -	mmput(mm);
> -	goto again;
> -
> -put_mm:
> -	amdxdna_umap_put(mapp);
> -	mmput(mm);
> -	return ret == -EBUSY ? -ETIME : ret;
>   }
>   
> -static void amdxdna_hmm_unreg_work(struct work_struct *work)
> +/**
> + * amdxdna_gem_umap_stale() - Check whether a BO needs its user mappings faulted
> + * @abo: The BO.
> + *
> + * Must be called with the notifier lock held, which must be kept held until
> + * the fences of the jobs using @abo are published in its reservation object.
> + *
> + * Return: True if amdxdna_gem_umap_populate() must be called for @abo.
> + */
> +bool amdxdna_gem_umap_stale(struct amdxdna_gem_obj *abo)
>   {
> -	struct amdxdna_gem_obj *abo = container_of(work, struct amdxdna_gem_obj,
> -						   hmm_unreg_work);
> +	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
> +	struct amdxdna_umap *mapp;
>   
> -	amdxdna_hmm_unreg_umaps(abo, false);
> +	lockdep_assert_held(&xdna->gpusvm.notifier_lock);
> +
> +	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
> +		if (amdxdna_umap_stale(&xdna->gpusvm, mapp))
> +			return true;
> +	}
> +
> +	return false;
>   }
>   
>   static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
> @@ -426,7 +459,6 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
>   	unsigned long len = vma->vm_end - vma->vm_start;
>   	unsigned long addr = vma->vm_start;
>   	struct amdxdna_umap *mapp;
> -	unsigned long nr_pages;
>   	int ret;
>   
>   	/*
> @@ -441,38 +473,34 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
>   		return 0;
>   	}
>   
> -	down_read(&xdna->notifier_lock);
> +	down_read(&xdna->gpusvm.notifier_lock);
>   	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
> -		if (compare_range(mapp, current->mm, addr, addr + len)) {
> -			up_read(&xdna->notifier_lock);
> +		if (amdxdna_umap_match(mapp, current->mm, addr, addr + len)) {
> +			up_read(&xdna->gpusvm.notifier_lock);
>   			return 0;
>   		}
>   	}
> -	up_read(&xdna->notifier_lock);
> +	up_read(&xdna->gpusvm.notifier_lock);
>   
>   	mapp = kzalloc_obj(*mapp);
>   	if (!mapp)
>   		return -ENOMEM;
>   
> -	nr_pages = (PAGE_ALIGN(addr + len) - (addr & PAGE_MASK)) >> PAGE_SHIFT;
> -	mapp->range.hmm_pfns = kvzalloc_objs(*mapp->range.hmm_pfns, nr_pages);
> -	if (!mapp->range.hmm_pfns) {
> -		ret = -ENOMEM;
> -		goto free_map;
> -	}
> +	/*
> +	 * Never faulted, so stale until the first amdxdna_gem_umap_populate(),
> +	 * should the pages of the BO have to be faulted in at all.
> +	 */
> +	drm_gpusvm_init_pages(&mapp->pages, &xdna->ddev);
> +	mapp->start = addr;
> +	mapp->end = addr + len;
>   
> -	mapp->range.notifier = &mapp->notifier;
> -	mapp->range.start = vma->vm_start;
> -	mapp->range.end = vma->vm_end;
>   	/*
>   	 * Access permissions are fixed at mmap() time. Changing them later
> -	 * with mprotect() is not supported: the range keeps requesting the
> +	 * with mprotect() is not supported: the mapping keeps requesting the
>   	 * original permissions, so the application may see a fault failure
>   	 * or an IOMMU fault.
>   	 */
> -	mapp->range.default_flags = HMM_PFN_REQ_FAULT;
> -	if (vma->vm_flags & VM_WRITE)
> -		mapp->range.default_flags |= HMM_PFN_REQ_WRITE;
> +	mapp->read_only = !(vma->vm_flags & VM_WRITE);
>   	mapp->abo = abo;
>   	kref_init(&mapp->refcnt);
>   
> @@ -483,23 +511,17 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo,
>   						  &amdxdna_hmm_ops);
>   	if (ret) {
>   		XDNA_ERR(xdna, "Insert mmu notifier failed, ret %d", ret);
> -		goto free_pfns;
> +		kfree(mapp);
> +		return ret;
>   	}
>   
> -	mutex_init(&mapp->lock);
> -	down_write(&xdna->notifier_lock);
> +	down_write(&xdna->gpusvm.notifier_lock);
>   	if (list_empty(&abo->mem.umap_list))
>   		abo->mem.uva = addr;
>   	list_add_tail(&mapp->node, &abo->mem.umap_list);
> -	up_write(&xdna->notifier_lock);
> +	up_write(&xdna->gpusvm.notifier_lock);
>   
>   	return 0;
> -
> -free_pfns:
> -	kvfree(mapp->range.hmm_pfns);
> -free_map:
> -	kfree(mapp);
> -	return ret;
>   }
>   
>   static struct amdxdna_gem_obj *
> @@ -547,21 +569,13 @@ static void amdxdna_gem_dev_obj_free(struct drm_gem_object *gobj)
>   	amdxdna_gem_destroy_obj(abo);
>   }
>   
> -static void amdxdna_mark_mapp_invalid(struct amdxdna_gem_obj *abo,
> -				      struct vm_area_struct *vma)
> +static void amdxdna_mark_map_invalid(struct amdxdna_gem_obj *abo)
>   {
>   	struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev);
> -	struct amdxdna_umap *mapp;
>   
> -	down_write(&xdna->notifier_lock);
> +	down_write(&xdna->gpusvm.notifier_lock);
>   	abo->mem.map_invalid = true;
> -	list_for_each_entry(mapp, &abo->mem.umap_list, node) {
> -		if (compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end)) {
> -			mapp->invalid = true;
> -			break;
> -		}
> -	}
> -	up_write(&xdna->notifier_lock);
> +	up_write(&xdna->gpusvm.notifier_lock);
>   }
>   
>   static int amdxdna_remap_pages(struct amdxdna_gem_obj *abo,
> @@ -628,7 +642,7 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo,
>   		}
>   
>   		if (!(vma->vm_flags & VM_PFNMAP))
> -			amdxdna_mark_mapp_invalid(abo, vma);
> +			amdxdna_mark_map_invalid(abo);
>   
>   		/* Drop the reference drm_gem_mmap_obj() acquired.*/
>   		drm_gem_object_put(to_gobj(abo));
> diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h
> index 5c429869992e8..2deead107d6b1 100644
> --- a/drivers/accel/amdxdna/amdxdna_gem.h
> +++ b/drivers/accel/amdxdna/amdxdna_gem.h
> @@ -7,19 +7,19 @@
>   #define _AMDXDNA_GEM_H_
>   
>   #include <drm/drm_gem_shmem_helper.h>
> -#include <linux/hmm.h>
> +#include <drm/drm_gpusvm.h>
>   #include <linux/iova.h>
>   #include "amdxdna_pci_drv.h"
>   
>   struct amdxdna_umap {
>   	struct mmu_interval_notifier	notifier;
> -	struct hmm_range		range;
> +	struct drm_gpusvm_pages		pages;
> +	unsigned long			start;
> +	unsigned long			end;
> +	bool				read_only;
>   	struct amdxdna_gem_obj		*abo;
>   	struct list_head		node;
>   	struct kref			refcnt;
> -	bool				invalid;
> -	bool				unmapped;
> -	struct mutex			lock; /* protect range */
>   };
>   
>   struct amdxdna_mem {
> @@ -27,10 +27,16 @@ struct amdxdna_mem {
>   	u64				dma_addr;
>   	size_t				size;
>   	struct list_head		umap_list;
> +	/*
> +	 * True, if the pages of the user mappings have to be faulted in before
> +	 * the device accesses them. A mapping which mmap() populated upfront,
> +	 * which is a VM_PFNMAP one, must not be faulted in: hmm_range_fault()
> +	 * rejects such a VMA.
> +	 */
>   	bool				map_invalid;
>   	/*
>   	 * Cache the first mmap uva as PASID addr, which can be accessed by driver
> -	 * without taking notifier_lock.
> +	 * without taking the gpusvm notifier lock.
>   	 */
>   	u64				uva;
>   };
> @@ -103,8 +109,9 @@ static inline u64 amdxdna_obj_dma_addr(struct amdxdna_gem_obj *abo)
>   		abo->mem.dma_addr : amdxdna_gem_uva(abo);
>   }
>   
> -void amdxdna_umap_put(struct amdxdna_umap *mapp);
> -int amdxdna_populate_range(struct amdxdna_gem_obj *abo);
> +int amdxdna_gem_umap_populate(struct amdxdna_gem_obj *abo,
> +			      unsigned long deadline);
> +bool amdxdna_gem_umap_stale(struct amdxdna_gem_obj *abo);
>   void amdxdna_gem_heap_free(struct amdxdna_client *client, struct amdxdna_gem_obj *abo);
>   
>   struct drm_gem_object *
> diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c
> index f233eb70a2957..18bd7bdd12969 100644
> --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c
> +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c
> @@ -363,6 +363,7 @@ static void amdxdna_xdna_drm_release(struct drm_device *drm, void *res)
>   	struct amdxdna_dev *xdna = res;
>   
>   	amdxdna_carveout_fini(xdna);
> +	drm_gpusvm_fini(&xdna->gpusvm);
>   }
>   
>   static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> @@ -389,18 +390,18 @@ static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>   	if (ret)
>   		return ret;
>   
> -	init_rwsem(&xdna->notifier_lock);
> +	ret = drm_gpusvm_init(&xdna->gpusvm, "amdxdna", NULL, 0, 0, 0, NULL,
> +			      NULL, 0);
> +	if (ret)
> +		return ret;
> +
>   	INIT_LIST_HEAD(&xdna->client_list);
>   	pci_set_drvdata(pdev, xdna);
>   
>   	ret = drmm_add_action(ddev, amdxdna_xdna_drm_release, xdna);
> -	if (ret)
> +	if (ret) {
> +		drm_gpusvm_fini(&xdna->gpusvm);
>   		return ret;
> -
> -	if (IS_ENABLED(CONFIG_LOCKDEP)) {
> -		fs_reclaim_acquire(GFP_KERNEL);
> -		might_lock(&xdna->notifier_lock);
> -		fs_reclaim_release(GFP_KERNEL);
>   	}
>   
>   	ret = amdxdna_iommu_init(xdna);
> diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.h b/drivers/accel/amdxdna/amdxdna_pci_drv.h
> index 27593190dcf67..7663b883d4f8b 100644
> --- a/drivers/accel/amdxdna/amdxdna_pci_drv.h
> +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.h
> @@ -7,6 +7,7 @@
>   #define _AMDXDNA_PCI_DRV_H_
>   
>   #include <drm/amdxdna_accel.h>
> +#include <drm/drm_gpusvm.h>
>   #include <drm/drm_mm.h>
>   #include <drm/drm_print.h>
>   #include <linux/iommu.h>
> @@ -118,7 +119,7 @@ struct amdxdna_dev {
>   	struct list_head		client_list;
>   	struct mutex			client_lock; /* client_list */
>   	struct amdxdna_drm_query_firmware_version fw_ver;
> -	struct rw_semaphore		notifier_lock; /* for mmu notifier*/
> +	struct drm_gpusvm		gpusvm; /* notifier lock for user mappings */
>   	struct workqueue_struct		*notifier_wq;
>   
>   	struct iommu_group		*group;


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
  2026-11-09  8:56 ` [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Huang, Honglei1
@ 2026-11-09 19:53   ` Lizhi Hou
  2026-11-09 22:35     ` Matthew Brost
  0 siblings, 1 reply; 7+ messages in thread
From: Lizhi Hou @ 2026-11-09 19:53 UTC (permalink / raw)
  To: Huang, Honglei1, Matthew Brost
  Cc: linux-kernel, Santan, Sonal, ogabbay, quic_jhugo, dri-devel,
	Limonciello, Mario, karol.wachowski, Zhen, Max, thomas.hellstrom,
	himal.prasad.ghimiray

Hi Matthew,


How do we converge on this?  Both 2 patches are reviewed, is it good to 
merge them to drm-misc-next?

Honglei, Thanks a lot for reviewing the code.


Lizhi

On 11/9/26 00:56, Huang, Honglei1 wrote:
> AMD General
>
> Reviewed-by: Honglei Huang <honghuan@amd.com>
>
> -----Original Message-----
> From: Lizhi Hou <lizhi.hou@amd.com>
> Sent: Saturday, November 7, 2026 2:42 AM
> To: ogabbay@kernel.org; quic_jhugo@quicinc.com; dri-devel@lists.freedesktop.org; Limonciello, Mario <Mario.Limonciello@amd.com>; karol.wachowski@linux.intel.com; Zhen, Max <Max.Zhen@amd.com>; thomas.hellstrom@linux.intel.com; himal.prasad.ghimiray@intel.com; Huang, Honglei1 <Honglei1.Huang@amd.com>
> Cc: Matthew Brost <matthew.brost@intel.com>; linux-kernel@vger.kernel.org; Santan, Sonal <sonal.santan@amd.com>; Hou, Lizhi <lizhi.hou@amd.com>
> Subject: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
>
> From: Matthew Brost <matthew.brost@intel.com>
>
> Drivers which only use the drm_gpusvm_pages API (no drm_gpusvm_range) have no way to mark pages unmapped from their MMU notifier, as
> drm_gpusvm_range_set_unmapped() requires a range. They also have to open code the mmu_interval_read_retry() check on the recorded notifier_seq to commit pages faulted with &drm_gpusvm_ctx.no_dma_map.
>
> Split the pages part of drm_gpusvm_range_set_unmapped() into drm_gpusvm_pages_set_unmapped(), and add drm_gpusvm_pages_read_retry() which performs the commit side check with a lockdep assert on the notifier lock.
>
> Assisted-by: LLM
> Signed-off-by: Matthew Brost <matthew.brost@intel.com>
> Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
> ---
>   drivers/gpu/drm/drm_gpusvm.c | 49 +++++++++++++++++++++++++-----------
>   include/drm/drm_gpusvm.h     | 29 +++++++++++++++++++++
>   2 files changed, 64 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 03dbe304e4b8f..63a26919f59db 100644
> --- a/drivers/gpu/drm/drm_gpusvm.c
> +++ b/drivers/gpu/drm/drm_gpusvm.c
> @@ -1761,9 +1761,8 @@ static int drm_gpusvm_dma_map_pages(struct drm_gpusvm *gpusvm,
>    *
>    * With &drm_gpusvm_ctx.no_dma_map no mapping state is recorded, so
>    * drm_gpusvm_pages_valid() never returns true and success is only a snapshot:
> - * the caller must recheck mmu_interval_read_retry() against the recorded
> - * &drm_gpusvm_pages.notifier_seq under the notifier lock, and hold it until
> - * its work is visible to invalidation.
> + * the caller must recheck drm_gpusvm_pages_read_retry() under the
> + notifier
> + * lock, and hold it until its work is visible to invalidation.
>    *
>    * Return: 0 on success, negative error code on failure.
>    */
> @@ -2021,6 +2020,38 @@ bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start,  }  EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping);
>
> +/**
> + * drm_gpusvm_pages_set_unmapped() - Mark GPU SVM pages as unmapped
> + * @gpusvm: Pointer to the GPU SVM structure.
> + * @pages: Pointer to the GPU SVM pages structure(s).
> + * @pages_count: Number of GPU SVM pages structure(s) passed in.
> + *
> + * Mark @pages as unmapped, after which drm_gpusvm_get_pages() fails
> +with
> + * -EFAULT for them. Intended to be called from the driver's MMU
> +notifier on
> + * MMU_NOTIFY_UNMAP, and usable without a &drm_gpusvm_range by drivers
> +that
> + * only use the drm_gpusvm_pages API. The caller must hold the notifier
> +lock
> + * in write mode.
> + */
> +void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
> +                                  struct drm_gpusvm_pages *pages,
> +                                  unsigned int pages_count)
> +{
> +       unsigned int i;
> +
> +       lockdep_assert_held_write(&gpusvm->notifier_lock);
> +
> +       for (i = 0; i < pages_count; ++i) {
> +               struct drm_gpusvm_pages_flags flags = {
> +                       .__flags = pages[i].flags.__flags,
> +               };
> +
> +               flags.unmapped = true;
> +               /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
> +               WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
> +       }
> +}
> +EXPORT_SYMBOL_GPL(drm_gpusvm_pages_set_unmapped);
> +
>   /**
>    * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped
>    * @range: Pointer to the GPU SVM range structure.
> @@ -2039,20 +2070,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
>          struct drm_gpusvm_range_flags range_flags = {
>                  .__flags = range->flags.__flags,
>          };
> -       unsigned int i;
>
> -       lockdep_assert_held_write(&range->gpusvm->notifier_lock);
> +       drm_gpusvm_pages_set_unmapped(range->gpusvm, pages, pages_count);
>
>          range_flags.unmapped = true;
> -       for (i = 0; i < pages_count; ++i) {
> -               struct drm_gpusvm_pages_flags flags = {
> -                       .__flags = pages[i].flags.__flags,
> -               };
> -
> -               flags.unmapped = true;
> -               /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
> -               WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
> -       }
>          if (drm_gpusvm_range_start(range) < mmu_range->start ||
>              drm_gpusvm_range_end(range) > mmu_range->end)
>                  range_flags.partial_unmap = true;
> diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 4d31c6bba7455..b43432af56377 100644
> --- a/include/drm/drm_gpusvm.h
> +++ b/include/drm/drm_gpusvm.h
> @@ -349,6 +349,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
>                                     unsigned int pages_count,
>                                     const struct mmu_notifier_range *mmu_range);
>
> +void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
> +                                  struct drm_gpusvm_pages *pages,
> +                                  unsigned int pages_count);
> +
>   int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
>                           struct drm_gpusvm_pages *svm_pages,
>                           unsigned int num_pages,
> @@ -383,6 +387,31 @@ static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages,
>          svm_pages->notifier_seq = LONG_MAX;
>   }
>
> +/**
> + * drm_gpusvm_pages_read_retry() - Check whether GPU SVM pages went
> +stale
> + * @gpusvm: Pointer to the GPU SVM structure.
> + * @svm_pages: Pointer to the drm_gpusvm_pages.
> + * @notifier: The notifier passed to drm_gpusvm_get_pages() for @svm_pages.
> + *
> + * Check whether @notifier was invalidated since the last successful
> + * drm_gpusvm_get_pages() for @svm_pages. This is the commit side check
> +for
> + * pages faulted with &drm_gpusvm_ctx.no_dma_map, where no mapping
> +state is
> + * tracked. Pages that were never faulted always report as stale. The
> +caller
> + * must hold the notifier lock and keep holding it until its use of the
> +pages
> + * is visible to the invalidation path.
> + *
> + * Return: True if the pages must be faulted again, false otherwise.
> + */
> +static inline bool
> +drm_gpusvm_pages_read_retry(struct drm_gpusvm *gpusvm,
> +                           struct drm_gpusvm_pages *svm_pages,
> +                           struct mmu_interval_notifier *notifier) {
> +       lockdep_assert_held(&gpusvm->notifier_lock);
> +
> +       return mmu_interval_read_retry(notifier, svm_pages->notifier_seq); }
> +
>   /**
>    * drm_gpusvm_pages_first_dma() - Resolve the device address array
>    * @svm_pages: Pointer to the drm_gpusvm_pages.
>
> base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
> --
> 2.34.1
>

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
  2026-11-06 18:42 [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Lizhi Hou
  2026-11-06 18:42 ` [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking Lizhi Hou
  2026-11-09  8:56 ` [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Huang, Honglei1
@ 2026-11-09 22:34 ` Matthew Brost
  2 siblings, 0 replies; 7+ messages in thread
From: Matthew Brost @ 2026-11-09 22:34 UTC (permalink / raw)
  To: Lizhi Hou
  Cc: ogabbay, quic_jhugo, dri-devel, mario.limonciello,
	karol.wachowski, max.zhen, thomas.hellstrom,
	himal.prasad.ghimiray, honghuan, linux-kernel, sonal.santan

On Fri, Nov 06, 2026 at 10:42:16AM -0800, Lizhi Hou wrote:
> From: Matthew Brost <matthew.brost@intel.com>
> 
> Drivers which only use the drm_gpusvm_pages API (no drm_gpusvm_range)
> have no way to mark pages unmapped from their MMU notifier, as
> drm_gpusvm_range_set_unmapped() requires a range. They also have to open
> code the mmu_interval_read_retry() check on the recorded notifier_seq
> to commit pages faulted with &drm_gpusvm_ctx.no_dma_map.
> 
> Split the pages part of drm_gpusvm_range_set_unmapped() into
> drm_gpusvm_pages_set_unmapped(), and add drm_gpusvm_pages_read_retry()
> which performs the commit side check with a lockdep assert on the
> notifier lock.
> 
> Assisted-by: LLM
> Signed-off-by: Matthew Brost <matthew.brost@intel.com>
> Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>

Reviewed-by: Matthew Brost <matthew.brost@intel.com>

> ---
>  drivers/gpu/drm/drm_gpusvm.c | 49 +++++++++++++++++++++++++-----------
>  include/drm/drm_gpusvm.h     | 29 +++++++++++++++++++++
>  2 files changed, 64 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c
> index 03dbe304e4b8f..63a26919f59db 100644
> --- a/drivers/gpu/drm/drm_gpusvm.c
> +++ b/drivers/gpu/drm/drm_gpusvm.c
> @@ -1761,9 +1761,8 @@ static int drm_gpusvm_dma_map_pages(struct drm_gpusvm *gpusvm,
>   *
>   * With &drm_gpusvm_ctx.no_dma_map no mapping state is recorded, so
>   * drm_gpusvm_pages_valid() never returns true and success is only a snapshot:
> - * the caller must recheck mmu_interval_read_retry() against the recorded
> - * &drm_gpusvm_pages.notifier_seq under the notifier lock, and hold it until
> - * its work is visible to invalidation.
> + * the caller must recheck drm_gpusvm_pages_read_retry() under the notifier
> + * lock, and hold it until its work is visible to invalidation.
>   *
>   * Return: 0 on success, negative error code on failure.
>   */
> @@ -2021,6 +2020,38 @@ bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping);
>  
> +/**
> + * drm_gpusvm_pages_set_unmapped() - Mark GPU SVM pages as unmapped
> + * @gpusvm: Pointer to the GPU SVM structure.
> + * @pages: Pointer to the GPU SVM pages structure(s).
> + * @pages_count: Number of GPU SVM pages structure(s) passed in.
> + *
> + * Mark @pages as unmapped, after which drm_gpusvm_get_pages() fails with
> + * -EFAULT for them. Intended to be called from the driver's MMU notifier on
> + * MMU_NOTIFY_UNMAP, and usable without a &drm_gpusvm_range by drivers that
> + * only use the drm_gpusvm_pages API. The caller must hold the notifier lock
> + * in write mode.
> + */
> +void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
> +				   struct drm_gpusvm_pages *pages,
> +				   unsigned int pages_count)
> +{
> +	unsigned int i;
> +
> +	lockdep_assert_held_write(&gpusvm->notifier_lock);
> +
> +	for (i = 0; i < pages_count; ++i) {
> +		struct drm_gpusvm_pages_flags flags = {
> +			.__flags = pages[i].flags.__flags,
> +		};
> +
> +		flags.unmapped = true;
> +		/* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
> +		WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
> +	}
> +}
> +EXPORT_SYMBOL_GPL(drm_gpusvm_pages_set_unmapped);
> +
>  /**
>   * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped
>   * @range: Pointer to the GPU SVM range structure.
> @@ -2039,20 +2070,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
>  	struct drm_gpusvm_range_flags range_flags = {
>  		.__flags = range->flags.__flags,
>  	};
> -	unsigned int i;
>  
> -	lockdep_assert_held_write(&range->gpusvm->notifier_lock);
> +	drm_gpusvm_pages_set_unmapped(range->gpusvm, pages, pages_count);
>  
>  	range_flags.unmapped = true;
> -	for (i = 0; i < pages_count; ++i) {
> -		struct drm_gpusvm_pages_flags flags = {
> -			.__flags = pages[i].flags.__flags,
> -		};
> -
> -		flags.unmapped = true;
> -		/* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
> -		WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
> -	}
>  	if (drm_gpusvm_range_start(range) < mmu_range->start ||
>  	    drm_gpusvm_range_end(range) > mmu_range->end)
>  		range_flags.partial_unmap = true;
> diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h
> index 4d31c6bba7455..b43432af56377 100644
> --- a/include/drm/drm_gpusvm.h
> +++ b/include/drm/drm_gpusvm.h
> @@ -349,6 +349,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
>  				   unsigned int pages_count,
>  				   const struct mmu_notifier_range *mmu_range);
>  
> +void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
> +				   struct drm_gpusvm_pages *pages,
> +				   unsigned int pages_count);
> +
>  int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
>  			 struct drm_gpusvm_pages *svm_pages,
>  			 unsigned int num_pages,
> @@ -383,6 +387,31 @@ static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages,
>  	svm_pages->notifier_seq = LONG_MAX;
>  }
>  
> +/**
> + * drm_gpusvm_pages_read_retry() - Check whether GPU SVM pages went stale
> + * @gpusvm: Pointer to the GPU SVM structure.
> + * @svm_pages: Pointer to the drm_gpusvm_pages.
> + * @notifier: The notifier passed to drm_gpusvm_get_pages() for @svm_pages.
> + *
> + * Check whether @notifier was invalidated since the last successful
> + * drm_gpusvm_get_pages() for @svm_pages. This is the commit side check for
> + * pages faulted with &drm_gpusvm_ctx.no_dma_map, where no mapping state is
> + * tracked. Pages that were never faulted always report as stale. The caller
> + * must hold the notifier lock and keep holding it until its use of the pages
> + * is visible to the invalidation path.
> + *
> + * Return: True if the pages must be faulted again, false otherwise.
> + */
> +static inline bool
> +drm_gpusvm_pages_read_retry(struct drm_gpusvm *gpusvm,
> +			    struct drm_gpusvm_pages *svm_pages,
> +			    struct mmu_interval_notifier *notifier)
> +{
> +	lockdep_assert_held(&gpusvm->notifier_lock);
> +
> +	return mmu_interval_read_retry(notifier, svm_pages->notifier_seq);
> +}
> +
>  /**
>   * drm_gpusvm_pages_first_dma() - Resolve the device address array
>   * @svm_pages: Pointer to the drm_gpusvm_pages.
> 
> base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
> -- 
> 2.34.1
> 

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
  2026-11-09 19:53   ` Lizhi Hou
@ 2026-11-09 22:35     ` Matthew Brost
  0 siblings, 0 replies; 7+ messages in thread
From: Matthew Brost @ 2026-11-09 22:35 UTC (permalink / raw)
  To: Lizhi Hou
  Cc: Huang, Honglei1, linux-kernel, Santan, Sonal, ogabbay,
	quic_jhugo, dri-devel, Limonciello, Mario, karol.wachowski, Zhen,
	Max, thomas.hellstrom, himal.prasad.ghimiray

On Mon, Nov 09, 2026 at 11:53:37AM -0800, Lizhi Hou wrote:
> Hi Matthew,
> 
> 
> How do we converge on this?  Both 2 patches are reviewed, is it good to
> merge them to drm-misc-next?
> 

I also gave an RB, feel free to merge or if you need help merging, let
me know and I can merge them.

Matt

> Honglei, Thanks a lot for reviewing the code.
> 
> 
> Lizhi
> 
> On 11/9/26 00:56, Huang, Honglei1 wrote:
> > AMD General
> > 
> > Reviewed-by: Honglei Huang <honghuan@amd.com>
> > 
> > -----Original Message-----
> > From: Lizhi Hou <lizhi.hou@amd.com>
> > Sent: Saturday, November 7, 2026 2:42 AM
> > To: ogabbay@kernel.org; quic_jhugo@quicinc.com; dri-devel@lists.freedesktop.org; Limonciello, Mario <Mario.Limonciello@amd.com>; karol.wachowski@linux.intel.com; Zhen, Max <Max.Zhen@amd.com>; thomas.hellstrom@linux.intel.com; himal.prasad.ghimiray@intel.com; Huang, Honglei1 <Honglei1.Huang@amd.com>
> > Cc: Matthew Brost <matthew.brost@intel.com>; linux-kernel@vger.kernel.org; Santan, Sonal <sonal.santan@amd.com>; Hou, Lizhi <lizhi.hou@amd.com>
> > Subject: [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers
> > 
> > From: Matthew Brost <matthew.brost@intel.com>
> > 
> > Drivers which only use the drm_gpusvm_pages API (no drm_gpusvm_range) have no way to mark pages unmapped from their MMU notifier, as
> > drm_gpusvm_range_set_unmapped() requires a range. They also have to open code the mmu_interval_read_retry() check on the recorded notifier_seq to commit pages faulted with &drm_gpusvm_ctx.no_dma_map.
> > 
> > Split the pages part of drm_gpusvm_range_set_unmapped() into drm_gpusvm_pages_set_unmapped(), and add drm_gpusvm_pages_read_retry() which performs the commit side check with a lockdep assert on the notifier lock.
> > 
> > Assisted-by: LLM
> > Signed-off-by: Matthew Brost <matthew.brost@intel.com>
> > Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
> > ---
> >   drivers/gpu/drm/drm_gpusvm.c | 49 +++++++++++++++++++++++++-----------
> >   include/drm/drm_gpusvm.h     | 29 +++++++++++++++++++++
> >   2 files changed, 64 insertions(+), 14 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 03dbe304e4b8f..63a26919f59db 100644
> > --- a/drivers/gpu/drm/drm_gpusvm.c
> > +++ b/drivers/gpu/drm/drm_gpusvm.c
> > @@ -1761,9 +1761,8 @@ static int drm_gpusvm_dma_map_pages(struct drm_gpusvm *gpusvm,
> >    *
> >    * With &drm_gpusvm_ctx.no_dma_map no mapping state is recorded, so
> >    * drm_gpusvm_pages_valid() never returns true and success is only a snapshot:
> > - * the caller must recheck mmu_interval_read_retry() against the recorded
> > - * &drm_gpusvm_pages.notifier_seq under the notifier lock, and hold it until
> > - * its work is visible to invalidation.
> > + * the caller must recheck drm_gpusvm_pages_read_retry() under the
> > + notifier
> > + * lock, and hold it until its work is visible to invalidation.
> >    *
> >    * Return: 0 on success, negative error code on failure.
> >    */
> > @@ -2021,6 +2020,38 @@ bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start,  }  EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping);
> > 
> > +/**
> > + * drm_gpusvm_pages_set_unmapped() - Mark GPU SVM pages as unmapped
> > + * @gpusvm: Pointer to the GPU SVM structure.
> > + * @pages: Pointer to the GPU SVM pages structure(s).
> > + * @pages_count: Number of GPU SVM pages structure(s) passed in.
> > + *
> > + * Mark @pages as unmapped, after which drm_gpusvm_get_pages() fails
> > +with
> > + * -EFAULT for them. Intended to be called from the driver's MMU
> > +notifier on
> > + * MMU_NOTIFY_UNMAP, and usable without a &drm_gpusvm_range by drivers
> > +that
> > + * only use the drm_gpusvm_pages API. The caller must hold the notifier
> > +lock
> > + * in write mode.
> > + */
> > +void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
> > +                                  struct drm_gpusvm_pages *pages,
> > +                                  unsigned int pages_count)
> > +{
> > +       unsigned int i;
> > +
> > +       lockdep_assert_held_write(&gpusvm->notifier_lock);
> > +
> > +       for (i = 0; i < pages_count; ++i) {
> > +               struct drm_gpusvm_pages_flags flags = {
> > +                       .__flags = pages[i].flags.__flags,
> > +               };
> > +
> > +               flags.unmapped = true;
> > +               /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
> > +               WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
> > +       }
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpusvm_pages_set_unmapped);
> > +
> >   /**
> >    * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped
> >    * @range: Pointer to the GPU SVM range structure.
> > @@ -2039,20 +2070,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
> >          struct drm_gpusvm_range_flags range_flags = {
> >                  .__flags = range->flags.__flags,
> >          };
> > -       unsigned int i;
> > 
> > -       lockdep_assert_held_write(&range->gpusvm->notifier_lock);
> > +       drm_gpusvm_pages_set_unmapped(range->gpusvm, pages, pages_count);
> > 
> >          range_flags.unmapped = true;
> > -       for (i = 0; i < pages_count; ++i) {
> > -               struct drm_gpusvm_pages_flags flags = {
> > -                       .__flags = pages[i].flags.__flags,
> > -               };
> > -
> > -               flags.unmapped = true;
> > -               /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
> > -               WRITE_ONCE(pages[i].flags.__flags, flags.__flags);
> > -       }
> >          if (drm_gpusvm_range_start(range) < mmu_range->start ||
> >              drm_gpusvm_range_end(range) > mmu_range->end)
> >                  range_flags.partial_unmap = true;
> > diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 4d31c6bba7455..b43432af56377 100644
> > --- a/include/drm/drm_gpusvm.h
> > +++ b/include/drm/drm_gpusvm.h
> > @@ -349,6 +349,10 @@ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
> >                                     unsigned int pages_count,
> >                                     const struct mmu_notifier_range *mmu_range);
> > 
> > +void drm_gpusvm_pages_set_unmapped(struct drm_gpusvm *gpusvm,
> > +                                  struct drm_gpusvm_pages *pages,
> > +                                  unsigned int pages_count);
> > +
> >   int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
> >                           struct drm_gpusvm_pages *svm_pages,
> >                           unsigned int num_pages,
> > @@ -383,6 +387,31 @@ static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages,
> >          svm_pages->notifier_seq = LONG_MAX;
> >   }
> > 
> > +/**
> > + * drm_gpusvm_pages_read_retry() - Check whether GPU SVM pages went
> > +stale
> > + * @gpusvm: Pointer to the GPU SVM structure.
> > + * @svm_pages: Pointer to the drm_gpusvm_pages.
> > + * @notifier: The notifier passed to drm_gpusvm_get_pages() for @svm_pages.
> > + *
> > + * Check whether @notifier was invalidated since the last successful
> > + * drm_gpusvm_get_pages() for @svm_pages. This is the commit side check
> > +for
> > + * pages faulted with &drm_gpusvm_ctx.no_dma_map, where no mapping
> > +state is
> > + * tracked. Pages that were never faulted always report as stale. The
> > +caller
> > + * must hold the notifier lock and keep holding it until its use of the
> > +pages
> > + * is visible to the invalidation path.
> > + *
> > + * Return: True if the pages must be faulted again, false otherwise.
> > + */
> > +static inline bool
> > +drm_gpusvm_pages_read_retry(struct drm_gpusvm *gpusvm,
> > +                           struct drm_gpusvm_pages *svm_pages,
> > +                           struct mmu_interval_notifier *notifier) {
> > +       lockdep_assert_held(&gpusvm->notifier_lock);
> > +
> > +       return mmu_interval_read_retry(notifier, svm_pages->notifier_seq); }
> > +
> >   /**
> >    * drm_gpusvm_pages_first_dma() - Resolve the device address array
> >    * @svm_pages: Pointer to the drm_gpusvm_pages.
> > 
> > base-commit: d2a8cee91bfa8616f713c7c8f539a81e8e3554ea
> > --
> > 2.34.1
> > 

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-11-09 22:35 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 18:42 [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Lizhi Hou
2026-11-06 18:42 ` [PATCH V1 2/2] accel/amdxdna: use drm_gpusvm_pages for user mapping tracking Lizhi Hou
2026-11-09 19:44   ` Max Zhen
2026-11-09  8:56 ` [PATCH V1 1/2] drm/gpusvm: add drm_gpusvm_pages unmapped and retry helpers Huang, Honglei1
2026-11-09 19:53   ` Lizhi Hou
2026-11-09 22:35     ` Matthew Brost
2026-11-09 22:34 ` Matthew Brost

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®