All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
	intel-xe@lists.freedesktop.org
Cc: dri-devel@lists.freedesktop.org
Subject: Re: [PATCH 4/4] drm/ttm, drm/xe: Gate reserve-dipping backup retries on may_writeback
Date: Wed, 04 Nov 2026 20:41:03 +0000	[thread overview]
Message-ID: <sashiko-outbox-198026@kernel.org> (raw)
In-Reply-To: <20261104201331.2462-5-thomas.hellstrom@linux.intel.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] drm/ttm: unsplit high-order retry drains reserves in ttm_pool_backup()
- [High] drm/xe: premature scan termination in xe_shrinker_walk() and xe_bo_shrink()
- [High] drm/ttm: corrupted caching and DMA state in ttm_pool_backup() on page-0 failure
--

commit 40d7831061e17e7b2e436a106ab6986d2645fa67
Author: Thomas Hellström <thomas.hellstrom@linux.intel.com>

drm/ttm, drm/xe: Gate reserve-dipping backup retries on may_writeback

This commit gates shmem backup retries that dip into kernel memory reserves on
a new may_writeback flag while separating direct swap writeback into
force_writeback, and threads both flags through TTM and the Xe shrinker.

> diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c
> index 8f05257e4f542..87c1ac39af94e 100644
> --- a/drivers/gpu/drm/ttm/ttm_pool.c
> +++ b/drivers/gpu/drm/ttm/ttm_pool.c
[ ... ]
> @@ -1256,7 +1260,9 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
>  	else
>  		gfp = GFP_HIGHUSER;
>  
> -	alloc_gfp = GFP_KERNEL | __GFP_HIGH | __GFP_NOWARN | __GFP_RETRY_MAYFAIL;
> +	/* Don't dip into kernel reserves unless @flags->may_writeback is set. */
> +	alloc_gfp = GFP_KERNEL | __GFP_HIGH | __GFP_NOWARN |
> +		    __GFP_RETRY_MAYFAIL | __GFP_NOMEMALLOC;

[Severity: High]
When ttm_pool_backup() is called with flags->may_writeback set to false (such
as the initial pass of xe_shrinker_walk()), can an -ENOMEM failure on the
very first page leave tt with corrupted caching and DMA state?

Before entering the backup loop, ttm_pool_backup() converts all resident pages
of a non-partial tt with tt->caching != ttm_cached to write-back via
set_pages_array_wb(), and unmaps and zeroes all tt->dma_address[] entries:

drivers/gpu/drm/ttm/ttm_pool.c:ttm_pool_backup() {
	if (!partial && tt->caching != ttm_cached) {
		...
		if (run_count)
			set_pages_array_wb(&tt->pages[run_start], run_count);
	}
	...
	if (tt->dma_address || flags->purge) {
		...
			if (tt->dma_address && tt->dma_address[i]) {
				ttm_pool_unmap(pool, tt->dma_address[i],
					       num_pages);
				memset(&tt->dma_address[i], 0,
				       num_pages * sizeof(*tt->dma_address));
			}
	}
}

Because alloc_gfp now includes __GFP_NOMEMALLOC for all page orders (including
order 0) and does not retry when flags->may_writeback is false,
shmem_read_folio_gfp() can fail with -ENOMEM on the first page (i == 0,
nr_backed == 0, shrunken == 0), causing ttm_pool_backup() to return -ENOMEM.

In ttm_tt_backup(), tt->page_flags is only updated when ret > 0:

drivers/gpu/drm/ttm/ttm_tt.c:ttm_tt_backup() {
	ret = ttm_pool_backup(&bdev->pool, tt, &flags);
	if (ret > 0) {
		tt->page_flags &= ~TTM_TT_FLAG_PRIV_POPULATED;
		...
		if (tt->remaining_pages) {
			tt->page_flags &= ~TTM_TT_FLAG_BACKED_UP;
			tt->page_flags |= TTM_TT_FLAG_PARTIAL;
		}
		...
	}
}

With TTM_TT_FLAG_PRIV_POPULATED still set and TTM_TT_FLAG_PARTIAL clear, won't
subsequent calls to ttm_tt_populate() see ttm_tt_is_populated(tt) as true and
return 0 without restoring WC/UC caching or DMA mappings, while
ttm_tt_unpopulate() -> ttm_pool_free() returns the write-back pages directly
into the WC/UC page pool and calls ttm_pool_unmap() on the zeroed
tt->dma_address[] entries?

