Skip to content

Commit

Permalink
drm/xe: Add a shrinker for xe bos
Browse files Browse the repository at this point in the history
Rather than relying on the TTM watermark accounting add a shrinker
for xe_bos in TT or system memory.

Leverage the newly added TTM per-page shrinking and shmem backup
support.

Although xe doesn't fully support WONTNEED (purgeable) bos yet,
introduce and add shrinker support for purgeable ttm_tts.

v2:
- Cleanups bugfixes and a KUNIT shrinker test.
- Add writeback support, and activate if kswapd.
v3:
- Move the try_shrink() helper to core TTM.
- Minor cleanups.
v4:
- Add runtime pm for the shrinker. Shrinking may require an active
  device for CCS metadata copying.
v5:
- Separately purge ghost- and zombie objects in the shrinker.
- Fix a format specifier - type inconsistency. (Kernel test robot).
v7:
- s/long/s64/ (Christian König)
- s/sofar/progress/ (Matt Brost)
v8:
- Rebase on Xe KUNIT update.
- Add content verifying to the shrinker kunit test.
- Split out TTM changes to a separate patch.
- Get rid of multiple bool arguments for clarity (Matt Brost)
- Avoid an error pointer dereference (Matt Brost)
- Avoid an integer overflow (Matt Auld)
- Address misc review comments by Matt Brost.
v9:
- Fix a compliation error.
- Rebase.
v10:
- Update to new LRU walk interface.
- Rework ghost-, zombie and purged object shrinking.
- Rebase.
v11:
- Use additional TTM helpers.
- Honor __GFP_FS and __GFP_IO
- Rebase.
v13:
- Use ttm_tt_setup_backup().
v14:
- Don't set up backup on imported bos.
v15:
- Rebase on backup interface changes.

Cc: Christian König <christian.koenig@amd.com>
Cc: Somalapuram Amaranath <Amaranath.Somalapuram@amd.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: <dri-devel@lists.freedesktop.org>
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
Acked-by: Christian König <christian.koenig@amd.com>
Link: https://lore.kernel.org/intel-xe/20250305092220.123405-7-thomas.hellstrom@linux.intel.com
  • Loading branch information
Thomas Hellström committed Mar 5, 2025
1 parent 70d645d commit 00c8efc
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 18 deletions.
1 change: 1 addition & 0 deletions drivers/gpu/drm/xe/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ xe-y += xe_bb.o \
xe_ring_ops.o \
xe_sa.o \
xe_sched_job.o \
xe_shrinker.o \
xe_step.o \
xe_sync.o \
xe_tile.o \
Expand Down
6 changes: 5 additions & 1 deletion drivers/gpu/drm/xe/tests/xe_bo.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,13 @@ static int shrink_test_run_device(struct xe_device *xe)
* other way around, they may not be subject to swapping...
*/
if (alloced < purgeable) {
xe_ttm_tt_account_subtract(&xe_tt->ttm);
xe_tt->purgeable = true;
xe_ttm_tt_account_add(&xe_tt->ttm);
bo->ttm.priority = 0;
spin_lock(&bo->ttm.bdev->lru_lock);
ttm_bo_move_to_lru_tail(&bo->ttm);
spin_unlock(&bo->ttm.bdev->lru_lock);
} else {
int ret = shrink_test_fill_random(bo, &prng, link);

Expand Down Expand Up @@ -570,7 +575,6 @@ static int shrink_test_run_device(struct xe_device *xe)
if (ret == -EINTR)
intr = true;
} while (ret == -EINTR && !signal_pending(current));

if (!ret && !purgeable)
failed = shrink_test_verify(test, bo, count, &prng, link);

Expand Down
202 changes: 185 additions & 17 deletions drivers/gpu/drm/xe/xe_bo.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <drm/drm_drv.h>
#include <drm/drm_gem_ttm_helper.h>
#include <drm/drm_managed.h>
#include <drm/ttm/ttm_backup.h>
#include <drm/ttm/ttm_device.h>
#include <drm/ttm/ttm_placement.h>
#include <drm/ttm/ttm_tt.h>
Expand All @@ -25,6 +26,7 @@
#include "xe_pm.h"
#include "xe_preempt_fence.h"
#include "xe_res_cursor.h"
#include "xe_shrinker.h"
#include "xe_trace_bo.h"
#include "xe_ttm_stolen_mgr.h"
#include "xe_vm.h"
Expand Down Expand Up @@ -281,9 +283,11 @@ static void xe_evict_flags(struct ttm_buffer_object *tbo,
}
}

/* struct xe_ttm_tt - Subclassed ttm_tt for xe */
struct xe_ttm_tt {
struct ttm_tt ttm;
struct device *dev;
/** @xe - The xe device */
struct xe_device *xe;
struct sg_table sgt;
struct sg_table *sg;
/** @purgeable: Whether the content of the pages of @ttm is purgeable. */
Expand All @@ -296,21 +300,22 @@ static int xe_tt_map_sg(struct ttm_tt *tt)
unsigned long num_pages = tt->num_pages;
int ret;

XE_WARN_ON(tt->page_flags & TTM_TT_FLAG_EXTERNAL);
XE_WARN_ON((tt->page_flags & TTM_TT_FLAG_EXTERNAL) &&
!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE));

