Skip to contentSkip to conversation

drm/xe: Userptr invalid page access fixes

PATCH Superseded Thomas Hellström 4 participants 10 messages Mar 4, 2025

Subsystems (3)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Thomas Hellström

author maintainer
#0 lore raw

v2 cover drm/xe: Userptr invalid page access fixes

Fix a number of potential invalid page-accesses by the userptr code.

Patch 1:
- Some coding style fixes.
Patch 2:
- Avoids invalid struct page accesses during userptr pinning. Patch 3:
- Block xe device accesses on mmu_invalidation on the iommu level.

v2:
- Address review comments by Matt Auld

Cc Matthew Auld <[email protected]>
Cc Oak Zeng <[email protected]>

Thomas Hellström (3):
drm/xe/hmm: Style- and include fixes
drm/xe/hmm: Don't dereference struct page pointers without notifier
lock
drm/xe/userptr: Unmap userptrs in the mmu notifier

 drivers/gpu/drm/xe/xe_hmm.c      | 175 ++++++++++++++++++++++++-------
 drivers/gpu/drm/xe/xe_hmm.h      |   7 ++
 drivers/gpu/drm/xe/xe_vm.c       |   4 +
 drivers/gpu/drm/xe/xe_vm_types.h |   4 +
 4 files changed, 150 insertions(+), 40 deletions(-)
Signature · 1 line
2.48.1
author maintainer
#1 lore raw

PATCH v2 1/3 drm/xe/hmm: Style- and include fixes

Add proper #ifndef around the xe_hmm.h header, proper spacing and since the documentation mostly follows kerneldoc format, make it kerneldoc. Also prepare for upcoming -stable fixes.

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+
---
 drivers/gpu/drm/xe/xe_hmm.c | 9 +++------
 drivers/gpu/drm/xe/xe_hmm.h | 5 +++++
 2 files changed, 8 insertions(+), 6 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +3 -6
index 089834467880..c56738fa713b 100644
@@ -19,11 +19,10 @@ static u64 xe_npages_in_range(unsigned long start, unsigned long end)
 	return (end - start) >> PAGE_SHIFT;
 }
 
-/*
+/**
  * xe_mark_range_accessed() - mark a range is accessed, so core mm
  * have such information for memory eviction or write back to
  * hard disk
- *
  * @range: the range to mark
  * @write: if write to this range, we mark pages in this range
  * as dirty
@@ -43,11 +42,10 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
 	}
 }
 
-/*
+/**
  * xe_build_sg() - build a scatter gather table for all the physical pages/pfn
  * in a hmm_range. dma-map pages if necessary. dma-address is save in sg table
  * and will be used to program GPU page table later.
- *
  * @xe: the xe device who will access the dma-address in sg table
  * @range: the hmm range that we build the sg table from. range->hmm_pfns[]
  * has the pfn numbers of pages that back up this hmm address range.
@@ -112,9 +110,8 @@ static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
 	return ret;
 }
 
-/*
+/**
  * xe_hmm_userptr_free_sg() - Free the scatter gather table of userptr
- *
  * @uvma: the userptr vma which hold the scatter gather table
  *
  * With function xe_userptr_populate_range, we allocate storage of
drivers/gpu/drm/xe/xe_hmm.h +5 -0
index 909dc2bdcd97..9602cb7d976d 100644
@@ -3,9 +3,14 @@
  * Copyright © 2024 Intel Corporation
  */
 
+#ifndef _XE_HMM_H_
+#define _XE_HMM_H_
+
 #include <linux/types.h>
 
 struct xe_userptr_vma;
 
 int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma, bool is_mm_mmap_locked);
+
 void xe_hmm_userptr_free_sg(struct xe_userptr_vma *uvma);
+#endif
Signature · 1 line
2.48.1
#2 lore raw

Re: [PATCH v2 1/3] drm/xe/hmm: Style- and include fixes

Quoted 8 lines · Thomas Hellström

Add proper #ifndef around the xe_hmm.h header, proper spacing and since the documentation mostly follows kerneldoc format, make it kerneldoc. Also prepare for upcoming -stable fixes.

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+

Doing this also flags xe_pcode_api.h:

index 856b14fe1c4d..ac635efa224b 100644

drivers/gpu/drm/xe/Makefile +1 -1
@@ -328,7 +328,7 @@ always-$(CONFIG_DRM_XE_WERROR) += \
 	$(patsubst %.h,%.hdrtest, $(shell cd $(src) && find * -name '*.h' $(hdrtest_find_args)))
 
 quiet_cmd_hdrtest = HDRTEST $(patsubst %.hdrtest,%.h,$@)
-      cmd_hdrtest = $(CC) -DHDRTEST $(filter-out $(CFLAGS_GCOV), $(c_flags)) -S -o /dev/null -x c /dev/null -include $<; touch $@
+      cmd_hdrtest = $(CC) -DHDRTEST $(filter-out $(CFLAGS_GCOV), $(c_flags)) -S -o /dev/null -x c /dev/null -include $< -include $<; touch $@
 
 $(obj)/%.hdrtest: $(src)/%.h FORCE
 	$(call if_changed_dep,hdrtest)

BR,
Jani.

Quoted 65 lines
---
 drivers/gpu/drm/xe/xe_hmm.c | 9 +++------
 drivers/gpu/drm/xe/xe_hmm.h | 5 +++++
 2 files changed, 8 insertions(+), 6 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +3 -6
index 089834467880..c56738fa713b 100644
@@ -19,11 +19,10 @@ static u64 xe_npages_in_range(unsigned long start, unsigned long end)
 	return (end - start) >> PAGE_SHIFT;
 }
 
