From: Matthew Brost <matthew.brost@intel.com>
To: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
Cc: <sashiko-reviews@lists.linux.dev>, <intel-xe@lists.freedesktop.org>
Subject: Re: [PATCH v4 3/4] drm/xe: Split the bo shrinker into purgeable and non-purgeable shrinkers
Date: Fri, 16 Oct 2026 11:41:08 -0700 [thread overview]
Message-ID: <atJvxCnNISBr2VLK@gsse-cloud1.jf.intel.com> (raw)
In-Reply-To: <4d1338d127bb9b77c8bc9a114c7c8740d2ec2780.camel@linux.intel.com>
On Fri, Oct 16, 2026 at 04:09:44PM +0200, Thomas Hellström wrote:
> On Fri, 2026-10-16 at 13:53 +0000, sashiko-bot@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 2 potential
> > issue(s) to consider:
> > - [High] drm/xe: Use-After-Free of shrinker during DRM device
> > teardown
> > - [High] drm/xe: O(N^2) CPU lockup in purgeable shrinker due to
> > uncounted LRU skips
> > --
> >
> > commit 1e9348c2fe4768de2cf24e4dd9b6188400c84043
> > Author: Thomas Hellström <thomas.hellstrom@linux.intel.com>
> >
> > drm/xe: Split the bo shrinker into purgeable and non-purgeable
> > shrinkers
> >
> > This commit splits the single xe_shrinker into two independent
> > shrinker
> > instances: one for purgeable objects and another for non-purgeable
> > (shrinkable) objects. This allows the shrinker core to apply
> > different
> > reclaim pressures based on the varying costs of reclaiming these
> > object
> > types.
> >
> > > diff --git a/drivers/gpu/drm/xe/xe_shrinker.c
> > > b/drivers/gpu/drm/xe/xe_shrinker.c
> > > index 378022c37a57c..8e3fe675e0f34 100644
> > > --- a/drivers/gpu/drm/xe/xe_shrinker.c
> > > +++ b/drivers/gpu/drm/xe/xe_shrinker.c
> >
> > [ ... ]
> >
> > > @@ -216,63 +238,52 @@ static bool xe_shrinker_runtime_pm_get(struct
> > > xe_shrinker *shrinker, bool force,
> >
> > [ ... ]
> >
> > > -static unsigned long xe_shrinker_scan(struct shrinker *shrink,
> > > struct shrink_control *sc)
> > > +static unsigned long xe_shrinker_scan_purgeable(struct shrinker
> > > *shrink,
> > > + struct
> > > shrink_control *sc)
> > > {
> > > struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
> > > struct ttm_operation_ctx ctx = {
> > > .interruptible = false,
> > > .no_wait_gpu = ttm_bo_shrink_avoid_wait(),
> > > };
> > > - unsigned long nr_to_scan, nr_scanned = 0, freed = 0;
> > > + unsigned long nr_to_scan = sc->nr_to_scan, nr_scanned = 0,
> > > freed = 0;
> > > struct xe_bo_shrink_flags shrink_flags = {
> > > .purge = true,
> > > - /* Don't request writeback without __GFP_IO. */
> > > - .writeback = !ctx.no_wait_gpu && (sc->gfp_mask &
> > > __GFP_IO),
> > > };
> > > - bool runtime_pm;
> > > - bool purgeable;
> > > - bool can_backup = !!(sc->gfp_mask & __GFP_FS);
> > > -
> > > - nr_to_scan = sc->nr_to_scan;
> > > -
> > > - read_lock(&shrinker->lock);
> > > - purgeable = !!shrinker->purgeable_pages;
> > > - read_unlock(&shrinker->lock);
> > >
> > > - /* 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)
> > > - xe_shrinker_walk(shrinker, &ctx, shrink_flags,
> > > - nr_to_scan, &nr_scanned, &freed);
> > > + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan,
> > > &nr_scanned, &freed);
> > >
> > > sc->nr_scanned = nr_scanned;
> >
> > [Severity: High]
> > Can this cause an O(N^2) CPU lockup in the purgeable shrinker?
> >
> > When the memory reclaim core (do_shrink_slab) calls this purgeable
> > scan
> > routine under memory pressure, xe_shrinker_walk() iterates the LRU
> > list.
> >
> > However, non-purgeable BOs return -EBUSY and are skipped without
> > advancing
> > the scanned counter:
> >
> > drivers/gpu/drm/xe/xe_bo.c:xe_bo_shrink() {
> > ...
> > if (!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE) ||
> > (flags.purge && !xe_tt->purgeable))
> > return -EBUSY;
> > ...
> > }
> >
> > drivers/gpu/drm/xe/xe_shrinker.c:__xe_shrinker_walk() {
> > ...
> > lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned);
> > /* Like the TTM LRU walk, skip bos we can't shrink now.
> > */
> > if (lret == -EBUSY)
> > continue;
> > ...
> > }
> >
> > Since the LRU cursor is initialized locally on each call and non-
> > purgeable
> > BOs do not increment nr_scanned, every batch rescans all leading
> > non-purgeable BOs without significantly advancing sc->nr_scanned.
> >
> > Will this force the shrinker core to repeatedly call this function,
> > processing the same leading non-purgeable BOs over and over again?
>
> This can't happen in practice since purgeable bos are always ordered
> before non-purgeable bos on the list.
>
Is this true, though? If a BO is marked as purged on a hot VM and shares
the VM's bulk-move structure, it would remain hot in the list. Also,
within an individual bulk-move list, I don't think a purgable object is
guaranteed to be at the front of the LRU.
So, at a minimum, BOs marked as purged should be moved to a lower
priority band, as this patch does [1], and reclaim should only walk the
purged-priority band [2].
We can do this in an immediate follow-up after this series merges.
Matt
[1] https://patchwork.freedesktop.org/patch/743454/?series=170454&rev=5
[2] https://patchwork.freedesktop.org/patch/743454/?series=170454&rev=5
> /Thomas
>
>
>
> >
> > > - if (nr_scanned >= nr_to_scan || !can_backup)
> > > - goto out;
> > >
> > > - /* If we didn't wake before, try to do it now if needed.
> > > */
> > > - if (!runtime_pm)
> > > - runtime_pm = xe_shrinker_runtime_pm_get(shrinker,
> > > true, 0, can_backup);
> > > + return nr_scanned ? freed : SHRINK_STOP;
> > > +}
> >
> > [ ... ]
> >
> > > @@ -294,6 +305,7 @@ static void xe_shrinker_fini(struct drm_device
> > > *drm, void *arg)
> > > xe_assert(shrinker->xe, !shrinker->purgeable_pages);
> > > xe_assert(shrinker->xe, !shrinker->populated_tts);
> > > shrinker_free(shrinker->shrink);
> > > + shrinker_free(shrinker->purge_shrink);
> > > flush_work(&shrinker->pm_worker);
> > > kfree(shrinker);
> > > }
> >
> > [Severity: High]
> > Does this kfree(shrinker) leave xe->mem.shrinker as a dangling
> > pointer?
> >
> > Because drmm actions execute in LIFO order upon device release,
> > xe_shrinker_fini() is called before xe_device_destroy().
> >
> > Later, xe_device_destroy() runs and calls xe_bo_dev_fini() which
> > flushes
> > the async_free workqueue. Pending BOs in the workqueue will have
> > their
> > final references dropped, leading to xe_ttm_tt_account_subtract():
> >
> > drivers/gpu/drm/xe/xe_bo.c:xe_ttm_tt_account_subtract() {
> > ...
> > if (xe_tt->purgeable)
> > xe_shrinker_mod_pages(xe->mem.shrinker, 0, -(long)tt-
> > >num_pages, -1);
> > else
> > xe_shrinker_mod_pages(xe->mem.shrinker, -(long)tt->num_pages,
> > 0, -1);
> > ...
> > }
> >
> > Can this lead to a use-after-free and memory corruption when driver
> > unloads?
next prev parent reply other threads:[~2026-10-16 18:41 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 13:35 [PATCH v4 0/4] drm/xe: Adjust the bo shrinker batch size Thomas Hellström
2026-10-16 13:35 ` [PATCH v4 1/4] drm/xe: Don't abort the shrinker walk on -EBUSY Thomas Hellström
2026-10-16 13:44 ` sashiko-bot
2026-10-16 13:55 ` Matthew Brost
2026-10-16 13:35 ` [PATCH v4 2/4] drm/xe: Track number of populated ttm_tts in the shrinker Thomas Hellström
2026-10-16 14:26 ` Matthew Brost
2026-10-16 13:35 ` [PATCH v4 3/4] drm/xe: Split the bo shrinker into purgeable and non-purgeable shrinkers Thomas Hellström
2026-10-16 13:53 ` sashiko-bot
2026-10-16 14:09 ` Thomas Hellström
2026-10-16 18:41 ` Matthew Brost [this message]
2026-10-16 18:16 ` Matthew Brost
2026-10-16 13:35 ` [PATCH v4 4/4] drm/xe: Update non-purgeable shrinker batch size based on average BO size Thomas Hellström
2026-10-16 13:45 ` sashiko-bot
2026-10-16 14:06 ` Thomas Hellström
2026-10-16 18:42 ` Matthew Brost
2026-10-16 18:44 ` Matthew Brost
2026-10-16 13:45 ` ✓ CI.KUnit: success for drm/xe: Adjust the bo shrinker batch size (rev4) Patchwork
2026-10-16 14:37 ` ✓ Xe.CI.BAT: " Patchwork
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=atJvxCnNISBr2VLK@gsse-cloud1.jf.intel.com \
--to=matthew.brost@intel.com \
--cc=intel-xe@lists.freedesktop.org \
--cc=sashiko-reviews@lists.linux.dev \
--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.