Couple shrinker related fixes plus a series of patches fixing several

xe_mmio_gem issues around fault handler and destroy path.
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEbSBwaO7dZQkcLOKj+mJfZA7rE8oFAmqr550ACgkQ+mJfZA7r
 E8p3UggAtIEI+BFbK7zeELK3zEtO85WQnIvUP/PprLu9Ga7Vvl+quXxZrWCHYcIE
 VwcK/5y2BGVKBmR1Vwm+PNBmtlbrNvIPGTn9u0oXB1bWVKqPT5o5MjY7lxdWhK95
 MkLFo7rsQM791DAEUeo6IzbdHd6K9k2At8Yzz5+1zt97zxGmXSNpGRxV6Ee8/crU
 e/B1cQSfY8I4sjhAormTLZ13M6vRh1Yoy5P21UdCqIU/Eq/PjpkW2bcmM6+8K+qO
 2a4y2CHIQnd+2bR4nEnuHYIa6DVuXEZIHh5v/8amenuZqBgVj7OcxUKiiZ4TnhMY
 bGe1UFyy0OWyEJ/X4ddGfCggpiFi6w==
 =0FwX
 -----END PGP SIGNATURE-----

Merge tag 'drm-xe-fixes-2026-09-17' of https://gitlab.freedesktop.org/drm/xe/kernel into drm-fixes

Couple shrinker related fixes plus a series of patches fixing several
xe_mmio_gem issues around fault handler and destroy path.

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Rodrigo Vivi <rodrigo.vivi@intel.com>
Link: https://patch.msgid.link/aqvoKaPuLLPBGayA@intel.com
This commit is contained in:
Dave Airlie 2026-09-18 10:54:57 +10:00
commit c24f824f0b
4 changed files with 145 additions and 92 deletions

View File