-/*
+/**
  * xe_mark_range_accessed() - mark a range is accessed, so core mm
  * have such information for memory eviction or write back to
  * hard disk
- *
  * @range: the range to mark
  * @write: if write to this range, we mark pages in this range
  * as dirty
@@ -43,11 +42,10 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
 	}
 }
 
-/*
+/**
  * xe_build_sg() - build a scatter gather table for all the physical pages/pfn
  * in a hmm_range. dma-map pages if necessary. dma-address is save in sg table
  * and will be used to program GPU page table later.
- *
  * @xe: the xe device who will access the dma-address in sg table
  * @range: the hmm range that we build the sg table from. range->hmm_pfns[]
  * has the pfn numbers of pages that back up this hmm address range.
@@ -112,9 +110,8 @@ static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
 	return ret;
 }
 
-/*
+/**
  * xe_hmm_userptr_free_sg() - Free the scatter gather table of userptr
- *
  * @uvma: the userptr vma which hold the scatter gather table
  *
  * With function xe_userptr_populate_range, we allocate storage of
drivers/gpu/drm/xe/xe_hmm.h +5 -0
index 909dc2bdcd97..9602cb7d976d 100644
@@ -3,9 +3,14 @@
  * Copyright © 2024 Intel Corporation
  */
 
+#ifndef _XE_HMM_H_
+#define _XE_HMM_H_
+
 #include <linux/types.h>
 
 struct xe_userptr_vma;
 
 int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma, bool is_mm_mmap_locked);
+
 void xe_hmm_userptr_free_sg(struct xe_userptr_vma *uvma);
+#endif
Signature · 1 line
Jani Nikula, Intel
Reviewed-by on v2 1/3
#3 lore raw

Re: [PATCH v2 1/3] drm/xe/hmm: Style- and include fixes

In reply to Thomas Hellström
Quoted 8 lines · Thomas Hellström

Add proper #ifndef around the xe_hmm.h header, proper spacing and since the documentation mostly follows kerneldoc format, make it kerneldoc. Also prepare for upcoming -stable fixes.

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+
Quoted 65 lines
---
  drivers/gpu/drm/xe/xe_hmm.c | 9 +++------
  drivers/gpu/drm/xe/xe_hmm.h | 5 +++++
  2 files changed, 8 insertions(+), 6 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +3 -6
index 089834467880..c56738fa713b 100644
@@ -19,11 +19,10 @@ static u64 xe_npages_in_range(unsigned long start, unsigned long end)
  	return (end - start) >> PAGE_SHIFT;
  }
 
-/*
+/**
   * xe_mark_range_accessed() - mark a range is accessed, so core mm
   * have such information for memory eviction or write back to
   * hard disk
- *
   * @range: the range to mark
   * @write: if write to this range, we mark pages in this range
   * as dirty
@@ -43,11 +42,10 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
  	}
  }
 
-/*
+/**
   * xe_build_sg() - build a scatter gather table for all the physical pages/pfn
   * in a hmm_range. dma-map pages if necessary. dma-address is save in sg table
   * and will be used to program GPU page table later.
- *
   * @xe: the xe device who will access the dma-address in sg table
   * @range: the hmm range that we build the sg table from. range->hmm_pfns[]
   * has the pfn numbers of pages that back up this hmm address range.
@@ -112,9 +110,8 @@ static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
  	return ret;
  }
 
-/*
+/**
   * xe_hmm_userptr_free_sg() - Free the scatter gather table of userptr
- *
   * @uvma: the userptr vma which hold the scatter gather table
   *
   * With function xe_userptr_populate_range, we allocate storage of
drivers/gpu/drm/xe/xe_hmm.h +5 -0
index 909dc2bdcd97..9602cb7d976d 100644
@@ -3,9 +3,14 @@
   * Copyright © 2024 Intel Corporation
   */
 
+#ifndef _XE_HMM_H_
+#define _XE_HMM_H_
+
  #include <linux/types.h>
 
  struct xe_userptr_vma;
 
  int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma, bool is_mm_mmap_locked);
+
  void xe_hmm_userptr_free_sg(struct xe_userptr_vma *uvma);
+#endif
author maintainer
#4 lore raw

PATCH v2 2/3 drm/xe/hmm: Don't dereference struct page pointers without notifier lock

In reply to Thomas Hellström

The pnfs that we obtain from hmm_range_fault() point to pages that we don't have a reference on, and the guarantee that they are still in the cpu page-tables is that the notifier lock must be held and the notifier seqno is still valid.

So while building the sg table and marking the pages accesses / dirty we need to hold this lock with a validated seqno.

However, the lock is reclaim tainted which makes sg_alloc_table_from_pages_segment() unusable, since it internally allocates memory.

Instead build the sg-table manually. For the non-iommu case this might lead to fewer coalesces, but if that's a problem it can be fixed up later in the resource cursor code. For the iommu case, the whole sg-table may still be coalesced to a single contigous device va region.

This avoids marking pages that we don't own dirty and accessed, and it also avoid dereferencing struct pages that we don't own.

v2:
- Use assert to check whether hmm pfns are valid (Matthew Auld)
- Take into account that large pages may cross range boundaries (Matthew Auld)

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+
---
 drivers/gpu/drm/xe/xe_hmm.c | 119 ++++++++++++++++++++++++++++--------
 1 file changed, 93 insertions(+), 26 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +93 -26
index c56738fa713b..93cce9e819a1 100644
@@ -42,6 +42,40 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
 	}
 }
 