if (xe_tt->sg)
return 0;

ret = sg_alloc_table_from_pages_segment(&xe_tt->sgt, tt->pages,
num_pages, 0,
(u64)num_pages << PAGE_SHIFT,
xe_sg_segment_size(xe_tt->dev),
xe_sg_segment_size(xe_tt->xe->drm.dev),
GFP_KERNEL);
if (ret)
return ret;

xe_tt->sg = &xe_tt->sgt;
ret = dma_map_sgtable(xe_tt->dev, xe_tt->sg, DMA_BIDIRECTIONAL,
ret = dma_map_sgtable(xe_tt->xe->drm.dev, xe_tt->sg, DMA_BIDIRECTIONAL,
DMA_ATTR_SKIP_CPU_SYNC);
if (ret) {
sg_free_table(xe_tt->sg);
Expand All @@ -326,7 +331,7 @@ static void xe_tt_unmap_sg(struct ttm_tt *tt)
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);

if (xe_tt->sg) {
dma_unmap_sgtable(xe_tt->dev, xe_tt->sg,
dma_unmap_sgtable(xe_tt->xe->drm.dev, xe_tt->sg,
DMA_BIDIRECTIONAL, 0);
sg_free_table(xe_tt->sg);
xe_tt->sg = NULL;
Expand All @@ -341,21 +346,47 @@ struct sg_table *xe_bo_sg(struct xe_bo *bo)
return xe_tt->sg;
}

/*
* Account ttm pages against the device shrinker's shrinkable and
* purgeable counts.
*/
static void xe_ttm_tt_account_add(struct ttm_tt *tt)
{
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);

if (xe_tt->purgeable)
xe_shrinker_mod_pages(xe_tt->xe->mem.shrinker, 0, tt->num_pages);
else
xe_shrinker_mod_pages(xe_tt->xe->mem.shrinker, tt->num_pages, 0);
}

static void xe_ttm_tt_account_subtract(struct ttm_tt *tt)
{
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);

if (xe_tt->purgeable)
xe_shrinker_mod_pages(xe_tt->xe->mem.shrinker, 0, -(long)tt->num_pages);
else
xe_shrinker_mod_pages(xe_tt->xe->mem.shrinker, -(long)tt->num_pages, 0);
}

static struct ttm_tt *xe_ttm_tt_create(struct ttm_buffer_object *ttm_bo,
u32 page_flags)
{
struct xe_bo *bo = ttm_to_xe_bo(ttm_bo);
struct xe_device *xe = xe_bo_device(bo);
struct xe_ttm_tt *tt;
struct xe_ttm_tt *xe_tt;
struct ttm_tt *tt;
unsigned long extra_pages;
enum ttm_caching caching = ttm_cached;
int err;

tt = kzalloc(sizeof(*tt), GFP_KERNEL);
if (!tt)
xe_tt = kzalloc(sizeof(*xe_tt), GFP_KERNEL);
if (!xe_tt)
return NULL;

tt->dev = xe->drm.dev;
tt = &xe_tt->ttm;
xe_tt->xe = xe;

extra_pages = 0;
if (xe_bo_needs_ccs_pages(bo))
Expand Down Expand Up @@ -401,42 +432,66 @@ static struct ttm_tt *xe_ttm_tt_create(struct ttm_buffer_object *ttm_bo,
caching = ttm_uncached;
}

err = ttm_tt_init(&tt->ttm, &bo->ttm, page_flags, caching, extra_pages);
if (ttm_bo->type != ttm_bo_type_sg)
page_flags |= TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE;

err = ttm_tt_init(tt, &bo->ttm, page_flags, caching, extra_pages);
if (err) {
kfree(tt);
kfree(xe_tt);
return NULL;
}

return &tt->ttm;
if (ttm_bo->type != ttm_bo_type_sg) {
err = ttm_tt_setup_backup(tt);
if (err) {
ttm_tt_fini(tt);
kfree(xe_tt);
return NULL;
}
}

return tt;
}

static int xe_ttm_tt_populate(struct ttm_device *ttm_dev, struct ttm_tt *tt,
struct ttm_operation_ctx *ctx)
{
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);
int err;

/*
* dma-bufs are not populated with pages, and the dma-
* addresses are set up when moved to XE_PL_TT.
*/
if (tt->page_flags & TTM_TT_FLAG_EXTERNAL)
if ((tt->page_flags & TTM_TT_FLAG_EXTERNAL) &&
!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE))
return 0;

err = ttm_pool_alloc(&ttm_dev->pool, tt, ctx);
if (ttm_tt_is_backed_up(tt) && !xe_tt->purgeable) {
err = ttm_tt_restore(ttm_dev, tt, ctx);
} else {
ttm_tt_clear_backed_up(tt);
err = ttm_pool_alloc(&ttm_dev->pool, tt, ctx);
}
if (err)
return err;

