drm/xe: Userptr invalid page access fixes
Subsystems (3)
Thomas Hellström
maintainerv2 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
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
Thomas Hellström
maintainerPATCH 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.
--- 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
@@ -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
@@ -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
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.hheader, 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+Signed-off-by Thomas Hellström <[email protected]>
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@@ -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@@ -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
Re: [PATCH v2 1/3] drm/xe/hmm: Style- and include fixes
In reply to Thomas HellströmQuoted 8 lines · Thomas Hellström
Add proper #ifndef around the
xe_hmm.hheader, 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+Signed-off-by Thomas Hellström <[email protected]>
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@@ -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@@ -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
Thomas Hellström
maintainerPATCH v2 2/3 drm/xe/hmm: Don't dereference struct page pointers without notifier lock
In reply to Thomas HellströmThe 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)
--- drivers/gpu/drm/xe/xe_hmm.c | 119 ++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 26 deletions(-)
drivers/gpu/drm/xe/xe_hmm.c +93 -26
@@ -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
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+Signed-off-by Thomas Hellström <[email protected]>--- drivers/gpu/drm/xe/xe_hmm.c | 119 ++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 26 deletions(-)drivers/gpu/drm/xe/xe_hmm.c +3
-0@@ -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; } -
Thomas Hellström
maintainerRe: [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 populateuserptr")
Cc Oak Zeng <[email protected]>Cc <[email protected]> # v6.10+Signed-off-by Thomas Hellström <[email protected]>---
drivers/gpu/drm/xe/xe_hmm.c| 119 ++++++++++++++++++++++++++++---
-----1 file changed, 93 insertions(+), 26 deletions(-)drivers/gpu/drm/xe/xe_hmm.c +0
-0@@ -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 ahmm_range. dma-map pages if necessary. dma-address is save in sg tabledrivers/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_DEVICEdrivers/gpu/drm/xe/xe_hmm.c +0
-0@@ -76,38 +111,41 @@ static void xe_mark_range_accessed(structhmm_range *range, bool write) * Returns 0 if successful; -ENOMEM if fails to allocate memory */ static int xe_build_sg(struct
xe_device*xe, structhmm_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(structxe_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,
Reviewed-by Matthew Auld <[email protected]>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)
gotofree_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;
}
-
Thomas Hellström
maintainerPATCH v2 3/3 drm/xe/userptr: Unmap userptrs in the mmu notifier
In reply to Thomas HellströmIf 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.
--- 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
@@ -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
@@ -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
@@ -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
@@ -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
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+Signed-off-by Thomas Hellström <[email protected]>
Patchwork
CI✗ 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".