Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
To: intel-xe@lists.freedesktop.org
Cc: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
	"Natalie Vock" <nat@pixelcluster.dev>,
	"Arun Pravin" <arunpravin.paneerselvam@amd.com>,
	"Matthew Auld" <matthew.auld@intel.com>,
	"Matthew Brost" <matthew.brost@intel.com>,
	"Niranjana Vishwanathapura" <niranjana.vishwanathapura@intel.com>,
	dri-devel@lists.freedesktop.org
Subject: [PATCH 3/4] drm/ttm, drm/xe: Introduce TTM_TT_FLAG_PARTIAL for partially backed-up tts
Date: Wed,  4 Nov 2026 21:13:30 +0100	[thread overview]
Message-ID: <20261104201331.2462-4-thomas.hellstrom@linux.intel.com> (raw)
In-Reply-To: <20261104201331.2462-1-thomas.hellstrom@linux.intel.com>

Backup failures can leave a ttm_tt partially backed up: some pages
swapped to shmem while others remain resident. Previously,
ttm_tt_backup() would set TTM_TT_FLAG_BACKED_UP regardless, which
made the resource unevictable and prevented the shrinker from
targeting it for continued backup.

Introduce TTM_TT_FLAG_PARTIAL to distinguish this state. A partial tt:
- Does NOT have PRIV_POPULATED set (it's not fully populated)
- Does NOT have BACKED_UP set (it's not fully backed up)
- Stays on the normal LRU (not unevictable)
- Can be targeted by the shrinker for continued backup
- Must be restored (not plain-allocated) when repopulating

On re-entry, ttm_pool_backup() skips already-backed-up handles and
the caching/DMA setup that was done on the first pass.

Track the number of still-resident pages in ttm_tt::remaining_pages.
ttm_pool_restore_abort() recounts them, since an aborted restore may
leave more pages resident than before. xe accounts shrinker pages of
a partial tt by remaining_pages: shrinking subtracts the change in
remaining_pages rather than tt->num_pages, repopulating adds back only
the pages that were subtracted, and destroying a partial tt subtracts
the pages still resident. A tt stays counted as populated by the
shrinker while it has accounted pages.

If restoring a fully backed-up tt fails after making pages resident,
mark it partial and return the bo to the evictable LRU, so that the
shrinker can reclaim those pages.

xe must treat a partial tt as holding data when moving the bo, so that
moves out of system memory restore it rather than discarding it.

Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 drivers/gpu/drm/ttm/ttm_bo.c            |   5 +-
 drivers/gpu/drm/ttm/ttm_bo_util.c       |   6 +-
 drivers/gpu/drm/ttm/ttm_pool.c          | 128 ++++++++++++++++++++++--
 drivers/gpu/drm/ttm/ttm_pool_internal.h |   2 +
 drivers/gpu/drm/ttm/ttm_tt.c            |  36 ++++++-
 drivers/gpu/drm/xe/xe_bo.c              |  82 +++++++++++----
 include/drm/ttm/ttm_tt.h                |  45 +++++++--
 7 files changed, 261 insertions(+), 43 deletions(-)

diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c
index 53c68e29ebc2..3c2f4297e20a 100644
--- a/drivers/gpu/drm/ttm/ttm_bo.c
+++ b/drivers/gpu/drm/ttm/ttm_bo.c
@@ -1524,9 +1524,8 @@ int ttm_bo_populate(struct ttm_buffer_object *bo,
 
 	swapped = ttm_tt_is_swapped(tt);
 	ret = ttm_tt_populate(bdev, tt, ctx);
-	if (ret)
-		return ret;
 
+	/* A failed restore may leave the tt partial, hence evictable. */
 	if (swapped && !ttm_tt_is_swapped(tt) && !bo->pin_count &&
 	    bo->resource) {
 		spin_lock(&bdev->lru_lock);
@@ -1535,7 +1534,7 @@ int ttm_bo_populate(struct ttm_buffer_object *bo,
 		spin_unlock(&bdev->lru_lock);
 	}
 
-	return 0;
+	return ret;
 }
 EXPORT_SYMBOL(ttm_bo_populate);
 
diff --git a/drivers/gpu/drm/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c
index f9458a4d5a09..975dea33075f 100644
--- a/drivers/gpu/drm/ttm/ttm_bo_util.c
+++ b/drivers/gpu/drm/ttm/ttm_bo_util.c
@@ -1124,7 +1124,7 @@ long ttm_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
 			     {.purge = flags.purge,
 			      .writeback = flags.writeback});
 
-	if (lret > 0) {
+	if (lret > 0 && !ttm_tt_is_partial(bo->ttm)) {
 		spin_lock(&bdev->lru_lock);
 		ttm_resource_del_bulk_move_unevictable(bo->resource, bo);
 		ttm_resource_move_to_lru_tail(bo->resource);
@@ -1151,7 +1151,9 @@ EXPORT_SYMBOL(ttm_bo_shrink);
  */
 bool ttm_bo_shrink_suitable(struct ttm_buffer_object *bo, struct ttm_operation_ctx *ctx)
 {
-	return bo->ttm && ttm_tt_is_populated(bo->ttm) && !bo->pin_count &&
+	return bo->ttm &&
+		(ttm_tt_is_populated(bo->ttm) || ttm_tt_is_partial(bo->ttm)) &&
+		!bo->pin_count &&
 		(!ctx->no_wait_gpu ||
 		 dma_resv_test_signaled(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP));
 }
diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c
index 1bf37023fed6..8f05257e4f54 100644
--- a/drivers/gpu/drm/ttm/ttm_pool.c
+++ b/drivers/gpu/drm/ttm/ttm_pool.c
@@ -628,7 +628,8 @@ static int ttm_pool_restore_commit(struct ttm_pool_tt_restore *restore,
 			 * using clever logic, but ATM we don't care, as
 			 * we prioritize releasing memory ASAP. Note that
 			 * here, the old retained page is always write-back
-			 * cached.
+			 * cached and not DMA-mapped. The commit below
+			 * installs the new page and its DMA address.
 			 */
 			ttm_pool_split_for_swap(restore->pool, p);
 			copy_highpage(restore->alloced_page + i, p);
@@ -895,7 +896,7 @@ int ttm_pool_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
 {
 	struct ttm_pool_alloc_state alloc;
 
-	if (WARN_ON(ttm_tt_is_backed_up(tt)))
+	if (WARN_ON(ttm_tt_is_backed_up(tt) || ttm_tt_is_partial(tt)))
 		return -EINVAL;
 
 	ttm_pool_alloc_state_init(tt, &alloc);
@@ -924,7 +925,7 @@ int ttm_pool_restore_and_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
 	struct ttm_pool_alloc_state alloc;
 	int ret;
 
-	if (WARN_ON(!ttm_tt_is_backed_up(tt)))
+	if (WARN_ON(!ttm_tt_is_backed_up(tt) && !ttm_tt_is_partial(tt)))
 		return -EINVAL;
 
 	if (!restore) {
@@ -967,6 +968,23 @@ int ttm_pool_restore_and_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
 	return __ttm_pool_alloc(pool, tt, ctx, &alloc, restore);
 }
 
+/**
+ * ttm_pool_restore_has_resident() - Whether an interrupted restore has
+ * made pages resident
+ * @tt: The struct ttm_tt.
+ *
+ * Return: true if an interrupted restore of @tt has committed or
+ * restored pages that would be kept on ttm_pool_restore_abort().
+ */
+bool ttm_pool_restore_has_resident(const struct ttm_tt *tt)
+{
+	const struct ttm_pool_tt_restore *restore = tt->restore;
+
+	return restore && (restore->alloced_pages ||
+			   (ttm_pool_restore_valid(restore) &&
+			    restore->restored_pages));
+}
+
 /**
  * ttm_pool_free - Free the backing pages from a ttm_tt object
  *
@@ -1000,7 +1018,7 @@ void ttm_pool_drop_backed_up(struct ttm_tt *tt)
 	struct ttm_pool_tt_restore *restore;
 	pgoff_t start_page = 0;
 
-	WARN_ON(!ttm_tt_is_backed_up(tt));
+	WARN_ON(!ttm_tt_is_backed_up(tt) && !ttm_tt_is_partial(tt));
 
 	restore = tt->restore;
 
@@ -1039,6 +1057,91 @@ void ttm_pool_drop_backed_up(struct ttm_tt *tt)
 	ttm_pool_free_range(NULL, tt, ttm_cached, start_page, tt->num_pages);
 }
 
+/**
+ * ttm_pool_restore_abort() - Abort an ongoing restore of a partial tt
+ * @tt: The struct ttm_tt with an active restore.
+ *
+ * Abort a partially completed restore, leaving the tt in a consistent
+ * state for a subsequent backup. The uncommitted in-flight page is
+ * DMA-unmapped and split; restored sub-pages are kept, unused ones freed.
+ * Non-cached committed pages are converted to write-back so the
+ * partial-backup invariant (all resident pages are cached) is maintained.
+ * Committed pages are left DMA-mapped for the backup path to handle.
+ * Since restored pages are now resident, &ttm_tt.remaining_pages is
+ * updated to reflect the number of resident pages.
+ */
+static void ttm_pool_restore_abort(struct ttm_tt *tt)
+{
+	struct ttm_pool_tt_restore *restore = tt->restore;
+	pgoff_t i, nr, mid;
+
+	if (!restore)
+		return;
+
+	/* Handle the uncommitted in-flight page. */
+	if (ttm_pool_restore_valid(restore)) {
+		struct page *page = restore->alloced_page;
+
+		nr = 1UL << restore->order;
+
+		if (tt->dma_address)
+			ttm_pool_unmap(restore->pool, restore->first_dma, nr);
+
+#ifdef CONFIG_X86
+		if (restore->page_caching != ttm_cached && !PageHighMem(page))
+			set_pages_wb(page, nr);
+#endif
+		ttm_pool_split_for_swap(restore->pool, page);
+
+		for (i = 0; i < restore->restored_pages; i++)
+			restore->snapshot_alloc.pages[i] = page + i;
+
+		/*
+		 * Clear stale DMA addresses for the in-flight sub-pages.
+		 * These pages were never committed via
+		 * ttm_pool_allocated_page_commit(), so tt->dma_address[]
+		 * contains stale values that the backup path would
+		 * incorrectly try to unmap. All @nr entries are stale,
+		 * including restored ones: commit is all-or-nothing per
+		 * compound, and the whole compound was unmapped above.
+		 */
+		if (tt->dma_address) {
+			pgoff_t off = restore->snapshot_alloc.pages -
+				      tt->pages;
+
+			memset(&tt->dma_address[off], 0,
+			       nr * sizeof(*tt->dma_address));
+		}
+
+		for (i = restore->restored_pages; i < nr; i++)
+			__free_pages_gpu_account(page + i, 0, false);
+	}
+
+	/*
+	 * Don't DMA-unmap committed restored pages here; their DMA
+	 * addresses are in tt->dma_address[] and the backup path
+	 * will unmap them.
+	 */
+
+	/* Convert non-cached committed pages to write-back. */
+	mid = restore->snapshot_alloc.caching_divide - tt->pages;
+#ifdef CONFIG_X86
+	if (mid > 0 && tt->caching != ttm_cached)
+		set_pages_array_wb(tt->pages, mid);
+#endif
+
+	kfree(restore);
+	tt->restore = NULL;
+
+	for (i = 0, nr = 0; i < tt->num_pages; i++) {
+		struct page *p = tt->pages[i];
+
+		if (p && !ttm_backup_page_ptr_is_handle(p))
+			nr++;
+	}
+	tt->remaining_pages = nr;
+}
+
 /**
  * ttm_pool_backup() - Back up or purge a struct ttm_tt
  * @pool: The pool used when allocating the struct ttm_tt.
@@ -1071,10 +1174,13 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
 	pgoff_t shrunken = 0;
 	pgoff_t i, j, num_pages, npages;
 	pgoff_t nr_backed;
+	bool partial = ttm_tt_is_partial(tt);
 
 	if (WARN_ON(ttm_tt_is_backed_up(tt)))
 		return -EINVAL;
 
+	ttm_pool_restore_abort(tt);
+
 	if ((!ttm_backup_bytes_avail() && !flags->purge) ||
 	    ttm_pool_uses_dma_alloc(pool) || ttm_tt_is_backed_up(tt))
 		return -EBUSY;
@@ -1082,8 +1188,9 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
 #ifdef CONFIG_X86
 	/* Anything returned to the system needs to be cached. Walk allocations
 	 * skipping NULL pages and issue set_pages_array_wb() per contiguous run.
+	 * Resident pages of a partial tt are already cached.
 	 */
-	if (tt->caching != ttm_cached) {
+	if (!partial && tt->caching != ttm_cached) {
 		pgoff_t run_start = 0, run_count = 0;
 
 		for (i = 0; i < tt->num_pages; i += num_pages) {
@@ -1119,9 +1226,18 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
 
 			order = ttm_pool_page_order(pool, page);
 			num_pages = 1UL << order;
-			if (tt->dma_address)
+			if (tt->dma_address && tt->dma_address[i]) {
 				ttm_pool_unmap(pool, tt->dma_address[i],
 					       num_pages);
+				/*
+				 * Don't unmap again on partial re-entry.
+				 * Pages left resident are never re-mapped;
+				 * restore copies them into newly mapped
+				 * pages.
+				 */
+				memset(&tt->dma_address[i], 0,
+				       num_pages * sizeof(*tt->dma_address));
+			}
 			if (flags->purge) {
 				shrunken += num_pages;
 				page->private = 0;
diff --git a/drivers/gpu/drm/ttm/ttm_pool_internal.h b/drivers/gpu/drm/ttm/ttm_pool_internal.h
index cbb17a2129fe..3f4f70037c39 100644
--- a/drivers/gpu/drm/ttm/ttm_pool_internal.h
+++ b/drivers/gpu/drm/ttm/ttm_pool_internal.h
@@ -30,4 +30,6 @@ static inline unsigned int ttm_pool_beneficial_order(struct ttm_pool *pool)
  */
 bool ttm_backup_fault_inject_folio(void);
 
+bool ttm_pool_restore_has_resident(const struct ttm_tt *tt);
+
 #endif
diff --git a/drivers/gpu/drm/ttm/ttm_tt.c b/drivers/gpu/drm/ttm/ttm_tt.c
index b645a1818184..2b441bf41d92 100644
--- a/drivers/gpu/drm/ttm/ttm_tt.c
+++ b/drivers/gpu/drm/ttm/ttm_tt.c
@@ -188,7 +188,7 @@ void ttm_tt_fini(struct ttm_tt *ttm)
 		fput(ttm->swap_storage);
 	ttm->swap_storage = NULL;
 
-	if (ttm_tt_is_backed_up(ttm))
+	if (ttm_tt_is_backed_up(ttm) || ttm_tt_is_partial(ttm))
 		ttm_pool_drop_backed_up(ttm);
 	if (ttm->backup) {
 		ttm_backup_fini(ttm->backup);
@@ -289,7 +289,18 @@ long ttm_tt_backup(struct ttm_device *bdev, struct ttm_tt *tt,
 	ret = ttm_pool_backup(&bdev->pool, tt, &flags);
 	if (ret > 0) {
 		tt->page_flags &= ~TTM_TT_FLAG_PRIV_POPULATED;
-		tt->page_flags |= TTM_TT_FLAG_BACKED_UP;
+		if (ret >= tt->remaining_pages)
+			tt->remaining_pages = 0;
+		else
+			tt->remaining_pages -= ret;
+
+		if (tt->remaining_pages) {
+			tt->page_flags &= ~TTM_TT_FLAG_BACKED_UP;
+			tt->page_flags |= TTM_TT_FLAG_PARTIAL;
+		} else {
+			tt->page_flags &= ~TTM_TT_FLAG_PARTIAL;
+			tt->page_flags |= TTM_TT_FLAG_BACKED_UP;
+		}
 	}
 
 	return ret;
@@ -300,10 +311,20 @@ int ttm_tt_restore(struct ttm_device *bdev, struct ttm_tt *tt,
 {
 	int ret = ttm_pool_restore_and_alloc(&bdev->pool, tt, ctx);
 
-	if (ret)
+	if (ret) {
+		/*
+		 * Let the shrinker reclaim pages made resident by the
+		 * failed restore. remaining_pages is recounted on backup.
+		 */
+		if (ttm_tt_is_backed_up(tt) &&
+		    ttm_pool_restore_has_resident(tt)) {
+			tt->page_flags &= ~TTM_TT_FLAG_BACKED_UP;
+			tt->page_flags |= TTM_TT_FLAG_PARTIAL;
+		}
 		return ret;
+	}
 
-	tt->page_flags &= ~TTM_TT_FLAG_BACKED_UP;
+	tt->page_flags &= ~(TTM_TT_FLAG_BACKED_UP | TTM_TT_FLAG_PARTIAL);
 
 	return 0;
 }
@@ -404,7 +425,12 @@ int ttm_tt_populate(struct ttm_device *bdev,
 		goto error;
 
 	ttm->page_flags |= TTM_TT_FLAG_PRIV_POPULATED;
-	ttm->page_flags &= ~TTM_TT_FLAG_BACKED_UP;
+	ttm->page_flags &= ~(TTM_TT_FLAG_BACKED_UP | TTM_TT_FLAG_PARTIAL);
+	/*
+	 * Only updated on success. If a restore fails, remaining_pages
+	 * goes stale until ttm_pool_restore_abort() recounts it.
+	 */
+	ttm->remaining_pages = ttm->num_pages;
 	if (unlikely(ttm->page_flags & TTM_TT_FLAG_SWAPPED)) {
 		ret = ttm_tt_swapin(ttm);
 		if (unlikely(ret != 0)) {
diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
index 2afee1a6de66..6907b683dee0 100644
--- a/drivers/gpu/drm/xe/xe_bo.c
+++ b/drivers/gpu/drm/xe/xe_bo.c
@@ -583,6 +583,26 @@ static void update_global_total_pages(struct ttm_device *ttm_dev,
 #endif
 }
 
+/*
+ * Account for the change in resident pages of @tt after a shrink or purge
+ * attempt. Note that the resident page count may also increase if a
+ * previously interrupted restore was aborted.
+ */
+static void xe_ttm_tt_account_shrunk(struct ttm_device *ttm_dev,
+				     struct ttm_tt *tt, pgoff_t old_remaining)
+{
+	pgoff_t new_remaining = ttm_tt_remaining_pages(tt);
+	long shrunk = (long)old_remaining - (long)new_remaining;
+
+	if (!shrunk)
+		return;
+
+	/* A tt is counted in populated_tts while it has accounted pages. */
+	xe_ttm_tt_account_subtract(ttm_to_xe_device(ttm_dev), tt, shrunk,
+				   !!old_remaining - !!new_remaining);
+	update_global_total_pages(ttm_dev, -shrunk);
+}
+
 static struct ttm_tt *xe_ttm_tt_create(struct ttm_buffer_object *ttm_bo,
 				       u32 page_flags)
 {
@@ -676,20 +696,35 @@ static int xe_ttm_tt_populate(struct ttm_device *ttm_dev, struct ttm_tt *tt,
 	    !(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE))
 		return 0;
 
-	if (ttm_tt_is_backed_up(tt) && !xe_tt->purgeable) {
+	/*
+	 * A purgeable backed-up tt was purged and holds neither pages nor
+	 * handles. Partial tts are never purgeable: madvise only marks
+	 * populated tts purgeable, and the shrinker purges those fully.
+	 */
+	if ((ttm_tt_is_backed_up(tt) || ttm_tt_is_partial(tt)) &&
+	    !xe_tt->purgeable) {
+		pgoff_t remaining = ttm_tt_remaining_pages(tt);
+		long backed_up_pages = tt->num_pages - remaining;
+
 		err = ttm_tt_restore(ttm_dev, tt, ctx);
+		if (!err) {
+			xe_tt->purgeable = false;
+			xe_ttm_tt_account_add(ttm_to_xe_device(ttm_dev), tt,
+					      backed_up_pages, !remaining);
+			update_global_total_pages(ttm_dev, backed_up_pages);
+		}
 	} else {
 		ttm_tt_clear_backed_up(tt);
 		err = ttm_pool_alloc(&ttm_dev->pool, tt, ctx);
+		if (!err) {
+			xe_tt->purgeable = false;
+			xe_ttm_tt_account_add(ttm_to_xe_device(ttm_dev), tt,
+					      tt->num_pages, 1);
+			update_global_total_pages(ttm_dev, tt->num_pages);
+		}
 	}
-	if (err)
-		return err;
 
-	xe_tt->purgeable = false;
-	xe_ttm_tt_account_add(ttm_to_xe_device(ttm_dev), tt, tt->num_pages, 1);
-	update_global_total_pages(ttm_dev, tt->num_pages);
-
-	return 0;
+	return err;
 }
 
 static void xe_ttm_tt_unpopulate(struct ttm_device *ttm_dev, struct ttm_tt *tt)
@@ -709,6 +744,15 @@ static void xe_ttm_tt_unpopulate(struct ttm_device *ttm_dev, struct ttm_tt *tt)
 
 static void xe_ttm_tt_destroy(struct ttm_device *ttm_dev, struct ttm_tt *tt)
 {
+	/* Resident pages of a partially backed-up tt are still accounted. */
+	if (ttm_tt_is_partial(tt)) {
+		long remaining = ttm_tt_remaining_pages(tt);
+
+		xe_ttm_tt_account_subtract(ttm_to_xe_device(ttm_dev), tt,
+					   remaining, !!remaining);
+		update_global_total_pages(ttm_dev, -remaining);
+	}
+
 	ttm_tt_fini(tt);
 	kfree(tt);
 }
@@ -1167,7 +1211,8 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict,
 		return ret;
 	}
 
-	tt_has_data = ttm && (ttm_tt_is_populated(ttm) || ttm_tt_is_swapped(ttm));
+	tt_has_data = ttm && (ttm_tt_is_populated(ttm) || ttm_tt_is_swapped(ttm) ||
+			      ttm_tt_is_partial(ttm));
 
 	move_lacks_source = !old_mem || (handle_system_ccs ? (!bo->ccs_cleared) :
 					 (!mem_type_is_vram(old_mem_type) && !tt_has_data));
@@ -1369,7 +1414,7 @@ static long xe_bo_shrink_purge(struct ttm_operation_ctx *ctx,
 			       unsigned long *scanned)
 {
 	struct xe_device *xe = ttm_to_xe_device(bo->bdev);
-	struct ttm_tt *tt = bo->ttm;
+	pgoff_t old_remaining;
 	long lret;
 
 	/* Fake move to system, without copying data. */
@@ -1388,16 +1433,14 @@ static long xe_bo_shrink_purge(struct ttm_operation_ctx *ctx,
 		ttm_bo_move_null(bo, new_resource);
 	}
 
-	*scanned += bo->ttm->num_pages;
+	old_remaining = ttm_tt_remaining_pages(bo->ttm);
+	*scanned += old_remaining;
 	lret = ttm_bo_shrink(ctx, bo, (struct ttm_bo_shrink_flags)
 			     {.purge = true,
 			      .writeback = false,
 			      .allow_move = false});
 
-	if (lret > 0) {
-		xe_ttm_tt_account_subtract(xe, bo->ttm, bo->ttm->num_pages, 1);
-		update_global_total_pages(bo->bdev, -(long)tt->num_pages);
-	}
+	xe_ttm_tt_account_shrunk(bo->bdev, bo->ttm, old_remaining);
 
 	return lret;
 }
@@ -1448,6 +1491,7 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
 	struct ttm_place place = {.mem_type = bo->resource->mem_type};
 	struct xe_bo *xe_bo = ttm_to_xe_bo(bo);
 	struct xe_device *xe = ttm_to_xe_device(bo->bdev);
+	pgoff_t old_remaining;
 	bool needs_rpm;
 	long lret = 0L;
 
@@ -1478,7 +1522,8 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
 	if (needs_rpm && !xe_pm_runtime_get_if_active(xe))
 		goto out_unref;
 
-	*scanned += tt->num_pages;
+	old_remaining = ttm_tt_remaining_pages(tt);
+	*scanned += old_remaining;
 	lret = ttm_bo_shrink(ctx, bo, (struct ttm_bo_shrink_flags)
 			     {.purge = false,
 			      .writeback = flags.writeback,
@@ -1486,10 +1531,7 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
 	if (needs_rpm)
 		xe_pm_runtime_put(xe);
 
-	if (lret > 0) {
-		xe_ttm_tt_account_subtract(xe, tt, tt->num_pages, 1);
-		update_global_total_pages(bo->bdev, -(long)tt->num_pages);
-	}
+	xe_ttm_tt_account_shrunk(bo->bdev, tt, old_remaining);
 
 out_unref:
 	xe_bo_put(xe_bo);
diff --git a/include/drm/ttm/ttm_tt.h b/include/drm/ttm/ttm_tt.h
index 406437ad674b..c292a8f0583f 100644
--- a/include/drm/ttm/ttm_tt.h
+++ b/include/drm/ttm/ttm_tt.h
@@ -88,7 +88,13 @@ struct ttm_tt {
 	 * used to assure pgprot always matches.
 	 *
 	 * TTM_TT_FLAG_BACKED_UP: TTM internal only. This is set if the
-	 * struct ttm_tt has been (possibly partially) backed up.
+	 * struct ttm_tt has been fully backed up.
+	 *
+	 * TTM_TT_FLAG_PARTIAL: TTM internal only. This is set if the
+	 * struct ttm_tt has been partially backed up, meaning some pages
+	 * have been swapped to the backup but some real pages remain.
+	 * Unlike BACKED_UP, a partial tt stays on the normal LRU and
+	 * can be targeted for continued backup.
 	 *
 	 * TTM_TT_FLAG_PRIV_POPULATED: TTM internal only. DO NOT USE. This is
 	 * set by TTM after ttm_tt_populate() has successfully returned, and is
@@ -101,11 +107,14 @@ struct ttm_tt {
 #define TTM_TT_FLAG_EXTERNAL_MAPPABLE	BIT(3)
 #define TTM_TT_FLAG_DECRYPTED		BIT(4)
 #define TTM_TT_FLAG_BACKED_UP	        BIT(5)
+#define TTM_TT_FLAG_PARTIAL		BIT(6)
 
-#define TTM_TT_FLAG_PRIV_POPULATED	BIT(6)
+#define TTM_TT_FLAG_PRIV_POPULATED	BIT(7)
 	uint32_t page_flags;
 	/** @num_pages: Number of pages in the page array. */
 	uint32_t num_pages;
+	/** @remaining_pages: Number of resident (non-backed-up) pages. */
+	pgoff_t remaining_pages;
 	/** @sg: for SG objects via dma-buf. */
 	struct sg_table *sg;
 	/** @dma_address: The DMA (bus) addresses of the pages. */
@@ -157,10 +166,10 @@ static inline bool ttm_tt_is_swapped(const struct ttm_tt *tt)
 }
 
 /**
- * ttm_tt_is_backed_up() - Whether the ttm_tt backed up
+ * ttm_tt_is_backed_up() - Whether the ttm_tt is fully backed up
  * @tt: The struct ttm_tt.
  *
- * Return: true if swapped or backed up, false otherwise.
+ * Return: true if fully backed up, false otherwise.
  */
 static inline bool ttm_tt_is_backed_up(const struct ttm_tt *tt)
 {
@@ -168,15 +177,37 @@ static inline bool ttm_tt_is_backed_up(const struct ttm_tt *tt)
 }
 
 /**
- * ttm_tt_clear_backed_up() - Clear the ttm_tt backed-up status
+ * ttm_tt_is_partial() - Whether the ttm_tt is partially backed up
+ * @tt: The struct ttm_tt.
+ *
+ * Return: true if partially backed up, false otherwise.
+ */
+static inline bool ttm_tt_is_partial(const struct ttm_tt *tt)
+{
+	return tt->page_flags & TTM_TT_FLAG_PARTIAL;
+}
+
+/**
+ * ttm_tt_remaining_pages() - Remaining non-backed-up pages
+ * @tt: The struct ttm_tt.
+ *
+ * Return: The number of pages that are still resident and not backed up.
+ */
+static inline pgoff_t ttm_tt_remaining_pages(const struct ttm_tt *tt)
+{
+	return tt->remaining_pages;
+}
+
+/**
+ * ttm_tt_clear_backed_up() - Clear the ttm_tt backed-up and partial status
  * @tt: The struct ttm_tt.
  *
- * Drivers can use this functionto clear the backed-up status,
+ * Drivers can use this function to clear the backed-up status,
  * for example before destroying or re-validating a purged tt.
  */
 static inline void ttm_tt_clear_backed_up(struct ttm_tt *tt)
 {
-	tt->page_flags &= ~TTM_TT_FLAG_BACKED_UP;
+	tt->page_flags &= ~(TTM_TT_FLAG_BACKED_UP | TTM_TT_FLAG_PARTIAL);
 }
 
 /**
-- 
2.56.0


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

Thread overview: 5+ 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 ` Thomas Hellström [this message]
2026-11-04 20:13 ` [PATCH 4/4] drm/ttm, drm/xe: Gate reserve-dipping backup retries on may_writeback Thomas Hellström

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=20261104201331.2462-4-thomas.hellstrom@linux.intel.com \
    --to=thomas.hellstrom@linux.intel.com \
    --cc=arunpravin.paneerselvam@amd.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=matthew.auld@intel.com \
    --cc=matthew.brost@intel.com \
    --cc=nat@pixelcluster.dev \
    --cc=niranjana.vishwanathapura@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox