From: Matthew Brost <matthew.brost@intel.com>
To: "Lin, Shuicheng" <shuicheng.lin@intel.com>
Cc: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
"intel-xe@lists.freedesktop.org" <intel-xe@lists.freedesktop.org>
Subject: Re: [PATCH] drm/xe/bo: Take a runtime PM ref when shrinking a bo needing invalidation
Date: Thu, 27 Aug 2026 09:44:08 -0700 [thread overview]
Message-ID: <apBpWBAyiAPMAkMm@gsse-cloud1.jf.intel.com> (raw)
In-Reply-To: <DM4PR11MB54565CC96CAE42FB1C25434EEAAD2@DM4PR11MB5456.namprd11.prod.outlook.com>
On Thu, Aug 27, 2026 at 10:20:10AM -0600, Lin, Shuicheng wrote:
> On Wed, Aug 26, 2026 10:49 PM Matthew Brost wrote:
> > On Wed, Aug 26, 2026 at 10:10:38PM +0000, Lin, Shuicheng wrote:
> > > On Wed, Aug 26, 2026 1:01 AM Thomas Hellström wrote:
> > > > On Tue, 2026-08-25 at 22:48 +0000, Shuicheng Lin wrote:
> > > > > xe_bo_shrink() only took a runtime PM reference for the System CCS
> > > > > backup case, and only after the purgeable branch had already returned.
> > > > > That branch calls xe_bo_move_notify(), which reaches
> > > > > xe_bo_trigger_rebind() -> xe_vm_invalidate_vma() and submits a TLB
> > > > > invalidation over GuC CT. The non-purgeable branch reaches the
> > > > > same code through ttm_bo_shrink(.allow_move = true) -> xe_bo_move().
> > > > >
> > > > > When the device is runtime suspended the CT is disabled and the
> > > > > send returns -ENODEV, tripping the XE_WARN_ON() in
> > xe_bo_trigger_rebind():
> > > > >
> > > > > WARNING: drivers/gpu/drm/xe/xe_bo.c:770 at
> > > > > xe_bo_move_notify+0x1fc/0x450 [xe], CPU#2: xe_madvise/21389
> > > > > xe_bo_shrink+0x20f/0x2b0 [xe]
> > > > > __xe_shrinker_walk+0x174/0x410 [xe]
> > > > > xe_shrinker_walk+0x56/0xf0 [xe]
> > > > > xe_shrinker_scan+0x10c/0x1e0 [xe]
> > > > > do_shrink_slab+0x176/0x7e0
> > > > > shrink_slab+0x137/0x990
> > > > > drop_slab+0x7f/0x130
> > > > > drop_caches_sysctl_handler+0x9c/0xf0
> > > > >
> > > > > The invalidation is always issued for a fault-mode vm; since
> > > > > commit 4e7ebff69aed ("drm/xe/xe3p_lpg: flush shrinker bo
> > > > > cachelines
> > > > > manually")
> > > > > it is also issued for a non-fault-mode vm on hardware with an
> > > > > optimized
> > > > > L2 flush, which is how this surfaced.
> > > > >
> > > > > Add bo_needs_invalidate(), mirroring that condition, and use it in
> > > > > xe_bo_shrink() to compute needs_rpm ahead of both branches. The
> > > > > reference is then held across xe_bo_move_notify() in either path,
> > > > > and is not taken for a bo whose mappings would not have been invalidated.
> > > > >
> > > > > Shrinking can run in reclaim contexts where the device may not be
> > > > > resumed, so a bo is skipped when the reference cannot be acquired.
> > > > > Have
> > > > > that skip queue the shrinker PM worker:
> > > > > xe_shrinker_runtime_pm_get() is gated on the needs of its own
> > > > > backup pass rather than on this one, and on DGFX it returns before
> > > > > queueing anything, so without this a scan where every candidate is
> > > > > skipped makes no progress, reports nothing scanned and returns
> > SHRINK_STOP with nothing arranging a wake.
> > > > >
> > > > > Also gate the System CCS term on !xe_tt->purgeable, since
> > > > > xe_bo_shrink_purge() frees the pages without a GPU copy.
> > > > >
> > > > > Reproduced with igt@xe_madvise@dontneed-before-exec while the GPU
> > > > > is runtime suspended.
> > > > >
> > > > > Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos")
> > > > > Assisted-by: Claude:claude-opus-5
> > > > > Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
> > > > > Signed-off-by: Shuicheng Lin <shuicheng.lin@intel.com>
> > > >
> > > > Nice catch.
> > > >
> > > > I wonder, however, can we skip the TLB flush if runtime PM is not
> > > > available at TLB flush time (runtime_pm_get_if_active()?) Assuming
> > > > that if runtime PM is not available, no contexts can be active and
> > > > they will flush TLB implicitly when becoming active?
> > >
> > > Do you mean call runtime_pm_get_if_active() in
> > > xe_tlb_inval_range_tilemask_submit(),
> > > or lower down in xe_tlb_inval_fence_init()?
> > > I'd prefer that too, as it would simplify the code. Two things stop me doing it
> > here.
> > >
> > > First, the PTE zap also needs the device, and it runs before the flush.
> > > xe_pt_zap_ptes_entry() does xe_map_memset(), which asserts via
> > > xe_device_assert_mem_access():
> > >
> >
> > I agree with this - the zap is not optional for fault mode, it probably isn't for
> > 'xe_device_is_l2_flush_optimized'.
> >
> > > Assertion `!xe_pm_runtime_suspended(xe)` failed!
> > > xe_pt_zap_ptes_entry+0xb8/0x120 [xe]
> > > xe_vm_invalidate_vma_submit+0xb1/0x770 [xe]
> > > xe_bo_move_notify+0x1b3/0x450 [xe]
> > > xe_bo_shrink+0x20f/0x2b0 [xe]
> > >
> > > The zap isn't optional - the pages are being freed, so the PTEs have
> > > to be cleared either way - and xe_pt_create() uses
> > > XE_BO_FLAG_VRAM_IF_DGFX(), so on discrete it is a BAR write that needs
> > > D0. Skipping the flush alone leaves that behind.
> > >
> > > Second, I can't find the implicit flush in the code.
> > > xe_pm_runtime_resume() only calls xe_gt_resume() (do_gt_restart())
> > > when d3cold.allowed; otherwise xe_gt_runtime_resume() just takes
> > > forcewake and does xe_uc_runtime_resume() - no reset, no invalidation.
> > > That doesn't mean the TLBs survive, it may well be a property of the
> > > power state, but I can't confirm the assumption by reading the driver.
> > > Do you know if that is guaranteed? Thanks.
> > >
> > > >
> > > > IIRC It's not totally clear whether this would work for "has_ctx_tlb_inval"
> > > > hardware, and if so we might need to add something similar to this
> > > > for that hardware.
> > >
> > > My understanding is that it falls out for free if the check sits above
> > xe_tlb_inval_issue().
> > > Is it right? Thanks.
> > >
> > > Shuicheng
> > >
> > > >
> > > > Thanks,
> > > > Thomas
> > > >
> > > > > ---
> > > > > drivers/gpu/drm/xe/xe_bo.c | 62
> > > > > +++++++++++++++++++++++++++---
> > > > > --
> > > > > drivers/gpu/drm/xe/xe_shrinker.c | 18 +++++++++-
> > > > > drivers/gpu/drm/xe/xe_shrinker.h | 2 ++
> > > > > 3 files changed, 72 insertions(+), 10 deletions(-)
> > > > >
> > > > > diff --git a/drivers/gpu/drm/xe/xe_bo.c
> > > > > b/drivers/gpu/drm/xe/xe_bo.c index 2eb5d6aac523..274ff97d8542
> > > > > 100644
> > > > > --- a/drivers/gpu/drm/xe/xe_bo.c
> > > > > +++ b/drivers/gpu/drm/xe/xe_bo.c
> > > > > @@ -734,6 +734,32 @@ static int xe_ttm_io_mem_reserve(struct
> > > > > ttm_device *bdev,
> > > > > }
> > > > > }
> > > > >
> > > > > +/*
> > > > > + * Whether xe_bo_move_notify() will invalidate the GPU mappings
> > > > > +of
> > > > > @bo, and
> > > > > + * therefore needs the device resumed to reach the GuC.
> > > > > + *
> > > > > + * This mirrors the condition under which xe_bo_trigger_rebind()
> > > > > below calls
> > > > > + * xe_vm_invalidate_vma(): always for a fault-mode vm, and for
> > > > > + any
> > > > > bound vm on
> > > > > + * hardware where the L2 flush is optimized. Keep the two in sync.
> > > > > + *
> > > > > + * Context: Caller must hold the BO's dma-resv lock.
> > > > > + */
> > > > > +static bool bo_needs_invalidate(struct xe_bo *bo) {
> > > > > + struct drm_gpuvm_bo *vm_bo;
> > > > > +
> > > > > + if (xe_device_is_l2_flush_optimized(xe_bo_device(bo)))
> > > > > + return xe_bo_is_vm_bound(bo);
> > > > > +
> > > > > + xe_bo_assert_held(bo);
> > > > > +
> > > > > + drm_gem_for_each_gpuvm_bo(vm_bo, &bo->ttm.base)
> > > > > + if (xe_vm_in_fault_mode(gpuvm_to_vm(vm_bo->vm)))
> > > > > + return true;
> > > > > +
> > > > > + return false;
> > > > > +}
> > > > > +
> > > > > static int xe_bo_trigger_rebind(struct xe_device *xe, struct
> > > > > xe_bo *bo,
> > > > > const struct ttm_operation_ctx *ctx)
> > > > > {
> > > > > @@ -1361,6 +1387,28 @@ long xe_bo_shrink(struct ttm_operation_ctx
> > > > > *ctx, struct ttm_buffer_object *bo,
> > > > > if (!xe_bo_is_xe_bo(bo) || !xe_bo_get_unless_zero(xe_bo))
> > > > > return xe_bo_shrink_purge(ctx, bo, scanned);
> > > > >
> > > > > + /*
> > > > > + * Moving this bo out of a non-system placement makes
> > > > > + * xe_bo_move_notify() invalidate its GPU mappings over GuC
> > > > > CT, and
> > > > > + * System CCS needs a gpu copy when moving PL_TT ->
> > > > > PL_SYSTEM. Both
> > > > > + * need the device resumed.
> > > > > + */
> > > > > + needs_rpm = bo->resource->mem_type != XE_PL_SYSTEM &&
> > > > > + (bo_needs_invalidate(xe_bo) ||
> > > > > + (!xe_tt->purgeable && !IS_DGFX(xe) &&
> > > > > + xe_bo_needs_ccs_pages(xe_bo)));
> >
> > This is a complex enough conditional that my current feeling is to just
> > unconditionally set:
> >
> > needs_rpm = true;
>
> How about keep the XE_PL_SYSTEM check?
>
I think checking system is reasonable, and better than my suggestion, as
this implies we don't have GPU pages (no invalidation) and no need for a
CCS copy, and is keep check which is easy to understand.
> /* Both the invalidation and the System CCS copy need the device. */
> needs_rpm = bo->resource->mem_type != XE_PL_SYSTEM;
>
> @Thomas Hellström you wrote the original code, what do you think?
>
Yes, let's see what Thomas says.
Matt
> >
> > In practice, on iGPUs where the display is in use, we always hold a runtime PM
> > reference. It would be very unusual for the display to be suspended while the
> > shrinker is running, at least as far as I can tell.
> > Let's drop this hard-to-understand, likely overengineered conditional.
>
> I could only reproduce the issue with display disabled. And the CI machine that hits it has no monitor connected.
>
> Shuicheng
>
> >
> > Thoughts?
> >
> > Matt
> >
> > > > > + if (needs_rpm && !xe_pm_runtime_get_if_active(xe)) {
> > > > > + /*
> > > > > + * Resuming is not allowed from all reclaim
> > > > > contexts, so leave
> > > > > + * this bo alone and ask for the device to be woken
> > > > > up outside
> > > > > + * of reclaim. xe_shrinker_runtime_pm_get() is gated
> > > > > on the
> > > > > + * needs of its own backup pass rather than on ours,
> > > > > and on
> > > > > + * DGFX it returns before queueing anything, so ask
> > > > > here.
> > > > > + */
> > > > > + xe_shrinker_queue_pm(xe->mem.shrinker);
> > > > > + goto out_unref;
> > > > > + }
> > > > > +
> > > > > if (xe_tt->purgeable) {
> > > > > if (bo->resource->mem_type != XE_PL_SYSTEM)
> > > > > lret = xe_bo_move_notify(xe_bo, ctx); @@ -1369,28
> > > > +1417,24 @@ long
> > > > > xe_bo_shrink(struct ttm_operation_ctx *ctx, struct
> > > > > ttm_buffer_object *bo,
> > > > > if (lret > 0 && xe_bo_madv_is_dontneed(xe_bo))
> > > > > xe_bo_set_purgeable_state(xe_bo,
> > > > >
> > > > > XE_MADV_PURGEABLE_PURGED);
> > > > > - goto out_unref;
> > > > > + goto out_put_rpm;
> > > > > }
> > > > >
> > > > > - /* System CCS needs gpu copy when moving PL_TT -> PL_SYSTEM
> > > > > */
> > > > > - needs_rpm = (!IS_DGFX(xe) && bo->resource->mem_type !=
> > > > > XE_PL_SYSTEM &&
> > > > > - xe_bo_needs_ccs_pages(xe_bo));
> > > > > - if (needs_rpm && !xe_pm_runtime_get_if_active(xe))
> > > > > - goto out_unref;
> > > > > -
> > > > > *scanned += tt->num_pages;
> > > > > lret = ttm_bo_shrink(ctx, bo, (struct ttm_bo_shrink_flags)
> > > > > {.purge = false,
> > > > > .writeback = flags.writeback,
> > > > > .allow_move = true});
> > > > > - if (needs_rpm)
> > > > > - xe_pm_runtime_put(xe);
> > > > >
> > > > > if (lret > 0) {
> > > > > xe_ttm_tt_account_subtract(xe, tt);
> > > > > update_global_total_pages(bo->bdev, -(long)tt-
> > > > > >num_pages);
> > > > > }
> > > > >
> > > > > +out_put_rpm:
> > > > > + if (needs_rpm)
> > > > > + xe_pm_runtime_put(xe);
> > > > > +
> > > > > out_unref:
> > > > > xe_bo_put(xe_bo);
> > > > >
> > > > > diff --git a/drivers/gpu/drm/xe/xe_shrinker.c
> > > > > b/drivers/gpu/drm/xe/xe_shrinker.c
> > > > > index 83374cd57660..fb6ec77972a5 100644
> > > > > --- a/drivers/gpu/drm/xe/xe_shrinker.c
> > > > > +++ b/drivers/gpu/drm/xe/xe_shrinker.c
> > > > > @@ -54,6 +54,22 @@ xe_shrinker_mod_pages(struct xe_shrinker
> > > > > *shrinker, long shrinkable, long purgea
> > > > > write_unlock(&shrinker->lock);
> > > > > }
> > > > >
> > > > > +/**
> > > > > + * xe_shrinker_queue_pm() - Ask for the device to be woken up for
> > > > > shrinking
> > > > > + * @shrinker: Pointer to the struct xe_shrinker.
> > > > > + *
> > > > > + * Queue a worker that takes and drops a runtime PM reference.
> > > > > Shrinking can
> > > > > + * be called from reclaim context, where resuming the device is
> > > > > + not
> > > > > always
> > > > > + * allowed, so a caller that needs the device resumed but could
> > > > > + not
> > > > > acquire a
> > > > > + * reference uses this to have it woken up outside of reclaim.
> > > > > + The
> > > > > current
> > > > > + * scan makes no progress on the affected buffer objects, but a
> > > > > subsequent one
> > > > > + * can.
> > > > > + */
> > > > > +void xe_shrinker_queue_pm(struct xe_shrinker *shrinker) {
> > > > > + queue_work(shrinker->xe->unordered_wq, &shrinker-
> > > > > >pm_worker);
> > > > > +}
> > > > > +
> > > > > static s64 __xe_shrinker_walk(struct xe_device *xe,
> > > > > struct ttm_operation_ctx *ctx,
> > > > > const struct xe_bo_shrink_flags flags, @@ -185,7
> > > > +201,7 @@
> > > > > static bool xe_shrinker_runtime_pm_get(struct xe_shrinker
> > > > > *shrinker, bool force,
> > > > > xe_pm_runtime_get(xe);
> > > > > return true;
> > > > > }
> > > > > - queue_work(xe->unordered_wq, &shrinker->pm_worker);
> > > > > + xe_shrinker_queue_pm(shrinker);
> > > > > return false;
> > > > > }
> > > > >
> > > > > diff --git a/drivers/gpu/drm/xe/xe_shrinker.h
> > > > > b/drivers/gpu/drm/xe/xe_shrinker.h
> > > > > index 5132ae5192e1..86d2a322cadd 100644
> > > > > --- a/drivers/gpu/drm/xe/xe_shrinker.h
> > > > > +++ b/drivers/gpu/drm/xe/xe_shrinker.h
> > > > > @@ -11,6 +11,8 @@ struct xe_device;
> > > > >
> > > > > void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long
> > > > > shrinkable, long purgeable);
> > > > >
> > > > > +void xe_shrinker_queue_pm(struct xe_shrinker *shrinker);
> > > > > +
> > > > > int xe_shrinker_create(struct xe_device *xe);
> > > > >
> > > > > #endif
next prev parent reply other threads:[~2026-08-27 16:44 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 22:48 [PATCH] drm/xe/bo: Take a runtime PM ref when shrinking a bo needing invalidation Shuicheng Lin
2026-08-25 22:56 ` ✓ CI.KUnit: success for " Patchwork
2026-08-25 23:57 ` ✗ Xe.CI.BAT: failure " Patchwork
2026-08-26 1:50 ` ✓ Xe.CI.FULL: success " Patchwork
2026-08-26 8:00 ` [PATCH] " Thomas Hellström
2026-08-26 22:10 ` Lin, Shuicheng
2026-08-27 5:48 ` Matthew Brost
2026-08-27 16:20 ` Lin, Shuicheng
2026-08-27 16:44 ` Matthew Brost [this message]
2026-08-28 14:07 ` Thomas Hellström
2026-08-28 16:51 ` Lin, Shuicheng
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=apBpWBAyiAPMAkMm@gsse-cloud1.jf.intel.com \
--to=matthew.brost@intel.com \
--cc=intel-xe@lists.freedesktop.org \
--cc=shuicheng.lin@intel.com \
--cc=thomas.hellstrom@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.