@ -318,8 +318,10 @@ void xe_i2c_pm_resume(struct xe_device *xe, bool d3cold)
static void xe_i2c_remove(void *data)
{
struct xe_i2c *i2c = data;
struct xe_device *xe = tile_to_xe(i2c->mmio->tile);
unsigned int i;
xe_i2c_irq_reset(xe);
xe_amc_exit(i2c);
for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) {
@ -329,6 +331,7 @@ static void xe_i2c_remove(void *data)
bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier);
xe_i2c_unregister_adapter(i2c);
xe->i2c = NULL;
}
/**

View File

@ -5,9 +5,9 @@
#include "xe_mmio_gem.h"
#include <linux/dma-resv.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_managed.h>
#include "xe_device_types.h"
@ -37,12 +37,24 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *);
struct xe_mmio_gem {
struct drm_gem_object base;
phys_addr_t phys_addr;
struct page *dummy_page; /* protected by the GEM's dma_resv */
bool destroyed; /* protected by the GEM's dma_resv */
};
static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr)
{
/*
* Forbid splitting. Together with VM_DONTEXPAND, this keeps the VMA
* matching the GEM object exactly.
*/
return -EINVAL;
}
static const struct vm_operations_struct vm_ops = {
.open = drm_gem_vm_open,
.close = drm_gem_vm_close,
.fault = xe_mmio_gem_vm_fault,
.may_split = xe_mmio_gem_vm_may_split,
};
static const struct drm_gem_object_funcs xe_mmio_gem_funcs = {
@ -121,6 +133,8 @@ static void xe_mmio_gem_free(struct drm_gem_object *base)
{
struct xe_mmio_gem *obj = to_xe_mmio_gem(base);
if (obj->dummy_page)
__free_page(obj->dummy_page);
drm_gem_object_release(base);
kfree(obj);
}
@ -128,15 +142,31 @@ static void xe_mmio_gem_free(struct drm_gem_object *base)
/**
* xe_mmio_gem_destroy - Destroy the GEM object that exposes an MMIO region
* @gem: the GEM object to destroy
* @file: DRM file descriptor previously passed to xe_mmio_gem_create()
*
* This function releases resources associated with the GEM object created by
* xe_mmio_gem_create().
*
* See: "Exposing MMIO regions to userspace"
*/
void xe_mmio_gem_destroy(struct xe_mmio_gem *gem)
void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file)
{
xe_mmio_gem_free(&gem->base);
struct drm_gem_object *base = &gem->base;
struct drm_device *dev = base->dev;
drm_vma_node_revoke(&base->vma_node, file);
dma_resv_lock(base->resv, NULL);
gem->destroyed = true;
dma_resv_unlock(base->resv);
/*
* Setting 'destroyed' under lock takes care of the subsequent faults.
* Zap the existing PTEs to cut off access to the real MMIO through
* currently mapped pages.
*/
drm_vma_node_unmap(&base->vma_node, dev->anon_inode->i_mapping);
drm_gem_object_put(base);
}
static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *vma)
@ -147,8 +177,6 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *
if ((vma->vm_flags & VM_SHARED) == 0)
return -EINVAL;
/* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 */
vma->vm_pgoff = 0;
vma->vm_page_prot = pgprot_noncached(vma_get_page_prot(vma));
vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP |
VM_DONTCOPY | VM_NORESERVE);
@ -157,51 +185,47 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *
return 0;
}
static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res)
static int alloc_dummy_page_if_needed(struct drm_gem_object *base)
{
__free_page((struct page *)res);
struct xe_mmio_gem *obj = to_xe_mmio_gem(base);
dma_resv_assert_held(base->resv);
if (!obj->dummy_page)
obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO);
return obj->dummy_page ? 0 : -ENOMEM;
}
static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma)
static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct drm_gem_object *base = vma->vm_private_data;
struct drm_device *dev = base->dev;
vm_fault_t ret = VM_FAULT_NOPAGE;
struct page *page;
struct xe_mmio_gem *obj = to_xe_mmio_gem(base);
unsigned long pfn;
unsigned long i;
page = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!page)
if (alloc_dummy_page_if_needed(base))
return VM_FAULT_OOM;
if (drmm_add_action_or_reset(dev, xe_mmio_gem_release_dummy_page, page))
return VM_FAULT_OOM;
pfn = page_to_pfn(obj->dummy_page);
pfn = page_to_pfn(page);
/* Map the entire VMA to the same dummy page */
for (i = 0; i < base->size; i += PAGE_SIZE) {
unsigned long addr = vma->vm_start + i;
ret = vmf_insert_pfn(vma, addr, pfn);
if (ret & VM_FAULT_ERROR)
break;
}
return ret;
return vmf_insert_pfn_prot(vma, vmf->address, pfn,
vm_get_page_prot(vma->vm_flags));
}
static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf)
static vm_fault_t xe_mmio_gem_vm_fault_locked(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct drm_gem_object *base = vma->vm_private_data;
struct xe_mmio_gem *obj = to_xe_mmio_gem(base);
struct drm_device *dev = base->dev;
vm_fault_t ret = VM_FAULT_NOPAGE;
unsigned long i;
unsigned long addr, pfn;
int idx;
dma_resv_assert_held(base->resv);
if (obj->destroyed)
return VM_FAULT_SIGBUS;
if (!drm_dev_enter(dev, &idx)) {
/*
* Provide a dummy page to avoid SIGBUS for events such as hot-unplug.
@ -209,18 +233,30 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf)
* It is assumed the userspace will receive the notification via some
* other channel (e.g. drm uevent).
*/
return xe_mmio_gem_vm_fault_dummy_page(vma);
return xe_mmio_gem_vm_fault_dummy_page(vmf);
}
for (i = 0; i < base->size; i += PAGE_SIZE) {
unsigned long addr = vma->vm_start + i;
unsigned long phys_addr = obj->phys_addr + i;
ret = vmf_insert_pfn(vma, addr, PHYS_PFN(phys_addr));
pfn = PHYS_PFN(obj->phys_addr);
for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) {
ret = vmf_insert_pfn(vma, addr, pfn);
if (ret & VM_FAULT_ERROR)
break;
pfn++;
}
drm_dev_exit(idx);
return ret;
}
static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct drm_gem_object *base = vma->vm_private_data;
vm_fault_t ret;
dma_resv_lock(base->resv, NULL);
ret = xe_mmio_gem_vm_fault_locked(vmf);
dma_resv_unlock(base->resv);
return ret;
}

View File

@ -15,6 +15,6 @@ struct xe_mmio_gem;
struct xe_mmio_gem *xe_mmio_gem_create(struct xe_device *xe, struct drm_file *file,
phys_addr_t phys_addr, size_t size);
u64 xe_mmio_gem_mmap_offset(struct xe_mmio_gem *gem);
void xe_mmio_gem_destroy(struct xe_mmio_gem *gem);
void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file);
#endif /* _XE_MMIO_GEM_H_ */

View File