return err;
xe_tt->purgeable = false;
xe_ttm_tt_account_add(tt);

return 0;
}

static void xe_ttm_tt_unpopulate(struct ttm_device *ttm_dev, struct ttm_tt *tt)
{
if (tt->page_flags & TTM_TT_FLAG_EXTERNAL)
if ((tt->page_flags & TTM_TT_FLAG_EXTERNAL) &&
!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE))
return;

xe_tt_unmap_sg(tt);

return ttm_pool_free(&ttm_dev->pool, tt);
ttm_pool_free(&ttm_dev->pool, tt);
xe_ttm_tt_account_subtract(tt);
}

static void xe_ttm_tt_destroy(struct ttm_device *ttm_dev, struct ttm_tt *tt)
Expand Down Expand Up @@ -871,6 +926,111 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict,
return ret;
}

static long xe_bo_shrink_purge(struct ttm_operation_ctx *ctx,
struct ttm_buffer_object *bo,
unsigned long *scanned)
{
long lret;

/* Fake move to system, without copying data. */
if (bo->resource->mem_type != XE_PL_SYSTEM) {
struct ttm_resource *new_resource;

lret = ttm_bo_wait_ctx(bo, ctx);
if (lret)
return lret;

lret = ttm_bo_mem_space(bo, &sys_placement, &new_resource, ctx);
if (lret)
return lret;

xe_tt_unmap_sg(bo->ttm);
ttm_bo_move_null(bo, new_resource);
}

*scanned += bo->ttm->num_pages;
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(bo->ttm);

return lret;
}

/**
* xe_bo_shrink() - Try to shrink an xe bo.
* @ctx: The struct ttm_operation_ctx used for shrinking.
* @bo: The TTM buffer object whose pages to shrink.
* @flags: Flags governing the shrink behaviour.
* @scanned: Pointer to a counter of the number of pages
* attempted to shrink.
*
* Try to shrink- or purge a bo, and if it succeeds, unmap dma.
* Note that we need to be able to handle also non xe bos
* (ghost bos), but only if the struct ttm_tt is embedded in
* a struct xe_ttm_tt. When the function attempts to shrink
* the pages of a buffer object, The value pointed to by @scanned
* is updated.
*
* Return: The number of pages shrunken or purged, or negative error
* code on failure.
*/
long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
const struct xe_bo_shrink_flags flags,
unsigned long *scanned)
{
struct ttm_tt *tt = bo->ttm;
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);
struct ttm_place place = {.mem_type = bo->resource->mem_type};
struct xe_bo *xe_bo = ttm_to_xe_bo(bo);
struct xe_device *xe = xe_tt->xe;
bool needs_rpm;
long lret = 0L;

if (!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE) ||
(flags.purge && !xe_tt->purgeable))
return -EBUSY;

if (!ttm_bo_eviction_valuable(bo, &place))
return -EBUSY;

if (!xe_bo_is_xe_bo(bo) || !xe_bo_get_unless_zero(xe_bo))
return xe_bo_shrink_purge(ctx, bo, scanned);

if (xe_tt->purgeable) {
if (bo->resource->mem_type != XE_PL_SYSTEM)
lret = xe_bo_move_notify(xe_bo, ctx);
if (!lret)
lret = xe_bo_shrink_purge(ctx, bo, scanned);
goto out_unref;
}

/* 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(tt);

out_unref:
xe_bo_put(xe_bo);

return lret;
}

/**
* xe_bo_evict_pinned() - Evict a pinned VRAM object to system memory
* @bo: The buffer object to move.
Expand Down Expand Up @@ -1885,6 +2045,8 @@ int xe_bo_pin_external(struct xe_bo *bo)
}

ttm_bo_pin(&bo->ttm);
if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_subtract(bo->ttm.ttm);

/*
* FIXME: If we always use the reserve / unreserve functions for locking
Expand Down Expand Up @@ -1944,6 +2106,8 @@ int xe_bo_pin(struct xe_bo *bo)
}

ttm_bo_pin(&bo->ttm);
if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_subtract(bo->ttm.ttm);

/*
* FIXME: If we always use the reserve / unreserve functions for locking
Expand Down Expand Up @@ -1978,6 +2142,8 @@ void xe_bo_unpin_external(struct xe_bo *bo)
spin_unlock(&xe->pinned.lock);

ttm_bo_unpin(&bo->ttm);
if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_add(bo->ttm.ttm);

/*
* FIXME: If we always use the reserve / unreserve functions for locking
Expand All @@ -2001,6 +2167,8 @@ void xe_bo_unpin(struct xe_bo *bo)
spin_unlock(&xe->pinned.lock);
}
ttm_bo_unpin(&bo->ttm);
if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_add(bo->ttm.ttm);
}

/**
Expand Down
Loading

0 comments on commit 00c8efc

Please sign in to comment.