drm/xe: have xe_svm_range embed one drm_gpusvm_pages

With drm_gpusvm_pages now self contained, make xe stop relying
on the drm_gpusvm_range pages and take responsibility for the page
lifecycle on the driver side.

Driver side (xe):

  - Embed struct drm_gpusvm_pages in xe_svm_range and route all
    xe accesses through it instead of range->base.pages.
  - Initialise the embedded pages via drm_gpusvm_init_pages(), which
    binds the owning &xe->drm up front, and take over the page
    lifecycle: xe_svm_range_get_pages() calls drm_gpusvm_get_pages()
    directly; the notifier event_end and xe_svm_range_free() paths
    drive unmap/free on the embedded pages object.
  - Convert the open-coded userptr pages init in xe_userptr_setup()
    to the same drm_gpusvm_init_pages() helper.
  - Switch xe_svm_range_pages_valid() to drm_gpusvm_pages_valid().

Framework side (drm_gpusvm):

  - Add a small inline drm_gpusvm_init_pages() helper that records the
    owning drm_device and initialises the per-pages state, giving
    drivers a single hook to extend.
  - Export drm_gpusvm_pages_valid() to let driver owned pages
    can query mapping state without going through a range.
  - Lifecycle change: drm_gpusvm_range_remove() no longer *triggers*
    unmap/free of the embedded pages. The unmap/free logic itself stays
    in the framework -- drm_gpusvm_free_pages() still performs the DMA
    unmap (as an idempotent backstop) and frees the dma_addr array --
    but the driver now owns *when* it runs, since the driver owns the
    drm_gpusvm_pages object.

Side effect / contract: a driver that owns a drm_gpusvm_pages is now
responsible for its lifecycle: drm_gpusvm_init_pages() before first
use, and drm_gpusvm_free_pages() when the owner goes away. Xe does the
latter from its ops->range_free callback, which the framework invokes
once the range refcount drops to zero in drm_gpusvm_range_remove().
The timely DMA unmap for the IOMMU security model still happens in the
notifier invalidate path via drm_gpusvm_unmap_pages(); the unmap inside
drm_gpusvm_free_pages() is only a backstop for pages that were never
invalidated.

Suggested-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Honglei Huang <honghuan@amd.com>
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260630102127.392396-4-honghuan@amd.com
This commit is contained in:
Honglei Huang
2026-06-30 18:21:25 +08:00
committed by Matthew Brost
parent 1d0c4c15c2
commit 24c339202c
6 changed files with 74 additions and 23 deletions

View File

@@ -1228,12 +1228,17 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_free_pages);
* This function removes the specified GPU SVM range and also removes the parent
* GPU SVM notifier if no more ranges remain in the notifier. The caller must
* hold a lock to protect range and notifier removal.
*
* This function does not unmap or free the drm_gpusvm_pages, the driver owns
* that lifecycle. The caller must DMA unmap the range's pages before calling
* this function, so a range is never removed from the MMU interval tree while
* still DMA mapped. Typically the driver calls drm_gpusvm_unmap_pages() first.
* And the range_free callback's drm_gpusvm_free_pages() is a final fallback safe
* net.
*/
void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range)
{
unsigned long npages = npages_in_range(drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range));
struct drm_gpusvm_notifier *notifier;
drm_gpusvm_driver_lock_held(gpusvm);
@@ -1245,8 +1250,6 @@ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm,
return;
drm_gpusvm_notifier_lock(gpusvm);
__drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages);
__drm_gpusvm_free_pages(gpusvm, &range->pages);
__drm_gpusvm_range_remove(notifier, range);
drm_gpusvm_notifier_unlock(gpusvm);
@@ -1325,13 +1328,14 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_range_put);
*
* Return: True if GPU SVM range has valid pages, False otherwise
*/
static bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages)
bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages)
{
lockdep_assert_held(&gpusvm->notifier_lock);
return svm_pages->flags.has_devmem_pages || svm_pages->flags.has_dma_mapping;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_pages_valid);
/**
* drm_gpusvm_range_pages_valid() - GPU SVM range pages valid

View File

@@ -760,7 +760,7 @@ xe_pt_stage_bind(struct xe_tile *tile, struct xe_vma *vma,
return -EAGAIN;
}
if (xe_svm_range_has_dma_mapping(range)) {
xe_res_first_dma(range->base.pages.dma_addr, 0,
xe_res_first_dma(range->pages.dma_addr, 0,
xe_svm_range_size(range),
&curs);
xe_svm_range_debug(range, "BIND PREPARE - MIXED");

View File

@@ -66,7 +66,7 @@ static bool xe_svm_range_in_vram(struct xe_svm_range *range)
struct drm_gpusvm_pages_flags flags = {
/* Pairs with WRITE_ONCE in drm_gpusvm.c */
.__flags = READ_ONCE(range->base.pages.flags.__flags),
.__flags = READ_ONCE(range->pages.flags.__flags),
};
return flags.has_devmem_pages;
@@ -96,7 +96,7 @@ static struct xe_vm *range_to_vm(struct drm_gpusvm_range *r)
(r__)->base.gpusvm, \
xe_svm_range_in_vram((r__)) ? 1 : 0, \
xe_svm_range_has_vram_binding((r__)) ? 1 : 0, \
(r__)->base.pages.notifier_seq, \
(r__)->pages.notifier_seq, \
xe_svm_range_start((r__)), xe_svm_range_end((r__)), \
xe_svm_range_size((r__)))
@@ -115,6 +115,7 @@ xe_svm_range_alloc(struct drm_gpusvm *gpusvm)
return NULL;
INIT_LIST_HEAD(&range->garbage_collector_link);
drm_gpusvm_init_pages(&range->pages, &gpusvm_to_vm(gpusvm)->xe->drm);
xe_vm_get(gpusvm_to_vm(gpusvm));
return &range->base;
@@ -122,8 +123,10 @@ xe_svm_range_alloc(struct drm_gpusvm *gpusvm)
static void xe_svm_range_free(struct drm_gpusvm_range *range)
{
drm_gpusvm_free_pages(range->gpusvm, &(to_xe_range(range)->pages),
drm_gpusvm_range_size(range) >> PAGE_SHIFT);
xe_vm_put(range_to_vm(range));
kfree(range);
kfree(to_xe_range(range));
}
static void
@@ -134,7 +137,7 @@ xe_svm_garbage_collector_add_range(struct xe_vm *vm, struct xe_svm_range *range,
range_debug(range, "GARBAGE COLLECTOR ADD");
drm_gpusvm_range_set_unmapped(&range->base, &range->base.pages, 1,
drm_gpusvm_range_set_unmapped(&range->base, &range->pages, 1,
mmu_range);
spin_lock(&vm->svm.garbage_collector.lock);
@@ -209,7 +212,8 @@ xe_svm_range_notifier_event_end(struct xe_vm *vm, struct drm_gpusvm_range *r,
xe_svm_assert_in_notifier(vm);
drm_gpusvm_range_unmap_pages(&vm->svm.gpusvm, r, &ctx);
drm_gpusvm_unmap_pages(&vm->svm.gpusvm, &(to_xe_range(r)->pages),
drm_gpusvm_range_size(r) >> PAGE_SHIFT, &ctx);
if (!xe_vm_is_closed(vm) && mmu_range->event == MMU_NOTIFY_UNMAP)
xe_svm_garbage_collector_add_range(vm, to_xe_range(r),
mmu_range);
@@ -300,6 +304,7 @@ static void xe_svm_invalidate(struct drm_gpusvm *gpusvm,
static int __xe_svm_garbage_collector(struct xe_vm *vm,
struct xe_svm_range *range)
{
struct drm_gpusvm_ctx ctx = { .in_notifier = false, };
struct dma_fence *fence;
range_debug(range, "GARBAGE COLLECTOR");
@@ -311,6 +316,10 @@ static int __xe_svm_garbage_collector(struct xe_vm *vm,
return PTR_ERR(fence);
dma_fence_put(fence);
drm_gpusvm_unmap_pages(&vm->svm.gpusvm, &range->pages,
drm_gpusvm_range_size(&range->base) >> PAGE_SHIFT,
&ctx);
drm_gpusvm_range_remove(&vm->svm.gpusvm, &range->base);
return 0;
@@ -945,15 +954,28 @@ void xe_svm_close(struct xe_vm *vm)
*/
void xe_svm_fini(struct xe_vm *vm)
{
struct drm_gpusvm_notifier *notifier, *next;
struct drm_gpusvm_ctx ctx = { .in_notifier = false, };
xe_assert(vm->xe, xe_vm_is_closed(vm));
drm_gpusvm_for_each_notifier_safe(notifier, next, &vm->svm.gpusvm, 0, LONG_MAX) {
struct drm_gpusvm_range *range, *__next;
drm_gpusvm_for_each_range_safe(range, __next, notifier, 0, LONG_MAX)
drm_gpusvm_unmap_pages(&vm->svm.gpusvm,
&(to_xe_range(range)->pages),
drm_gpusvm_range_size(range) >> PAGE_SHIFT,
&ctx);
}
drm_gpusvm_fini(&vm->svm.gpusvm);
}
static bool xe_svm_range_has_pagemap_locked(const struct xe_svm_range *range,
const struct drm_pagemap *dpagemap)
{
return range->base.pages.dpagemap == dpagemap;
return range->pages.dpagemap == dpagemap;
}
static bool xe_svm_range_has_pagemap(struct xe_svm_range *range,
@@ -1018,7 +1040,7 @@ bool xe_svm_range_validate(struct xe_vm *vm,
if (dpagemap)
ret = ret && xe_svm_range_has_pagemap_locked(range, dpagemap);
else
ret = ret && !range->base.pages.dpagemap;
ret = ret && !range->pages.dpagemap;
xe_svm_notifier_unlock(vm);
@@ -1513,7 +1535,11 @@ int xe_svm_range_get_pages(struct xe_vm *vm, struct xe_svm_range *range,
{
int err = 0;
err = drm_gpusvm_range_get_pages(&vm->svm.gpusvm, &range->base, ctx);
err = drm_gpusvm_get_pages(&vm->svm.gpusvm, &range->pages,
vm->svm.gpusvm.mm,
&range->base.notifier->notifier,
drm_gpusvm_range_start(&range->base),
drm_gpusvm_range_end(&range->base), ctx);
if (err == -EOPNOTSUPP) {
range_debug(range, "PAGE FAULT - EVICT PAGES");
drm_gpusvm_range_evict(&vm->svm.gpusvm, &range->base);

View File

@@ -31,6 +31,8 @@ struct xe_vram_region;
struct xe_svm_range {
/** @base: base drm_gpusvm_range */
struct drm_gpusvm_range base;
/** @pages: Page/DMA mapping state for this range (single drm_device). */
struct drm_gpusvm_pages pages;
/**
* @garbage_collector_link: Link into VM's garbage collect SVM range
* list. Protected by VM's garbage collect lock.
@@ -74,7 +76,7 @@ struct xe_pagemap {
*/
static inline bool xe_svm_range_pages_valid(struct xe_svm_range *range)
{
return drm_gpusvm_range_pages_valid(range->base.gpusvm, &range->base);
return drm_gpusvm_pages_valid(range->base.gpusvm, &range->pages);
}
int xe_devm_add(struct xe_tile *tile, struct xe_vram_region *vr);
@@ -132,7 +134,7 @@ void *xe_svm_private_page_owner(struct xe_vm *vm, bool force_smem);
static inline bool xe_svm_range_has_dma_mapping(struct xe_svm_range *range)
{
lockdep_assert_held(&range->base.gpusvm->notifier_lock);
return range->base.pages.flags.has_dma_mapping;
return range->pages.flags.has_dma_mapping;
}
/**
@@ -210,10 +212,10 @@ struct xe_vram_region;
struct xe_svm_range {
struct {
struct interval_tree_node itree;
struct {
const struct drm_pagemap_addr *dma_addr;
} pages;
} base;
struct {
const struct drm_pagemap_addr *dma_addr;
} pages;
u32 tile_present;
u32 tile_invalidated;
};

View File

@@ -396,15 +396,14 @@ int xe_userptr_setup(struct xe_userptr_vma *uvma, unsigned long start,
INIT_LIST_HEAD(&userptr->invalidate_link);
INIT_LIST_HEAD(&userptr->repin_link);
drm_gpusvm_init_pages(&userptr->pages, &vm->xe->drm);
err = mmu_interval_notifier_insert(&userptr->notifier, current->mm,
start, range,
&vma_userptr_notifier_ops);
if (err)
return err;
userptr->pages.notifier_seq = LONG_MAX;
userptr->pages.drm = &vm->xe->drm;
return 0;
}

View File

@@ -310,6 +310,9 @@ void drm_gpusvm_range_put(struct drm_gpusvm_range *range);
bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range);
bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages);
int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range,
const struct drm_gpusvm_ctx *ctx);
@@ -350,6 +353,23 @@ void drm_gpusvm_free_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages,
unsigned long npages);
/**
* drm_gpusvm_init_pages() - Initialize a freshly allocated drm_gpusvm_pages
* @svm_pages: Pointer to the drm_gpusvm_pages to initialize.
* @drm: The DRM device that will own DMA mappings for this pages object.
*
* Drivers that embed one or more drm_gpusvm_pages in their own range
* structure must call this once on each pages instance after allocation,
* before the first drm_gpusvm_get_pages() / unmap / free.
*/
static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages,
struct drm_device *drm)
{
memset(svm_pages, 0, sizeof(*svm_pages));
svm_pages->drm = drm;
svm_pages->notifier_seq = LONG_MAX;
}
/**
* enum drm_gpusvm_scan_result - Scan result from the drm_gpusvm_scan_mm() function.
* @DRM_GPUSVM_SCAN_UNPOPULATED: At least one page was not present or inaccessible.