>  
>  	num_pages = tt->num_pages;
>  
> @@ -1280,14 +1286,7 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
>  		order = ttm_pool_page_order(pool, page);
>  		npages = 1UL << order;
>  
> -		/*
> -		 * We don't allow dipping kernel reserves for high order backup
> -		 */
> -		if (order)
> -			alloc_gfp |= __GFP_NOMEMALLOC;
> -		else
> -			alloc_gfp &= ~__GFP_NOMEMALLOC;
> -
> +		dip_reserves = false;
>  		/*
>  		 * Back up the compound atomically at its native order. If
>  		 * fault injection truncated num_pages mid-compound, skip
> @@ -1296,10 +1295,18 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
>  		if (unlikely(i + npages > num_pages))
>  			break;
>  
> +retry:
>  		handle = ttm_backup_backup_folio(backup, page_folio(page),
> -						 order, flags->writeback, i,
> -						 gfp, alloc_gfp,
> -						 &nr_backed);
> +						 order, flags->force_writeback, i,
> +						 gfp, dip_reserves ?
> +						 alloc_gfp & ~__GFP_NOMEMALLOC :
> +						 alloc_gfp, &nr_backed);
> +		if (unlikely(handle == -ENOMEM && !nr_backed &&
> +			     flags->may_writeback && !dip_reserves)) {
> +			dip_reserves = true;
> +			goto retry;
> +		}

[Severity: High]
If ttm_backup_backup_folio() fails with -ENOMEM on the first subpage
(!nr_backed) of a high-order compound page (order > 0), doesn't jumping
directly to retry re-invoke ttm_backup_backup_folio() at the full compound
order with __GFP_NOMEMALLOC cleared instead of splitting the compound into
order-0 pages first?

When system free memory is below normal zone watermarks during reclaim, the
initial __GFP_NOMEMALLOC allocation fails on subpage 0 with handle == -ENOMEM
and nr_backed == 0. Because ttm_pool_split_for_swap() is only called further
down when nr_backed > 0 and nr_backed < npages, retrying at the original
order forces ttm_backup_backup_folio() to allocate up to 1 << order shmem
pages from kernel memory reserves before returning to ttm_pool_backup() where
the source compound page can finally be freed.

Should this path split a high-order page via ttm_pool_split_for_swap(pool,
page) and reset order = 0 and npages = 1 before retrying with reserves, as
described in the struct ttm_backup_flags kerneldoc for may_writeback, so each
order-0 subpage is backed up and freed before allocating the next shmem page?

[ ... ]
> diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c
> index c86455ea08d18..c24f35cbc2358 100644
> --- a/drivers/gpu/drm/xe/xe_shrinker.c
> +++ b/drivers/gpu/drm/xe/xe_shrinker.c
> @@ -171,6 +171,7 @@ static int __xe_shrinker_walk(struct xe_shrinker *shrinker,
>  static int xe_shrinker_walk(struct xe_shrinker *shrinker,
>  			    struct ttm_operation_ctx *ctx,
>  			    const struct xe_bo_shrink_flags flags,
> +			    bool allow_writeback,
>  			    unsigned long to_scan, unsigned long *scanned,
>  			    unsigned long *freed)
>  {
> @@ -179,7 +180,8 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker,
>  	int ret;
>  
>  	swap(no_wait_gpu, ctx->no_wait_gpu);
> -	save_flags.writeback = false;
> +	save_flags.may_writeback = false;
> +	save_flags.force_writeback = false;
>  	ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned,
>  				 freed);
>  	swap(no_wait_gpu, ctx->no_wait_gpu);
> @@ -187,15 +189,32 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker,
>  		return ret;
>  
>  	if (!ctx->no_wait_gpu) {
> +		/*
> +		 * Blocking is now tolerated: allow retrying a failed backup
> +		 * allocation using kernel reserves, but only if writeback is
> +		 * permitted at all in this gfp context (i.e. __GFP_IO is
> +		 * set); see @allow_writeback in xe_shrinker_scan().
> +		 */
> +		save_flags.may_writeback = allow_writeback;
>  		ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned,
>  					 freed);
>  		if (ret || *scanned >= to_scan)
>  			return ret;
>  	}
>  
> -	if (flags.writeback)
> -		ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned,
> +	if (allow_writeback) {
> +		struct xe_bo_shrink_flags wb_flags = flags;
> +
> +		/*
> +		 * Writeback explicitly allowed: also force writeback on any
> +		 * retry so it actually relieves memory pressure instead of
> +		 * just growing dirty shmem.
> +		 */
> +		wb_flags.may_writeback = true;
> +		wb_flags.force_writeback = true;
> +		ret = __xe_shrinker_walk(shrinker, ctx, wb_flags, to_scan, scanned,
>  					 freed);
> +	}

[Severity: High]
Can xe_shrinker_walk() terminate after the first pass with 0 freed pages when
__GFP_NOMEMALLOC shmem allocations fail under memory pressure, without ever
reaching the passes that set may_writeback or force_writeback?

In xe_bo_shrink(), *scanned is incremented by the full old_remaining page
count before calling ttm_bo_shrink():

drivers/gpu/drm/xe/xe_bo.c:xe_bo_shrink() {
	old_remaining = ttm_tt_remaining_pages(tt);
	*scanned += old_remaining;
	lret = ttm_bo_shrink(ctx, bo, (struct ttm_bo_shrink_flags)
			     {.purge = false,
			      .allow_move = true,
			      .may_writeback = flags.may_writeback,
			      .force_writeback = flags.force_writeback});
}

During the first pass of xe_shrinker_walk() (where save_flags.may_writeback is
false), if shmem_read_folio_gfp() with __GFP_NOMEMALLOC fails with -ENOMEM on
an idle BO, ttm_pool_backup() stops without retrying and ttm_bo_shrink()
returns -EBUSY (or a partial count if it stopped mid-BO). In
__xe_shrinker_walk(), lret == -EBUSY continues to the next BO on the LRU
while keeping old_remaining added to *scanned. Once Pass 1 returns,
*scanned >= to_scan is satisfied and xe_shrinker_walk() returns immediately
without running Pass 2 or Pass 3.

Additionally, if Pass 1 visits fewer than to_scan pages in idle BOs and
reaches Pass 2 with allow_writeback set to true, Pass 2 runs with
save_flags.may_writeback = true and save_flags.force_writeback = false. Won't
Pass 2 dip into kernel memory reserves to grow dirty shmem without writeback
and satisfy *scanned >= to_scan before Pass 3 (where force_writeback = true)
can run?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104201331.2462-1-thomas.hellstrom@linux.intel.com?part=4

  reply	other threads:[~2026-11-04 20:41 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 20:13 [PATCH 0/4] drm/ttm, drm/xe: Support partially backed-up ttm_tts Thomas Hellström
2026-11-04 20:13 ` [PATCH 1/4] drm/ttm: Don't mark backed-up shmem folios accessed Thomas Hellström
2026-11-04 20:13 ` [PATCH 2/4] drm/xe/bo: Pass page and tt counts to the shrinker account helpers Thomas Hellström
2026-11-04 20:13 ` [PATCH 3/4] drm/ttm, drm/xe: Introduce TTM_TT_FLAG_PARTIAL for partially backed-up tts Thomas Hellström
2026-11-04 20:47   ` sashiko-bot
2026-11-05  4:50   ` kernel test robot
2026-11-04 20:13 ` [PATCH 4/4] drm/ttm, drm/xe: Gate reserve-dipping backup retries on may_writeback Thomas Hellström
2026-11-04 20:41   ` sashiko-bot [this message]
2026-11-04 20:23 ` ✓ CI.KUnit: success for drm/ttm, drm/xe: Support partially backed-up ttm_tts Patchwork
2026-11-04 20:57 ` ✓ Xe.CI.BAT: " Patchwork
2026-11-05  4:36 ` ✓ CI.KUnit: success for drm/ttm, drm/xe: Support partially backed-up ttm_tts (rev2) Patchwork
2026-11-05  5:13 ` ✓ 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=sashiko-outbox-198026@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --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.