+static int xe_alloc_sg(struct xe_device *xe, struct sg_table *st,
+		       struct hmm_range *range, struct rw_semaphore *notifier_sem)
+{
+	unsigned long i, npages, hmm_pfn;
+	unsigned long num_chunks = 0;
+	int ret;
+
+	/* HMM docs says this is needed. */
+	ret = down_read_interruptible(notifier_sem);
+	if (ret)
+		return ret;
+
+	if (mmu_interval_read_retry(range->notifier, range->notifier_seq))
+		return -EAGAIN;
+
+	npages = xe_npages_in_range(range->start, range->end);
+	for (i = 0; i < npages;) {
+		unsigned long len;
+
+		hmm_pfn = range->hmm_pfns[i];
+		xe_assert(xe, hmm_pfn & HMM_PFN_VALID);
+
+		len = 1UL << hmm_pfn_to_map_order(hmm_pfn);
+
+		/* If order > 0 the page may extend beyond range->start */
+		len -= (hmm_pfn & ~HMM_PFN_FLAGS) & (len - 1);
+		i += len;
+		num_chunks++;
+	}
+	up_read(notifier_sem);
+
+	return sg_alloc_table(st, num_chunks, GFP_KERNEL);
+}
+
 /**
  * xe_build_sg() - build a scatter gather table for all the physical pages/pfn
  * in a hmm_range. dma-map pages if necessary. dma-address is save in sg table
@@ -50,6 +84,7 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
  * @range: the hmm range that we build the sg table from. range->hmm_pfns[]
  * has the pfn numbers of pages that back up this hmm address range.
  * @st: pointer to the sg table.
+ * @notifier_sem: The xe notifier lock.
  * @write: whether we write to this range. This decides dma map direction
  * for system pages. If write we map it bi-diretional; otherwise
  * DMA_TO_DEVICE
@@ -76,38 +111,41 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
  * Returns 0 if successful; -ENOMEM if fails to allocate memory
  */
 static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
-		       struct sg_table *st, bool write)
+		       struct sg_table *st,
+		       struct rw_semaphore *notifier_sem,
+		       bool write)
 {
+	unsigned long npages = xe_npages_in_range(range->start, range->end);
 	struct device *dev = xe->drm.dev;
-	struct page **pages;
-	u64 i, npages;
-	int ret;
+	struct scatterlist *sgl;
+	struct page *page;
+	unsigned long i, j;
 
-	npages = xe_npages_in_range(range->start, range->end);
-	pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL);
-	if (!pages)
-		return -ENOMEM;
+	lockdep_assert_held(notifier_sem);
 
-	for (i = 0; i < npages; i++) {
-		pages[i] = hmm_pfn_to_page(range->hmm_pfns[i]);
-		xe_assert(xe, !is_device_private_page(pages[i]));
-	}
+	i = 0;
+	for_each_sg(st->sgl, sgl, st->nents, j) {
+		unsigned long hmm_pfn, size;
 
-	ret = sg_alloc_table_from_pages_segment(st, pages, npages, 0, npages << PAGE_SHIFT,
-						xe_sg_segment_size(dev), GFP_KERNEL);
-	if (ret)
-		goto free_pages;
+		hmm_pfn = range->hmm_pfns[i];
+		page = hmm_pfn_to_page(hmm_pfn);
+		xe_assert(xe, !is_device_private_page(page));
 
-	ret = dma_map_sgtable(dev, st, write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE,
-			      DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_NO_KERNEL_MAPPING);
-	if (ret) {
-		sg_free_table(st);
-		st = NULL;
+		size = 1UL << hmm_pfn_to_map_order(hmm_pfn);
+		size -= page_to_pfn(page) & (size - 1);
+		i += size;
+
+		if (unlikely(j == st->nents - 1)) {
+			if (i > npages)
+				size -= (i - npages);
+			sg_mark_end(sgl);
+		}
+		sg_set_page(sgl, page, size << PAGE_SHIFT, 0);
 	}
+	xe_assert(xe, i == npages);
 
-free_pages:
-	kvfree(pages);
-	return ret;
+	return dma_map_sgtable(dev, st, write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE,
+			       DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_NO_KERNEL_MAPPING);
 }
 
 /**
@@ -235,16 +273,45 @@ int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma,
 	if (ret)
 		goto free_pfns;
 
-	ret = xe_build_sg(vm->xe, &hmm_range, &userptr->sgt, write);
+	if (unlikely(userptr->sg)) {
+		ret = down_write_killable(&vm->userptr.notifier_lock);
+		if (ret)
+			goto free_pfns;
+
+		xe_hmm_userptr_free_sg(uvma);
+		up_write(&vm->userptr.notifier_lock);
+	}
+
+	ret = xe_alloc_sg(vm->xe, &userptr->sgt, &hmm_range, &vm->userptr.notifier_lock);
 	if (ret)
 		goto free_pfns;
 
+	ret = down_read_interruptible(&vm->userptr.notifier_lock);
+	if (ret)
+		goto free_st;
+
+	if (mmu_interval_read_retry(hmm_range.notifier, hmm_range.notifier_seq)) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	ret = xe_build_sg(vm->xe, &hmm_range, &userptr->sgt,
+			  &vm->userptr.notifier_lock, write);
+	if (ret)
+		goto out_unlock;
+
 	xe_mark_range_accessed(&hmm_range, write);
 	userptr->sg = &userptr->sgt;
 	userptr->notifier_seq = hmm_range.notifier_seq;
+	up_read(&vm->userptr.notifier_lock);
+	kvfree(pfns);
+	return 0;
 
+out_unlock:
+	up_read(&vm->userptr.notifier_lock);
+free_st:
+	sg_free_table(&userptr->sgt);
 free_pfns:
 	kvfree(pfns);
 	return ret;
 }
-
Signature · 1 line
2.48.1
Reviewed-by on v2 2/3
#5 lore raw

Re: [PATCH v2 2/3] drm/xe/hmm: Don't dereference struct page pointers without notifier lock

Quoted 45 lines · Thomas Hellström

The pnfs that we obtain from hmm_range_fault() point to pages that we don't have a reference on, and the guarantee that they are still in the cpu page-tables is that the notifier lock must be held and the notifier seqno is still valid.

So while building the sg table and marking the pages accesses / dirty we need to hold this lock with a validated seqno.

However, the lock is reclaim tainted which makes sg_alloc_table_from_pages_segment() unusable, since it internally allocates memory.

Instead build the sg-table manually. For the non-iommu case this might lead to fewer coalesces, but if that's a problem it can be fixed up later in the resource cursor code. For the iommu case, the whole sg-table may still be coalesced to a single contigous device va region.

This avoids marking pages that we don't own dirty and accessed, and it also avoid dereferencing struct pages that we don't own.

v2:
- Use assert to check whether hmm pfns are valid (Matthew Auld)
- Take into account that large pages may cross range boundaries (Matthew Auld)

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+
---
  drivers/gpu/drm/xe/xe_hmm.c | 119 ++++++++++++++++++++++++++++--------
  1 file changed, 93 insertions(+), 26 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +3 -0
index c56738fa713b..93cce9e819a1 100644
@@ -42,6 +42,40 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
  	}
  }
 
+static int xe_alloc_sg(struct xe_device *xe, struct sg_table *st,
+		       struct hmm_range *range, struct rw_semaphore *notifier_sem)
+{

drivers/gpu/drm/xe/xe_hmm.c static void xe_mark_range_accessed(struct hmm_range *range, bool write) · line 57

Quoted 10 lines
drivers/gpu/drm/xe/xe_hmm.c +10 -0
+	unsigned long i, npages, hmm_pfn;
+	unsigned long num_chunks = 0;
+	int ret;
+
+	/* HMM docs says this is needed. */
+	ret = down_read_interruptible(notifier_sem);
+	if (ret)
+		return ret;
+
+	if (mmu_interval_read_retry(range->notifier, range->notifier_seq))