@ -54,13 +54,40 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea
write_unlock(&shrinker->lock);
}
static s64 __xe_shrinker_walk(struct xe_device *xe,
static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker)
{
struct xe_device *xe = shrinker->xe;
if (xe_pm_runtime_get_if_active(xe))
return true;
if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) {
xe_pm_runtime_get(xe);
return true;
}
queue_work(xe->unordered_wq, &shrinker->pm_worker);
return false;
}
static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm)
{
if (runtime_pm)
xe_pm_runtime_put(shrinker->xe);
}
static int __xe_shrinker_walk(struct xe_shrinker *shrinker,
struct ttm_operation_ctx *ctx,
const struct xe_bo_shrink_flags flags,
unsigned long to_scan, unsigned long *scanned)
unsigned long to_scan, unsigned long *scanned,
unsigned long *freed)
{
struct xe_device *xe = shrinker->xe;
unsigned int mem_type;
s64 freed = 0, lret;
bool rpm = false;
int ret = 0;
s64 lret;
for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) {
struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type);
@ -74,23 +101,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe,
if (!man || !man->use_tt)
continue;
if (mem_type != XE_PL_SYSTEM && !rpm &&
xe_device_is_l2_flush_optimized(xe)) {
if (!__xe_shrinker_runtime_pm_get(shrinker))
break;
rpm = true;
}
ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) {
if (!ttm_bo_shrink_suitable(ttm_bo, ctx))
continue;
lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned);
if (lret < 0)
return lret;
if (lret < 0) {
ret = lret;
goto out;
}
freed += lret;
*freed += lret;
if (*scanned >= to_scan)
break;
goto out;
}
/* Trylocks should never error, just fail. */
xe_assert(xe, !IS_ERR(ttm_bo));
}
return freed;
out:
xe_shrinker_runtime_pm_put(shrinker, rpm);
return ret;
}
/*
@ -99,40 +138,36 @@ static s64 __xe_shrinker_walk(struct xe_device *xe,
* add writeback. This avoids stalls and explicit writebacks with light or
* moderate memory pressure.
*/
static s64 xe_shrinker_walk(struct xe_device *xe,
static int xe_shrinker_walk(struct xe_shrinker *shrinker,
struct ttm_operation_ctx *ctx,
const struct xe_bo_shrink_flags flags,
unsigned long to_scan, unsigned long *scanned)
unsigned long to_scan, unsigned long *scanned,
unsigned long *freed)
{
bool no_wait_gpu = true;
struct xe_bo_shrink_flags save_flags = flags;
s64 lret, freed;
int ret;
swap(no_wait_gpu, ctx->no_wait_gpu);
save_flags.writeback = false;
lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned);
ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned,
freed);
swap(no_wait_gpu, ctx->no_wait_gpu);
if (lret < 0 || *scanned >= to_scan)
return lret;
if (ret || *scanned >= to_scan)
return ret;
freed = lret;
if (!ctx->no_wait_gpu) {
lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned);
if (lret < 0)
return lret;
freed += lret;
if (*scanned >= to_scan)
return freed;
ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned,
freed);
if (ret || *scanned >= to_scan)
return ret;
}
if (flags.writeback) {
lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned);
if (lret < 0)
return lret;
freed += lret;
}
if (flags.writeback)
ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned,
freed);
return freed;
return ret;
}
static unsigned long
@ -180,22 +215,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force,
return false;
}
if (!xe_pm_runtime_get_if_active(xe)) {
if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) {
xe_pm_runtime_get(xe);
return true;
}
queue_work(xe->unordered_wq, &shrinker->pm_worker);
return false;
}
return true;
}
static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm)
{
if (runtime_pm)
xe_pm_runtime_put(shrinker->xe);
return __xe_shrinker_runtime_pm_get(shrinker);
}
static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
@ -214,7 +234,6 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con
bool runtime_pm;
bool purgeable;
bool can_backup = !!(sc->gfp_mask & __GFP_FS);
s64 lret;
nr_to_scan = sc->nr_to_scan;
@ -225,12 +244,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con
/* Might need runtime PM. Try to wake early if it looks like it. */
runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup);
if (purgeable && nr_scanned < nr_to_scan) {
lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags,
nr_to_scan, &nr_scanned);
if (lret >= 0)
freed += lret;
}
if (purgeable && nr_scanned < nr_to_scan)
xe_shrinker_walk(shrinker, &ctx, shrink_flags,
nr_to_scan, &nr_scanned, &freed);
sc->nr_scanned = nr_scanned;
if (nr_scanned >= nr_to_scan || !can_backup)
@ -242,10 +258,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con
shrink_flags.purge = false;
lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags,
nr_to_scan, &nr_scanned);
if (lret >= 0)
freed += lret;
xe_shrinker_walk(shrinker, &ctx, shrink_flags,
nr_to_scan, &nr_scanned, &freed);
sc->nr_scanned = nr_scanned;
out: