From: Niranjana Vishwanathapura <niranjana.vishwanathapura@intel.com>
To: Matthew Brost <matthew.brost@intel.com>
Cc: intel-xe@lists.freedesktop.org, dri-devel@lists.freedesktop.org,
"Carlos Santa" <carlos.santa@intel.com>,
"Ryan Neph" <ryanneph@google.com>,
"Christian Koenig" <christian.koenig@amd.com>,
"Huang Rui" <ray.huang@amd.com>,
"Matthew Auld" <matthew.auld@intel.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
linux-kernel@vger.kernel.org,
"Thomas Hellström" <thomas.hellstrom@linux.intel.com>
Subject: Re: [PATCH v3 07/33] drm/ttm: Harvest beneficial-order pages on defragmentation moves
Date: Mon, 9 Nov 2026 18:37:05 -0800 [thread overview]
Message-ID: <avKDURZLW9pvCFvF@nvishwa1-desk> (raw)
In-Reply-To: <20260711025619.2540575-8-matthew.brost@intel.com>
On Fri, Jul 10, 2026 at 07:55:53PM -0700, Matthew Brost wrote:
>A defragmentation move relocates a populated tt into a freshly allocated
>one so that sub-beneficial-order chunks can be upgraded to the device's
>beneficial order. Previously, if allocation could not complete entirely
>at beneficial order, the move failed and all progress was discarded.
>
>Instead, once allocation has successfully upgraded part of the object,
>the remainder of the old tt is harvested to complete the new tt. Thread
>the BO's old, still-populated tt into the page allocator via a new
>ttm_operation_ctx::defrag_old_tt. While allocation is still proceeding,
>any existing chunk already at (or above) the beneficial order is reused
>directly instead of being reallocated. If allocation later has to fall
>back below beneficial_order after making partial progress, the remainder
>of the old tt is harvested instead of failing the move. This preserves
>the beneficial-order upgrades already made while leaving the rest of the
>object unchanged, allowing subsequent defragmentation attempts to make
>additional forward progress.
>
>Harvesting only borrows the pages; they remain owned by the old tt until
>the move commits. This keeps every unwind path non-destructive: if the
>(re)allocation or the move later fails, the new tt is torn down without
>freeing the borrowed pages and the old tt is restored fully intact.
>Ownership of the shared pages is transferred to the new tt only once the
>move has committed. ttm_tt_defrag_disown_borrowed() implements both
>directions of the transfer by clearing a tt's slots for every page it
>shares with the other tt (same struct page at the same index), so that
>tt's teardown leaves the shared pages alone and each page is freed
>exactly once by its owner:
>
>- on the alloc unwind and the failed-move paths, the new tt disowns the
> borrowed pages, leaving the old tt as the sole owner;
>- on the successful-move paths (synchronous teardown and the pipelined
> ghost-object teardown), the old tt disowns the now-transferred pages,
> leaving the new tt as the sole owner.
>
>Detecting shared pages by same-index struct page identity needs no extra
>per-tt bookkeeping, since two distinct allocations never alias. This is
>safe with drivers, such as Xe, that build their own per-tt sg table and
>DMA-map it independently: each tt maps and unmaps its own sg, so sharing
>the underlying pages between tts is DMA-clean.
>
>This is a no-op unless ctx->defrag_old_tt is set, which only happens on a
>defragmentation move.
>
>Cc: Carlos Santa <carlos.santa@intel.com>
>Cc: Ryan Neph <ryanneph@google.com>
>Cc: Christian Koenig <christian.koenig@amd.com>
>Cc: Huang Rui <ray.huang@amd.com>
>Cc: Matthew Auld <matthew.auld@intel.com>
>Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
>Cc: Maxime Ripard <mripard@kernel.org>
>Cc: Thomas Zimmermann <tzimmermann@suse.de>
>Cc: David Airlie <airlied@gmail.com>
>Cc: Simona Vetter <simona@ffwll.ch>
>Cc: dri-devel@lists.freedesktop.org
>Cc: linux-kernel@vger.kernel.org
>Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
>Assisted-by: GitHub_Copilot:claude-opus-4.8
>Signed-off-by: Matthew Brost <matthew.brost@intel.com>
>
>---
>
>v3:
> - Document the chunk-head invariant read by
> ttm_pool_harvest_remaining() (Sashiko)
>---
> drivers/gpu/drm/ttm/ttm_bo.c | 20 ++-
> drivers/gpu/drm/ttm/ttm_bo_util.c | 5 +
> drivers/gpu/drm/ttm/ttm_pool.c | 235 ++++++++++++++++++++++++++++--
> drivers/gpu/drm/ttm/ttm_tt.c | 67 +++++++++
> include/drm/ttm/ttm_bo.h | 12 ++
> include/drm/ttm/ttm_tt.h | 2 +
> 6 files changed, 328 insertions(+), 13 deletions(-)
>
>diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c
>index c1629b4c4f0f..ab2e637a017f 100644
>--- a/drivers/gpu/drm/ttm/ttm_bo.c
>+++ b/drivers/gpu/drm/ttm/ttm_bo.c
>@@ -182,7 +182,14 @@ static int ttm_bo_handle_move_mem(struct ttm_buffer_object *bo,
> }
>
> if (defrag_old_tt || mem->mem_type != TTM_PL_SYSTEM) {
>+ /*
>+ * Let the page allocator harvest already
>+ * beneficial-order chunks out of the old tt to back
>+ * the new one, rather than reallocating them.
>+ */
>+ ctx->defrag_old_tt = defrag_old_tt;
> ret = ttm_bo_populate(bo, ctx);
>+ ctx->defrag_old_tt = NULL;
> if (ret) {
> /*
> * Discharge the freshly allocated backing and
>@@ -235,6 +242,12 @@ static int ttm_bo_handle_move_mem(struct ttm_buffer_object *bo,
> }
>
> if (bo->defrag_old_tt) {
>+ /*
>+ * Successful defrag move: any pages the new tt borrowed from the
>+ * old tt are now owned by the new tt. Disown them from the old
>+ * tt before it is torn down so they are not freed.
>+ */
>+ ttm_tt_defrag_disown_borrowed(bo->defrag_old_tt, bo->ttm);
> ttm_tt_unpopulate(bo->bdev, bo->defrag_old_tt);
> ttm_tt_destroy(bo->bdev, bo->defrag_old_tt);
> bo->defrag_old_tt = NULL;
>@@ -245,7 +258,12 @@ static int ttm_bo_handle_move_mem(struct ttm_buffer_object *bo,
>
> out_err:
> if (bo->defrag_old_tt) {
>- /* Failed defrag move: restore the original backing. */
>+ /*
>+ * Failed defrag move: drop any pages the new tt borrowed from
>+ * the old tt (still owned by the old tt) before destroying the
>+ * new tt, then restore the original backing.
>+ */
>+ ttm_tt_defrag_disown_borrowed(bo->ttm, bo->defrag_old_tt);
> ttm_bo_tt_destroy(bo);
> bo->ttm = bo->defrag_old_tt;
> bo->defrag_old_tt = NULL;
>diff --git a/drivers/gpu/drm/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c
>index 771313162c8d..7cb718fc97c0 100644
>--- a/drivers/gpu/drm/ttm/ttm_bo_util.c
>+++ b/drivers/gpu/drm/ttm/ttm_bo_util.c
>@@ -633,7 +633,12 @@ static int ttm_bo_move_to_ghost(struct ttm_buffer_object *bo,
> * tt (the move's source) to the ghost so it is kept populated
> * until @fence signals and then unbound and destroyed, instead
> * of being torn down synchronously. @bo keeps its new tt.
>+ *
>+ * Any pages the new tt borrowed from the old tt are now owned by
>+ * the new tt; disown them from the old tt so the ghost teardown
>+ * leaves them alone.
> */
>+ ttm_tt_defrag_disown_borrowed(bo->defrag_old_tt, bo->ttm);
> ghost_obj->ttm = bo->defrag_old_tt;
> ghost_obj->defrag_old_tt = NULL;
> bo->defrag_old_tt = NULL;
>diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c
>index 2598d2f33767..52f5e64f05dd 100644
>--- a/drivers/gpu/drm/ttm/ttm_pool.c
>+++ b/drivers/gpu/drm/ttm/ttm_pool.c
>@@ -792,6 +792,148 @@ static unsigned int ttm_pool_alloc_find_order(unsigned int highest,
> return min_t(unsigned int, highest, __fls(alloc->remaining_pages));
> }
>
>+/*
>+ * Defragmentation page harvesting.
>+ *
>+ * A defrag move relocates a populated tt into a freshly allocated one in order
>+ * to upgrade sub-beneficial-order chunks to the device's beneficial order.
>+ * Chunks in the old tt (ctx->defrag_old_tt) that are ALREADY at the beneficial
>+ * order do not need to be reallocated: their physical pages can simply be
>+ * transferred ("harvested") into the new tt at the same offset, avoiding the
>+ * alloc/free churn that is significant for large buffers.
>+ *
>+ * Harvesting only borrows the pages: the new tt simply references the old tt's
>+ * pages, which remain owned by the old tt. This keeps the unwind paths simple -
>+ * if the (re)allocation or the move later fails, the new tt is torn down without
>+ * freeing the borrowed pages and the old tt is restored fully intact. Ownership
>+ * of the shared pages is transferred to the new tt only once the move has
>+ * committed, via ttm_tt_defrag_disown_borrowed() (see ttm_tt.c).
>+ */
>+
>+/*
>+ * Return the allocation order of the old tt chunk whose head sits at @off, or 0
>+ * if @off is not an old chunk head (it falls inside a chunk, or the slot is
>+ * empty / backed up). @cursor monotonically tracks the old chunk boundary and
>+ * must be advanced past every offset that has already been queried; a page
>+ * order may only be read from a chunk head page, hence the lazy walk.
>+ */
>+static unsigned int ttm_pool_defrag_old_order(struct ttm_pool *pool,
>+ struct ttm_tt *old_tt,
>+ pgoff_t off, pgoff_t *cursor)
>+{
>+ struct page *p;
>+
>+ while (*cursor < off) {
>+ p = old_tt->pages[*cursor];
>+ if (!p || ttm_backup_page_ptr_is_handle(p))
>+ return 0;
Assisted-by: LLM
ttm_backup_page_ptr_is_handle() is only check here, but not in the harvest
function. Probably we won't endup with ptr_is_handle() case during defrag,
but thisasymmetry makes it bit confusing.
>+ *cursor += 1UL << ttm_pool_page_order(pool, p);
>+ }
>+
>+ if (*cursor != off)
>+ return 0;
>+
>+ p = old_tt->pages[off];
>+ if (!p || ttm_backup_page_ptr_is_handle(p))
>+ return 0;
>+
>+ return ttm_pool_page_order(pool, p);
>+}
>+
>+/*
>+ * Borrow an already beneficial-order chunk from the old tt into @alloc. The
>+ * pages stay owned by the old tt until the move commits.
>+ */
>+static int ttm_pool_harvest_page(struct ttm_tt *old_tt, unsigned int order,
>+ pgoff_t off, struct ttm_pool_alloc_state *alloc)
>+{
>+ pgoff_t nr = 1UL << order;
>+ int r;
>+
>+ /*
>+ * A harvested chunk keeps the (same) bo's caching, so the deferred
>+ * caching range is consistent here; flush it before committing, exactly
>+ * like the caching-consistent path in ttm_pool_page_allocated().
>+ */
>+ r = ttm_pool_apply_caching(alloc);
>+ if (r)
>+ return r;
>+
>+ /*
>+ * The borrowed chunk's page pointers (consecutive struct pages of one
>+ * compound allocation) and dma addresses already sit contiguously in
>+ * the old tt at @off. Copy the whole run with memcpy() instead of the
>+ * per-page increment loop in ttm_pool_allocated_page_commit(): for a
>+ * large defrag the borrowed pages dominate the new tt, so this turns
>+ * the populate fill from O(num_pages) individual stores into a handful
>+ * of bulk copies.
>+ */
>+ memcpy(alloc->pages, &old_tt->pages[off], nr * sizeof(*alloc->pages));
>+ alloc->pages += nr;
>+ alloc->remaining_pages -= nr;
>+
>+ if (alloc->dma_addr) {
>+ memcpy(alloc->dma_addr, &old_tt->dma_address[off],
>+ nr * sizeof(*alloc->dma_addr));
>+ alloc->dma_addr += nr;
>+ }
>+
>+ alloc->caching_divide = alloc->pages;
>+
>+ return 0;
>+}
>+
>+static int ttm_pool_harvest_remaining(struct ttm_pool *pool,
>+ struct ttm_tt *old_tt, pgoff_t off,
>+ struct ttm_pool_alloc_state *alloc)
>+{
>+ /*
>+ * @off is always an old-tt chunk head here, never a tail page, so
>+ * reading the per-chunk order below is safe. This holds because:
>+ * - Old chunks are self-aligned: greedy largest-power-of-two fill
>+ * places an order-k chunk at a 2^k-aligned offset, so any
>+ * beneficial-order-aligned offset is a chunk boundary.
>+ * - Fresh defrag allocations are capped at the beneficial order (see
>+ * ttm_pool_iter_acquire_page()), so they advance @off in beneficial
>+ * steps and never overshoot into the middle of a larger old chunk;
>+ * super-beneficial old chunks are always harvested whole by
>+ * ttm_pool_iter_reuse_old().
>+ * - We are only reached once the defrag budget/prealloc is exhausted,
>+ * which in the capped/prealloc config leaves @off beneficial-aligned
>+ * (or, in the sub-beneficial tail, already walking old boundaries).
>+ */
>+ while (alloc->remaining_pages) {
>+ struct page *p = old_tt->pages[off];
>+ unsigned int order = ttm_pool_page_order(pool, p);
>+ pgoff_t nr = 1UL << order;
>+ int r;
>+
>+ r = ttm_pool_harvest_page(old_tt, order, off, alloc);
>+ if (r)
>+ return r;
>+
>+ off += nr;
>+ }
The 'off' being always at chunk head is documented, but not checked/enforced
in the code. Though currently, we won't have it otherwise, but tomorrow if
we do for some reason, it will go unnoticed. Probably worth adding a warning
here.
>+
>+ return 0;
>+}
>+
>+/**
>+ * enum ttm_pool_iter_action - Outcome of a per-order allocation phase
>+ * @TTM_POOL_ITER_FILL: A page (@it->p) was acquired; proceed to fill it in.
>+ * @TTM_POOL_ITER_RETRY: Loop state was adjusted (e.g. order lowered or a chunk
>+ * harvested); re-enter the loop body for the next order.
>+ * @TTM_POOL_ITER_STOP: The allocation is complete; stop the loop.
>+ *
>+ * Phase helpers return one of these (>= 0) on success, or a negative errno on
>+ * failure (the caller then unwinds via error_free_all).
>+ */
>+enum ttm_pool_iter_action {
>+ TTM_POOL_ITER_FILL,
>+ TTM_POOL_ITER_RETRY,
>+ TTM_POOL_ITER_STOP,
NIT...STOP is really not needed here, RETRY will do as the loop is
expected to end there. But it is fine.
>+};
>+
> /**
> * struct ttm_pool_alloc_iter - Working state for the __ttm_pool_alloc() loop
> *
>@@ -806,6 +948,7 @@ struct ttm_pool_alloc_iter {
> const struct ttm_operation_ctx *ctx;
> struct ttm_pool_alloc_state *alloc;
> struct ttm_pool_tt_restore *restore;
>+ struct ttm_tt *defrag_old_tt;
> unsigned int beneficial_order;
> gfp_t gfp_flags;
>
>@@ -814,9 +957,50 @@ struct ttm_pool_alloc_iter {
> enum ttm_caching page_caching;
> bool allow_pools;
> bool fail_beneficial;
>+ unsigned int alloc_count;
>+ pgoff_t old_cursor;
> struct page *p;
> };
>
>+/*
>+ * Defrag move: reuse an already beneficial-order chunk from the old tt at this
>+ * offset rather than reallocating it. Harvest whatever order the old chunk has
>+ * (>= beneficial order), which may exceed the order the new tt would otherwise
>+ * pick. An order below beneficial_order is only reused for the unaligned tail
>+ * (where there is no benefit to reallocating). Only borrows the pages; the alloc
>+ * unwind path can drop them again.
>+ *
>+ * Returns TTM_POOL_ITER_RETRY when a chunk was harvested, TTM_POOL_ITER_FILL to
>+ * fall through to a fresh allocation, or a negative errno.
>+ */
>+static int ttm_pool_iter_reuse_old(struct ttm_pool_alloc_iter *it)
>+{
>+ unsigned int harvest_order;
>+ pgoff_t off;
>+ int r;
>+
>+ if (!it->defrag_old_tt || !it->beneficial_order)
>+ return TTM_POOL_ITER_FILL;
>+
>+ off = it->tt->num_pages - it->alloc->remaining_pages;
>+ harvest_order = ttm_pool_defrag_old_order(it->pool, it->defrag_old_tt,
>+ off, &it->old_cursor);
>+
>+ if (harvest_order < it->beneficial_order &&
>+ it->order >= it->beneficial_order)
>+ return TTM_POOL_ITER_FILL;
>+
>+ it->order = harvest_order;
>+ r = ttm_pool_harvest_page(it->defrag_old_tt, it->order, off, it->alloc);
>+ if (r)
>+ return r;
>+
>+ it->page_caching = it->tt->caching;
>+ it->allow_pools = true;
>+
>+ return TTM_POOL_ITER_RETRY;
>+}
>+
> /*
> * Acquire a single page for the current order, leaving it in @it->p (NULL on
> * failure). Tries a same-order pool page, then a fresh system allocation. Fault
>@@ -859,9 +1043,10 @@ static void ttm_pool_iter_acquire_page(struct ttm_pool_alloc_iter *it)
> /*
> * The current order could not be satisfied. Lower it and retry, recording a
> * sub-optimal backing (so the driver can later re-defrag) when we drop below
>- * the beneficial order.
>+ * the beneficial order. For a defrag move that has already made partial
>+ * progress, harvest the remaining old pages and stop instead.
> *
>- * Returns 0 to retry the (lowered) order, or a negative errno.
>+ * Returns TTM_POOL_ITER_RETRY, TTM_POOL_ITER_STOP, or a negative errno.
> */
> static int ttm_pool_iter_lower_order(struct ttm_pool_alloc_iter *it)
> {
>@@ -871,16 +1056,24 @@ static int ttm_pool_iter_lower_order(struct ttm_pool_alloc_iter *it)
> if (!it->order)
> return -ENOMEM;
>
>- /*
>- * Failing to allocate at the device's beneficial order means we are
>- * about to back this object with a sub-optimal (smaller order) set of
>- * pages. Record it so the driver can later try to defragment the object
>- * back to beneficial order.
>- */
> if (it->fail_beneficial || at_beneficial) {
> it->tt->page_flags |= TTM_TT_FLAG_BENEFICIAL_ORDER_FAILED;
>- if (it->ctx->defrag)
>+
>+ if (it->alloc_count && it->defrag_old_tt) {
>+ pgoff_t off = it->tt->num_pages -
>+ it->alloc->remaining_pages;
>+ int r;
>+
>+ r = ttm_pool_harvest_remaining(it->pool,
>+ it->defrag_old_tt, off,
>+ it->alloc);
>+ if (r)
>+ return r;
>+
>+ return TTM_POOL_ITER_STOP;
>+ } else if (it->defrag_old_tt) {
> return -ENOMEM;
>+ }
> }
>
> if (it->fail_beneficial)
>@@ -890,7 +1083,7 @@ static int ttm_pool_iter_lower_order(struct ttm_pool_alloc_iter *it)
> it->page_caching = it->tt->caching;
> it->allow_pools = true;
>
>- return 0;
>+ return TTM_POOL_ITER_RETRY;
> }
>
> static int __ttm_pool_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
>@@ -904,6 +1097,7 @@ static int __ttm_pool_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
> .ctx = ctx,
> .alloc = alloc,
> .restore = restore,
>+ .defrag_old_tt = restore ? NULL : ctx->defrag_old_tt,
> .beneficial_order = ttm_pool_beneficial_order(pool),
> .page_caching = tt->caching,
> .allow_pools = true,
>@@ -931,13 +1125,22 @@ static int __ttm_pool_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
> for (it.order = ttm_pool_alloc_find_order(MAX_PAGE_ORDER, alloc);
> alloc->remaining_pages;
> it.order = ttm_pool_alloc_find_order(it.order, alloc)) {
>+ /* Reuse an already beneficial-order chunk from the old tt. */
>+ r = ttm_pool_iter_reuse_old(&it);
>+ if (r < 0)
>+ goto error_free_all;
>+ if (r == TTM_POOL_ITER_RETRY)
>+ continue;
>+
> /* Acquire a page (pool / system) for this order. */
> ttm_pool_iter_acquire_page(&it);
> if (!it.p) {
> r = ttm_pool_iter_lower_order(&it);
>- if (r)
>+ if (r < 0)
> goto error_free_all;
>- continue;
>+ if (r == TTM_POOL_ITER_RETRY)
>+ continue;
>+ break; /* TTM_POOL_ITER_STOP */
> }
>
> r = ttm_pool_page_allocated(pool, it.order, it.p,
>@@ -951,6 +1154,8 @@ static int __ttm_pool_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
> if (r)
> goto error_free_all;
> }
>+
>+ it.alloc_count++;
> }
>
> r = ttm_pool_apply_caching(alloc);
>@@ -969,6 +1174,12 @@ static int __ttm_pool_alloc(struct ttm_pool *pool, struct ttm_tt *tt,
> if (tt->restore)
> return r;
>
>+ /*
>+ * Drop any pages borrowed from the old tt during a defrag move: they are
>+ * still owned by the old tt and must not be freed here.
>+ */
>+ ttm_tt_defrag_disown_borrowed(tt, it.defrag_old_tt);
>+
> caching_divide = alloc->caching_divide - tt->pages;
> ttm_pool_free_range(pool, tt, tt->caching, 0, caching_divide);
> ttm_pool_free_range(pool, tt, ttm_cached, caching_divide,
>diff --git a/drivers/gpu/drm/ttm/ttm_tt.c b/drivers/gpu/drm/ttm/ttm_tt.c
>index b645a1818184..2388ff794a59 100644
>--- a/drivers/gpu/drm/ttm/ttm_tt.c
>+++ b/drivers/gpu/drm/ttm/ttm_tt.c
>@@ -450,6 +450,73 @@ void ttm_tt_unpopulate(struct ttm_device *bdev, struct ttm_tt *ttm)
> }
> EXPORT_SYMBOL_FOR_TESTS_ONLY(ttm_tt_unpopulate);
>
>+/**
>+ * ttm_tt_defrag_disown_borrowed() - Drop borrowed defrag pages from a tt
>+ * @tt: The tt to drop the borrowed page references from.
>+ * @src: The tt the pages may have been harvested from, or NULL.
>+ *
>+ * During a defragmentation move the freshly (re)allocated tt may harvest
>+ * already beneficial-order chunks from the BO's old tt, sharing the underlying
>+ * pages instead of reallocating them. Until the move commits these pages stay
>+ * owned by the old tt. Clearing a tt's slots for every page it shares with
>+ * @src (the same struct page at the same index) makes that tt's teardown leave
>+ * the shared pages alone, so they are freed exactly once by their owner.
>+ *
>+ * This is used both to unwind a failed defrag move (disown the borrowed pages
>+ * from the new tt, leaving the old tt intact) and to commit a successful one
>+ * (disown the now-transferred pages from the old tt, leaving them owned by the
>+ * new tt).
>+ */
>+void ttm_tt_defrag_disown_borrowed(struct ttm_tt *tt, const struct ttm_tt *src)
>+{
>+ pgoff_t i = 0, run_start = 0;
>+ bool in_run = false;
>+
>+ if (!tt || !src)
>+ return;
>+
>+ /*
>+ * Borrowing happens at chunk granularity: a borrowed chunk shares the
>+ * same struct page on both tts across its whole allocation order, while
>+ * a non-borrowed chunk differs across its whole order. Stride by @src's
>+ * chunk order instead of walking every page, coalescing the borrowed
>+ * (matching) chunks into runs and clearing each run with a single
>+ * memset rather than a per-page store. @src is a fully populated,
>+ * non-dma_alloc tt (the defrag path), so its page order is recorded in
>+ * page->private and read via ttm_pool_page_order_nodma().
>+ */
>+ while (i < tt->num_pages) {
>+ struct page *sp = src->pages[i];
>+ unsigned int order = sp ? ttm_pool_page_order_nodma(sp) : 0;
Here ttm_pool_page_order_nodma() is used where as in harvest funciton,
ttm_pool_page_order() is used. Though it doesn't matter to Xe, this asymmetry
makes it bit confusing.
Niranjana
>+ pgoff_t nr = 1UL << order;
>+
>+ if (sp && tt->pages[i] == sp) {
>+ if (!in_run) {
>+ run_start = i;
>+ in_run = true;
>+ }
>+ } else if (in_run) {
>+ memset(&tt->pages[run_start], 0,
>+ (i - run_start) * sizeof(*tt->pages));
>+ if (tt->dma_address)
>+ memset(&tt->dma_address[run_start], 0,
>+ (i - run_start) *
>+ sizeof(*tt->dma_address));
>+ in_run = false;
>+ }
>+
>+ i += nr;
>+ }
>+
>+ if (in_run) {
>+ memset(&tt->pages[run_start], 0,
>+ (i - run_start) * sizeof(*tt->pages));
>+ if (tt->dma_address)
>+ memset(&tt->dma_address[run_start], 0,
>+ (i - run_start) * sizeof(*tt->dma_address));
>+ }
>+}
>+
> #ifdef CONFIG_DEBUG_FS
>
> /* Test the shrinker functions and dump the result */
>diff --git a/include/drm/ttm/ttm_bo.h b/include/drm/ttm/ttm_bo.h
>index f4f8a8c29f04..fcaba5fb8bf7 100644
>--- a/include/drm/ttm/ttm_bo.h
>+++ b/include/drm/ttm/ttm_bo.h
>@@ -211,6 +211,18 @@ struct ttm_operation_ctx {
> * order.
> */
> bool defrag;
>+ /**
>+ * @defrag_old_tt: During a defragmentation move, the bo's existing
>+ * (populated) tt that is being relocated. The page allocator may
>+ * harvest already beneficial-order chunks from this tt to back the new
>+ * tt instead of reallocating them, avoiding alloc/free churn for large
>+ * buffers. Harvested pages are only borrowed: they stay owned by
>+ * @defrag_old_tt until the move commits, at which point
>+ * ttm_tt_defrag_disown_borrowed() clears the old tt's slots for the
>+ * transferred pages so they are not freed when the old tt is torn down.
>+ * NULL for non-defrag operations.
>+ */
>+ struct ttm_tt *defrag_old_tt;
> /**
> * @resv: Reservation object to be used together with
> * @allow_res_evict.
>diff --git a/include/drm/ttm/ttm_tt.h b/include/drm/ttm/ttm_tt.h
>index ce7533677d77..55c35bcf134d 100644
>--- a/include/drm/ttm/ttm_tt.h
>+++ b/include/drm/ttm/ttm_tt.h
>@@ -284,6 +284,8 @@ int ttm_tt_populate(struct ttm_device *bdev, struct ttm_tt *ttm,
> */
> void ttm_tt_unpopulate(struct ttm_device *bdev, struct ttm_tt *ttm);
>
>+void ttm_tt_defrag_disown_borrowed(struct ttm_tt *tt, const struct ttm_tt *src);
>+
> /**
> * ttm_tt_mark_for_clear - Mark pages for clearing on populate.
> *
>--
>2.34.1
>
next prev parent reply other threads:[~2026-11-10 2:37 UTC|newest]
Thread overview: 60+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-11 2:55 [PATCH v3 00/33] drm/ttm, drm/xe: Minimize dma-resv hold times and defragment sub-optimally backed BOs Matthew Brost
2026-07-11 2:55 ` [PATCH v3 01/33] drm/ttm/pool: Allow backing off reclaim at the beneficial order Matthew Brost
2026-11-06 2:55 ` Niranjana Vishwanathapura
2026-07-11 2:55 ` [PATCH v3 02/33] drm/ttm/pool: Add ttm_pool_page_order_nodma() helper Matthew Brost
2026-11-05 11:27 ` Matthew Auld
2026-11-06 2:30 ` Niranjana Vishwanathapura
2026-07-11 2:55 ` [PATCH v3 03/33] drm/ttm: Record sub-optimal page order allocations in ttm_tt Matthew Brost
2026-11-06 2:34 ` Niranjana Vishwanathapura
2026-11-06 20:06 ` Matthew Brost
2026-07-11 2:55 ` [PATCH v3 04/33] drm/ttm: Introduce ttm_pool_alloc_iter for __ttm_pool_alloc() Matthew Brost
2026-11-06 2:39 ` Niranjana Vishwanathapura
2026-07-11 2:55 ` [PATCH v3 05/33] drm/ttm: Support defragmentation moves Matthew Brost
2026-11-06 2:51 ` Niranjana Vishwanathapura
2026-07-11 2:55 ` [PATCH v3 06/33] drm/ttm: Add fault injection for beneficial-order allocation failures Matthew Brost
2026-11-06 2:54 ` Niranjana Vishwanathapura
2026-07-11 2:55 ` [PATCH v3 07/33] drm/ttm: Harvest beneficial-order pages on defragmentation moves Matthew Brost
2026-11-10 2:37 ` Niranjana Vishwanathapura [this message]
2026-07-11 2:55 ` [PATCH v3 08/33] drm/ttm: Bound page (re)allocation per defragmentation move Matthew Brost
2026-11-10 2:44 ` Niranjana Vishwanathapura
2026-07-11 2:55 ` [PATCH v3 09/33] drm/ttm: Preallocate beneficial-order defrag pages outside the lock Matthew Brost
2026-07-11 2:55 ` [PATCH v3 10/33] drm/ttm: Add full out-of-lock preallocation for ttm_pool_alloc() Matthew Brost
2026-07-11 2:56 ` [PATCH v3 15/33] drm/xe: Track BOs backed at a sub-optimal page order Matthew Brost
2026-11-07 4:49 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 16/33] drm/xe: Back off beneficial-order reclaim under defrag pressure Matthew Brost
2026-11-07 5:02 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 17/33] drm/xe: Add xe_migrate_copy_defrag() for on-GPU defrag copies Matthew Brost
2026-11-03 12:58 ` Matthew Auld
2026-11-03 20:08 ` Matthew Brost
2026-11-04 12:37 ` Matthew Auld
2026-11-07 5:07 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 18/33] drm/xe: Handle defrag moves in xe_bo_move() Matthew Brost
2026-11-07 5:12 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 19/33] drm/xe: Skip self-copies for borrowed pages on defrag moves Matthew Brost
2026-07-11 2:56 ` [PATCH v3 20/33] drm/xe: Add a page defragmentation worker Matthew Brost
2026-11-07 5:27 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 21/33] drm/xe: Add defrag GT stats Matthew Brost
2026-11-09 1:10 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 22/33] drm/xe: Add Kconfig.profile options for BO defrag configuration Matthew Brost
2026-11-09 1:31 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 23/33] drm/xe: Defrag using out-of-lock page preallocation Matthew Brost
2026-07-11 2:56 ` [PATCH v3 24/33] drm/xe: Add defrag profiling tracepoints Matthew Brost
2026-11-09 4:34 ` Niranjana Vishwanathapura
2026-07-11 2:56 ` [PATCH v3 25/33] drm/xe: Preallocate system BO backing outside the dma-resv lock Matthew Brost
2026-07-11 2:56 ` [PATCH v3 26/33] drm/xe: Add tracepoint for xe_gem_create_ioctl Matthew Brost
2026-07-11 2:56 ` [PATCH v3 27/33] drm/xe: Add IOVA-based xe_res_cursor variant Matthew Brost
2026-11-04 12:23 ` Matthew Auld
2026-07-11 2:56 ` [PATCH v3 28/33] drm/xe: Use IOVA-based DMA mapping for eligible tt BOs Matthew Brost
2026-11-03 19:00 ` Matthew Auld
2026-11-03 20:21 ` Matthew Brost
2026-11-04 11:39 ` Matthew Auld
2026-11-05 3:04 ` Matthew Brost
2026-07-11 2:56 ` [PATCH v3 29/33] drm/xe: Add per-device dependency scheduler for IOVA defrag finalize Matthew Brost
2026-07-11 2:56 ` [PATCH v3 30/33] drm/xe: Add packed copy-step IOVA mapping for defrag Matthew Brost
2026-07-11 2:56 ` [PATCH v3 31/33] drm/xe: Blit src-natural to dst-packed for defrag-IOVA copies Matthew Brost
2026-07-11 2:56 ` [PATCH v3 32/33] drm/xe: Finalize defrag-IOVA moves with post-copy job Matthew Brost
2026-07-11 2:56 ` [PATCH v3 33/33] drm/amdgpu: Preallocate system BO pages outside the reservation lock Matthew Brost
2026-07-11 10:46 ` Christian König
2026-07-13 20:24 ` Matthew Brost
2026-07-14 9:17 ` Christian König
2026-07-14 15:15 ` Matthew Brost
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=avKDURZLW9pvCFvF@nvishwa1-desk \
--to=niranjana.vishwanathapura@intel.com \
--cc=airlied@gmail.com \
--cc=carlos.santa@intel.com \
--cc=christian.koenig@amd.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=intel-xe@lists.freedesktop.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=matthew.auld@intel.com \
--cc=matthew.brost@intel.com \
--cc=mripard@kernel.org \
--cc=ray.huang@amd.com \
--cc=ryanneph@google.com \
--cc=simona@ffwll.ch \
--cc=thomas.hellstrom@linux.intel.com \
--cc=tzimmermann@suse.de \
/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
all inboxes | Powered by JetHome®