up_read() ?

Quoted 99 lines
drivers/gpu/drm/xe/xe_hmm.c +49 -24
+		return -EAGAIN;
+
+	npages = xe_npages_in_range(range->start, range->end);
+	for (i = 0; i < npages;) {
+		unsigned long len;
+
+		hmm_pfn = range->hmm_pfns[i];
+		xe_assert(xe, hmm_pfn & HMM_PFN_VALID);
+
+		len = 1UL << hmm_pfn_to_map_order(hmm_pfn);
+
+		/* If order > 0 the page may extend beyond range->start */
+		len -= (hmm_pfn & ~HMM_PFN_FLAGS) & (len - 1);
+		i += len;
+		num_chunks++;
+	}
+	up_read(notifier_sem);
+
+	return sg_alloc_table(st, num_chunks, GFP_KERNEL);
+}
+
  /**
   * xe_build_sg() - build a scatter gather table for all the physical pages/pfn
   * in a hmm_range. dma-map pages if necessary. dma-address is save in sg table
@@ -50,6 +84,7 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
   * @range: the hmm range that we build the sg table from. range->hmm_pfns[]
   * has the pfn numbers of pages that back up this hmm address range.
   * @st: pointer to the sg table.
+ * @notifier_sem: The xe notifier lock.
   * @write: whether we write to this range. This decides dma map direction
   * for system pages. If write we map it bi-diretional; otherwise
   * DMA_TO_DEVICE
@@ -76,38 +111,41 @@ static void xe_mark_range_accessed(struct hmm_range *range, bool write)
   * Returns 0 if successful; -ENOMEM if fails to allocate memory
   */
  static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
-		       struct sg_table *st, bool write)
+		       struct sg_table *st,
+		       struct rw_semaphore *notifier_sem,
+		       bool write)
  {
+	unsigned long npages = xe_npages_in_range(range->start, range->end);
  	struct device *dev = xe->drm.dev;
-	struct page **pages;
-	u64 i, npages;
-	int ret;
+	struct scatterlist *sgl;
+	struct page *page;
+	unsigned long i, j;
 
-	npages = xe_npages_in_range(range->start, range->end);
-	pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL);
-	if (!pages)
-		return -ENOMEM;
+	lockdep_assert_held(notifier_sem);
 
-	for (i = 0; i < npages; i++) {
-		pages[i] = hmm_pfn_to_page(range->hmm_pfns[i]);
-		xe_assert(xe, !is_device_private_page(pages[i]));
-	}
+	i = 0;
+	for_each_sg(st->sgl, sgl, st->nents, j) {
+		unsigned long hmm_pfn, size;
 
-	ret = sg_alloc_table_from_pages_segment(st, pages, npages, 0, npages << PAGE_SHIFT,
-						xe_sg_segment_size(dev), GFP_KERNEL);
-	if (ret)
-		goto free_pages;
+		hmm_pfn = range->hmm_pfns[i];
+		page = hmm_pfn_to_page(hmm_pfn);
+		xe_assert(xe, !is_device_private_page(page));
 
-	ret = dma_map_sgtable(dev, st, write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE,
-			      DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_NO_KERNEL_MAPPING);
-	if (ret) {
-		sg_free_table(st);
-		st = NULL;
+		size = 1UL << hmm_pfn_to_map_order(hmm_pfn);
+		size -= page_to_pfn(page) & (size - 1);
+		i += size;
+
+		if (unlikely(j == st->nents - 1)) {
+			if (i > npages)
+				size -= (i - npages);
+			sg_mark_end(sgl);
+		}
+		sg_set_page(sgl, page, size << PAGE_SHIFT, 0);
  	}
+	xe_assert(xe, i == npages);
 
-free_pages:
-	kvfree(pages);
-	return ret;
+	return dma_map_sgtable(dev, st, write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE,
+			       DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_NO_KERNEL_MAPPING);
  }
 
  /**

drivers/gpu/drm/xe/xe_hmm.c int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma, · line 276

Quoted 5 lines
drivers/gpu/drm/xe/xe_hmm.c +1 -1
@@ -235,16 +273,45 @@ int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma,
  	if (ret)
  		goto free_pfns;
 
-	ret = xe_build_sg(vm->xe, &hmm_range, &userptr->sgt, write);
+	if (unlikely(userptr->sg)) {

Is it really possible to hit this? We did the unmap above also, although that was outside of the notifier lock?

Otherwise,

Quoted 42 lines
drivers/gpu/drm/xe/xe_hmm.c +30 -1
+		ret = down_write_killable(&vm->userptr.notifier_lock);
+		if (ret)
+			goto free_pfns;
+
+		xe_hmm_userptr_free_sg(uvma);
+		up_write(&vm->userptr.notifier_lock);
+	}
+
+	ret = xe_alloc_sg(vm->xe, &userptr->sgt, &hmm_range, &vm->userptr.notifier_lock);
  	if (ret)
  		goto free_pfns;
 
+	ret = down_read_interruptible(&vm->userptr.notifier_lock);
+	if (ret)
+		goto free_st;
+
+	if (mmu_interval_read_retry(hmm_range.notifier, hmm_range.notifier_seq)) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	ret = xe_build_sg(vm->xe, &hmm_range, &userptr->sgt,
+			  &vm->userptr.notifier_lock, write);
+	if (ret)
+		goto out_unlock;
+
  	xe_mark_range_accessed(&hmm_range, write);
  	userptr->sg = &userptr->sgt;
  	userptr->notifier_seq = hmm_range.notifier_seq;
+	up_read(&vm->userptr.notifier_lock);
+	kvfree(pfns);
+	return 0;
 
+out_unlock:
+	up_read(&vm->userptr.notifier_lock);
+free_st:
+	sg_free_table(&userptr->sgt);
  free_pfns:
  	kvfree(pfns);
  	return ret;
  }
-
author maintainer
#6 lore raw

Re: [PATCH v2 2/3] drm/xe/hmm: Don't dereference struct page pointers without notifier lock

Quoted 186 lines · Matthew Auld
Quoted 62 lines · Thomas Hellström

The pnfs that we obtain from hmm_range_fault() point to pages that we don't have a reference on, and the guarantee that they are still in the cpu page-tables is that the notifier lock must be held and the
notifier seqno is still valid.

So while building the sg table and marking the pages accesses / dirty
we need to hold this lock with a validated seqno.

However, the lock is reclaim tainted which makes sg_alloc_table_from_pages_segment() unusable, since it internally allocates memory.

Instead build the sg-table manually. For the non-iommu case this might lead to fewer coalesces, but if that's a problem it can be fixed up later in the resource cursor code. For the iommu case, the whole sg-table may still be coalesced to a single contigous device va region.

This avoids marking pages that we don't own dirty and accessed, and it also avoid dereferencing struct pages that we don't own.

v2:
- Use assert to check whether hmm pfns are valid (Matthew Auld)
- Take into account that large pages may cross range boundaries (Matthew Auld)

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate

userptr")

---
  drivers/gpu/drm/xe/xe_hmm.c | 119 ++++++++++++++++++++++++++++---
-----

  1 file changed, 93 insertions(+), 26 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +0 -0
index c56738fa713b..93cce9e819a1 100644
@@ -42,6 +42,40 @@ static void xe_mark_range_accessed(struct
 hmm_range *range, bool write)
   	}
   }

drivers/gpu/drm/xe/xe_hmm.c +2 -0
+static int xe_alloc_sg(struct xe_device *xe, struct sg_table *st,
+		       struct hmm_range *range, struct

rw_semaphore *notifier_sem)

drivers/gpu/drm/xe/xe_hmm.c +11 -0
+{
+	unsigned long i, npages, hmm_pfn;
+	unsigned long num_chunks = 0;
+	int ret;
+
+	/* HMM docs says this is needed. */
+	ret = down_read_interruptible(notifier_sem);
+	if (ret)
+		return ret;
+
+	if (mmu_interval_read_retry(range->notifier, range-
Quoted 1 line
Quoted 1 line

notifier_seq))

up_read() ?

Quoted 119 lines
drivers/gpu/drm/xe/xe_hmm.c +12 -0
+		return -EAGAIN;
+
+	npages = xe_npages_in_range(range->start, range->end);
+	for (i = 0; i < npages;) {
+		unsigned long len;
+
+		hmm_pfn = range->hmm_pfns[i];
+		xe_assert(xe, hmm_pfn & HMM_PFN_VALID);
+
+		len = 1UL << hmm_pfn_to_map_order(hmm_pfn);
+
+		/* If order > 0 the page may extend beyond range-
Quoted 1 line

start */

drivers/gpu/drm/xe/xe_hmm.c +9 -0
+		len -= (hmm_pfn & ~HMM_PFN_FLAGS) & (len - 1);
+		i += len;
+		num_chunks++;
+	}
+	up_read(notifier_sem);
+
+	return sg_alloc_table(st, num_chunks, GFP_KERNEL);
+}
+

  /**
   * xe_build_sg() - build a scatter gather table for all the physical pages/pfn
   * in a hmm_range. dma-map pages if necessary. dma-address is save in sg table

drivers/gpu/drm/xe/xe_hmm.c +0 -0
@@ -50,6 +84,7 @@ static void xe_mark_range_accessed(struct
 hmm_range *range, bool write)
    * @range: the hmm range that we build the sg table from. range-
Quoted 1 line

hmm_pfns[]

   * has the pfn numbers of pages that back up this hmm address range.
   * @st: pointer to the sg table.
+ * @notifier_sem: The xe notifier lock.
   * @write: whether we write to this range. This decides dma map direction
   * for system pages. If write we map it bi-diretional; otherwise
   * DMA_TO_DEVICE

drivers/gpu/drm/xe/xe_hmm.c +0 -0
@@ -76,38 +111,41 @@ static void xe_mark_range_accessed(struct
 hmm_range *range, bool write)
    * Returns 0 if successful; -ENOMEM if fails to allocate memory
    */

  static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,

drivers/gpu/drm/xe/xe_hmm.c +3 -1
-		       struct sg_table *st, bool write)
+		       struct sg_table *st,
+		       struct rw_semaphore *notifier_sem,
+		       bool write)

  {

drivers/gpu/drm/xe/xe_hmm.c +1 -0
+	unsigned long npages = xe_npages_in_range(range->start,

range->end);
   struct device *dev = xe->drm.dev;

drivers/gpu/drm/xe/xe_hmm.c +3 -3
-	struct page **pages;
-	u64 i, npages;
-	int ret;
+	struct scatterlist *sgl;
+	struct page *page;
+	unsigned long i, j;

drivers/gpu/drm/xe/xe_hmm.c +0 -2
-	npages = xe_npages_in_range(range->start, range->end);
-	pages = kvmalloc_array(npages, sizeof(*pages),

GFP_KERNEL);

drivers/gpu/drm/xe/xe_hmm.c +1 -2
-	if (!pages)
-		return -ENOMEM;
+	lockdep_assert_held(notifier_sem);

drivers/gpu/drm/xe/xe_hmm.c +3 -4
-	for (i = 0; i < npages; i++) {
-		pages[i] = hmm_pfn_to_page(range->hmm_pfns[i]);
-		xe_assert(xe, !is_device_private_page(pages[i]));
-	}
+	i = 0;
+	for_each_sg(st->sgl, sgl, st->nents, j) {
+		unsigned long hmm_pfn, size;

drivers/gpu/drm/xe/xe_hmm.c +0 -1
-	ret = sg_alloc_table_from_pages_segment(st, pages, npages,

0, npages << PAGE_SHIFT,
-

						xe_sg_segment_size(dev), GFP_KERNEL);
drivers/gpu/drm/xe/xe_hmm.c +3 -2
-	if (ret)
-		goto free_pages;
+		hmm_pfn = range->hmm_pfns[i];
+		page = hmm_pfn_to_page(hmm_pfn);
+		xe_assert(xe, !is_device_private_page(page));

drivers/gpu/drm/xe/xe_hmm.c +0 -1
-	ret = dma_map_sgtable(dev, st, write ? DMA_BIDIRECTIONAL :

DMA_TO_DEVICE,

drivers/gpu/drm/xe/xe_hmm.c +0 -1
-			      DMA_ATTR_SKIP_CPU_SYNC |

DMA_ATTR_NO_KERNEL_MAPPING);

drivers/gpu/drm/xe/xe_hmm.c +10 -3
-	if (ret) {
-		sg_free_table(st);
-		st = NULL;
+		size = 1UL << hmm_pfn_to_map_order(hmm_pfn);
+		size -= page_to_pfn(page) & (size - 1);
+		i += size;
+
+		if (unlikely(j == st->nents - 1)) {
+			if (i > npages)
+				size -= (i - npages);
+			sg_mark_end(sgl);
+		}
+		sg_set_page(sgl, page, size << PAGE_SHIFT, 0);

   }

drivers/gpu/drm/xe/xe_hmm.c +1 -0
+	xe_assert(xe, i == npages);

drivers/gpu/drm/xe/xe_hmm.c +1 -3
-free_pages:
-	kvfree(pages);
-	return ret;
+	return dma_map_sgtable(dev, st, write ? DMA_BIDIRECTIONAL

: DMA_TO_DEVICE,

drivers/gpu/drm/xe/xe_hmm.c +1 -0
+			       DMA_ATTR_SKIP_CPU_SYNC |

DMA_ATTR_NO_KERNEL_MAPPING);
  }

  /**

drivers/gpu/drm/xe/xe_hmm.c +0 -0
@@ -235,16 +273,45 @@ int xe_hmm_userptr_populate_range(struct
 xe_userptr_vma *uvma,
   	if (ret)
   		goto free_pfns;
Quoted 8 lines

drivers/gpu/drm/xe/xe_hmm.c int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma, · line 276

Quoted 4 lines

drivers/gpu/drm/xe/xe_hmm.c +0 -1
-	ret = xe_build_sg(vm->xe, &hmm_range, &userptr->sgt,

write);

drivers/gpu/drm/xe/xe_hmm.c +1 -0
+	if (unlikely(userptr->sg)) {

Is it really possible to hit this? We did the unmap above also, although
that was outside of the notifier lock?

Right. That was a spill-over from the next patch where we unmap in the notifier. But for this patch I'll fix up the two issues mentioned and add your R-B.

Thanks for reviewing.
Thomas

Quoted 50 lines

Otherwise,

Quoted 45 lines
drivers/gpu/drm/xe/xe_hmm.c +1 -0
+		ret = down_write_killable(&vm-
Quoted 1 line

userptr.notifier_lock);

drivers/gpu/drm/xe/xe_hmm.c +8 -0
+		if (ret)
+			goto free_pfns;
+
+		xe_hmm_userptr_free_sg(uvma);
+		up_write(&vm->userptr.notifier_lock);
+	}
+
+	ret = xe_alloc_sg(vm->xe, &userptr->sgt, &hmm_range, &vm-
Quoted 1 line

userptr.notifier_lock);

   if (ret)
   goto free_pfns;

drivers/gpu/drm/xe/xe_hmm.c +5 -0
+	ret = down_read_interruptible(&vm->userptr.notifier_lock);
+	if (ret)
+		goto free_st;
+
+	if (mmu_interval_read_retry(hmm_range.notifier,

hmm_range.notifier_seq)) {

drivers/gpu/drm/xe/xe_hmm.c +9 -0
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	ret = xe_build_sg(vm->xe, &hmm_range, &userptr->sgt,
+			  &vm->userptr.notifier_lock, write);
+	if (ret)
+		goto out_unlock;
+

   xe_mark_range_accessed(&hmm_range, write); userptr->sg = &userptr->sgt; userptr->notifier_seq = hmm_range.notifier_seq;

drivers/gpu/drm/xe/xe_hmm.c +3 -0
+	up_read(&vm->userptr.notifier_lock);
+	kvfree(pfns);
+	return 0;

drivers/gpu/drm/xe/xe_hmm.c +4 -0
+out_unlock:
+	up_read(&vm->userptr.notifier_lock);
+free_st:
+	sg_free_table(&userptr->sgt);

  free_pfns:
   kvfree(pfns);
   return ret;
  }
-

author maintainer
#7 lore raw

PATCH v2 3/3 drm/xe/userptr: Unmap userptrs in the mmu notifier

In reply to Thomas Hellström

If userptr pages are freed after a call to the xe mmu notifier, the device will not be blocked out from theoretically accessing these pages unless they are also unmapped from the iommu, and this violates some aspects of the iommu-imposed security.

Ensure that userptrs are unmapped in the mmu notifier to mitigate this. A naive attempt would try to free the sg table, but the sg table itself may be accessed by a concurrent bind operation, so settle for unly unmapping.

Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+
---
 drivers/gpu/drm/xe/xe_hmm.c      | 49 ++++++++++++++++++++++++++------
 drivers/gpu/drm/xe/xe_hmm.h      |  2 ++
 drivers/gpu/drm/xe/xe_vm.c       |  4 +++
 drivers/gpu/drm/xe/xe_vm_types.h |  4 +++
 4 files changed, 50 insertions(+), 9 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +40 -9
index 93cce9e819a1..b8d94f4c1a7f 100644
@@ -148,6 +148,43 @@ static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
 			       DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_NO_KERNEL_MAPPING);
 }
 
+static void xe_hmm_userptr_set_mapped(struct xe_userptr_vma *uvma)
+{
+	struct xe_userptr *userptr = &uvma->userptr;
+	struct xe_vm *vm = xe_vma_vm(&uvma->vma);
+
+	lockdep_assert_held_write(&vm->lock);
+
+	mutex_lock(&userptr->unmap_mutex);
+	xe_assert(vm->xe, !userptr->mapped);
+	userptr->mapped = true;
+	mutex_unlock(&userptr->unmap_mutex);
+}
+
+void xe_hmm_userptr_unmap(struct xe_userptr_vma *uvma)
+{
+	struct xe_userptr *userptr = &uvma->userptr;
+	struct xe_vma *vma = &uvma->vma;
+	bool write = !xe_vma_read_only(vma);
+	struct xe_vm *vm = xe_vma_vm(vma);
+	struct xe_device *xe = vm->xe;
+
+	if (!lockdep_is_held_type(&vm->userptr.notifier_lock, 0) &&
+	    !lockdep_is_held_type(&vm->lock, 0) &&
+	    !(vma->gpuva.flags & XE_VMA_DESTROYED)) {
+		xe_vm_assert_held(vm);
+		lockdep_assert_held(&vm->userptr.notifier_lock);
+		lockdep_assert_held(&vm->lock);
+	}
+
+	mutex_lock(&userptr->unmap_mutex);
+	if (userptr->sg && userptr->mapped)
+		dma_unmap_sgtable(xe->drm.dev, userptr->sg,
+				  write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE, 0);
+	userptr->mapped = false;
+	mutex_unlock(&userptr->unmap_mutex);
+}
+
 /**
  * xe_hmm_userptr_free_sg() - Free the scatter gather table of userptr
  * @uvma: the userptr vma which hold the scatter gather table
@@ -159,16 +196,9 @@ static int xe_build_sg(struct xe_device *xe, struct hmm_range *range,
 void xe_hmm_userptr_free_sg(struct xe_userptr_vma *uvma)
 {
 	struct xe_userptr *userptr = &uvma->userptr;
-	struct xe_vma *vma = &uvma->vma;
-	bool write = !xe_vma_read_only(vma);
-	struct xe_vm *vm = xe_vma_vm(vma);
-	struct xe_device *xe = vm->xe;
-	struct device *dev = xe->drm.dev;
-
-	xe_assert(xe, userptr->sg);
-	dma_unmap_sgtable(dev, userptr->sg,
-			  write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE, 0);
 
+	xe_assert(xe_vma_vm(&uvma->vma)->xe, userptr->sg);
+	xe_hmm_userptr_unmap(uvma);
 	sg_free_table(userptr->sg);
 	userptr->sg = NULL;
 }
@@ -302,6 +332,7 @@ int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma,
 
 	xe_mark_range_accessed(&hmm_range, write);
 	userptr->sg = &userptr->sgt;
+	xe_hmm_userptr_set_mapped(uvma);
 	userptr->notifier_seq = hmm_range.notifier_seq;
 	up_read(&vm->userptr.notifier_lock);
 	kvfree(pfns);
drivers/gpu/drm/xe/xe_hmm.h +2 -0
index 9602cb7d976d..0ea98d8e7bbc 100644
@@ -13,4 +13,6 @@ struct xe_userptr_vma;
 int xe_hmm_userptr_populate_range(struct xe_userptr_vma *uvma, bool is_mm_mmap_locked);
 
 void xe_hmm_userptr_free_sg(struct xe_userptr_vma *uvma);
+
+void xe_hmm_userptr_unmap(struct xe_userptr_vma *uvma);
 #endif
drivers/gpu/drm/xe/xe_vm.c +4 -0
index dd422ac95dc0..3dbd3d38008a 100644
@@ -621,6 +621,8 @@ static void __vma_userptr_invalidate(struct xe_vm *vm, struct xe_userptr_vma *uv
 		err = xe_vm_invalidate_vma(vma);
 		XE_WARN_ON(err);
 	}
+
+	xe_hmm_userptr_unmap(uvma);
 }
 
 static bool vma_userptr_invalidate(struct mmu_interval_notifier *mni,
@@ -1039,6 +1041,7 @@ static struct xe_vma *xe_vma_create(struct xe_vm *vm,
 			INIT_LIST_HEAD(&userptr->invalidate_link);
 			INIT_LIST_HEAD(&userptr->repin_link);
 			vma->gpuva.gem.offset = bo_offset_or_userptr;
+			mutex_init(&userptr->unmap_mutex);
 
 			err = mmu_interval_notifier_insert(&userptr->notifier,
 							   current->mm,
@@ -1080,6 +1083,7 @@ static void xe_vma_destroy_late(struct xe_vma *vma)
 		 * them anymore
 		 */
 		mmu_interval_notifier_remove(&userptr->notifier);
+		mutex_destroy(&userptr->unmap_mutex);
 		xe_vm_put(vm);
 	} else if (xe_vma_is_null(vma)) {
 		xe_vm_put(vm);
drivers/gpu/drm/xe/xe_vm_types.h +4 -0
index 1fe79bf23b6b..eca73c4197d4 100644
@@ -59,12 +59,16 @@ struct xe_userptr {
 	struct sg_table *sg;
 	/** @notifier_seq: notifier sequence number */
 	unsigned long notifier_seq;
+	/** @unmap_mutex: Mutex protecting dma-unmapping */
+	struct mutex unmap_mutex;
 	/**
 	 * @initial_bind: user pointer has been bound at least once.
 	 * write: vm->userptr.notifier_lock in read mode and vm->resv held.
 	 * read: vm->userptr.notifier_lock in write mode or vm->resv held.
 	 */
 	bool initial_bind;
+	/** @mapped: Whether the @sgt sg-table is dma-mapped. Protected by @unmap_mutex. */
+	bool mapped;
 #if IS_ENABLED(CONFIG_DRM_XE_USERPTR_INVAL_INJECT)
 	u32 divisor;
 #endif
Signature · 1 line
2.48.1
Reviewed-by on v2 3/3
#8 lore raw

Re: [PATCH v2 3/3] drm/xe/userptr: Unmap userptrs in the mmu notifier

Quoted 9 lines · Thomas Hellström

If userptr pages are freed after a call to the xe mmu notifier, the device will not be blocked out from theoretically accessing these pages unless they are also unmapped from the iommu, and this violates some aspects of the iommu-imposed security.

Ensure that userptrs are unmapped in the mmu notifier to mitigate this. A naive attempt would try to free the sg table, but the sg table itself may be accessed by a concurrent bind operation, so settle for unly unmapping.

only

Quoted 5 lines
Fixes 81e058a3e7fd ("drm/xe: Introduce helper to populate userptr")
Cc Oak Zeng <[email protected]>
Cc <[email protected]> # v6.10+
#9 lore raw

✗ CI.Patch_applied: failure for drm/xe: Userptr invalid page access fixes (rev2)

In reply to Thomas Hellström
== Series Details ==

== Series Details ==

Series: drm/xe: Userptr invalid page access fixes (rev2)
URL : https://patchwork.freedesktop.org/series/145630/
State : failure

== Summary ==

=== Applying kernel patches on branch 'drm-tip' with base: ===
Base commit: 1cf56e26a932 drm-tip: 2025y-03m-04d-00h-06m-47s UTC integration manifest === git am output follows ===
error: patch failed: drivers/gpu/drm/xe/xe_vm.c:621
error: drivers/gpu/drm/xe/xe_vm.c: patch does not apply
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Applying: drm/xe/hmm: Style- and include fixes
Applying: drm/xe/hmm: Don't dereference struct page pointers without notifier lock
Applying: drm/xe/userptr: Unmap userptrs in the mmu notifier
Patch failed at 0003 drm/xe/userptr: Unmap userptrs in the mmu notifier When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".