xref: /linux/drivers/gpu/drm/xe/xe_shrinker.c (revision 12b6c62c038e85354154aee4eb2cf7a2168b3ecc)
100c8efc3SThomas Hellström // SPDX-License-Identifier: MIT
200c8efc3SThomas Hellström /*
300c8efc3SThomas Hellström  * Copyright © 2024 Intel Corporation
400c8efc3SThomas Hellström  */
500c8efc3SThomas Hellström 
600c8efc3SThomas Hellström #include <linux/shrinker.h>
700c8efc3SThomas Hellström 
800c8efc3SThomas Hellström #include <drm/ttm/ttm_backup.h>
900c8efc3SThomas Hellström #include <drm/ttm/ttm_bo.h>
1000c8efc3SThomas Hellström #include <drm/ttm/ttm_tt.h>
1100c8efc3SThomas Hellström 
1200c8efc3SThomas Hellström #include "xe_bo.h"
1300c8efc3SThomas Hellström #include "xe_pm.h"
1400c8efc3SThomas Hellström #include "xe_shrinker.h"
1500c8efc3SThomas Hellström 
1600c8efc3SThomas Hellström /**
1700c8efc3SThomas Hellström  * struct xe_shrinker - per-device shrinker
1800c8efc3SThomas Hellström  * @xe: Back pointer to the device.
1900c8efc3SThomas Hellström  * @lock: Lock protecting accounting.
2000c8efc3SThomas Hellström  * @shrinkable_pages: Number of pages that are currently shrinkable.
2100c8efc3SThomas Hellström  * @purgeable_pages: Number of pages that are currently purgeable.
2200c8efc3SThomas Hellström  * @shrink: Pointer to the mm shrinker.
2300c8efc3SThomas Hellström  * @pm_worker: Worker to wake up the device if required.
2400c8efc3SThomas Hellström  */
2500c8efc3SThomas Hellström struct xe_shrinker {
2600c8efc3SThomas Hellström 	struct xe_device *xe;
2700c8efc3SThomas Hellström 	rwlock_t lock;
2800c8efc3SThomas Hellström 	long shrinkable_pages;
2900c8efc3SThomas Hellström 	long purgeable_pages;
3000c8efc3SThomas Hellström 	struct shrinker *shrink;
3100c8efc3SThomas Hellström 	struct work_struct pm_worker;
3200c8efc3SThomas Hellström };
3300c8efc3SThomas Hellström 
to_xe_shrinker(struct shrinker * shrink)3400c8efc3SThomas Hellström static struct xe_shrinker *to_xe_shrinker(struct shrinker *shrink)
3500c8efc3SThomas Hellström {
3600c8efc3SThomas Hellström 	return shrink->private_data;
3700c8efc3SThomas Hellström }
3800c8efc3SThomas Hellström 
3900c8efc3SThomas Hellström /**
4000c8efc3SThomas Hellström  * xe_shrinker_mod_pages() - Modify shrinker page accounting
4100c8efc3SThomas Hellström  * @shrinker: Pointer to the struct xe_shrinker.
4200c8efc3SThomas Hellström  * @shrinkable: Shrinkable pages delta. May be negative.
4300c8efc3SThomas Hellström  * @purgeable: Purgeable page delta. May be negative.
4400c8efc3SThomas Hellström  *
4500c8efc3SThomas Hellström  * Modifies the shrinkable and purgeable pages accounting.
4600c8efc3SThomas Hellström  */
4700c8efc3SThomas Hellström void
xe_shrinker_mod_pages(struct xe_shrinker * shrinker,long shrinkable,long purgeable)4800c8efc3SThomas Hellström xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgeable)
4900c8efc3SThomas Hellström {
5000c8efc3SThomas Hellström 	write_lock(&shrinker->lock);
5100c8efc3SThomas Hellström 	shrinker->shrinkable_pages += shrinkable;
5200c8efc3SThomas Hellström 	shrinker->purgeable_pages += purgeable;
5300c8efc3SThomas Hellström 	write_unlock(&shrinker->lock);
5400c8efc3SThomas Hellström }
5500c8efc3SThomas Hellström 
xe_shrinker_walk(struct xe_device * xe,struct ttm_operation_ctx * ctx,const struct xe_bo_shrink_flags flags,unsigned long to_scan,unsigned long * scanned)5600c8efc3SThomas Hellström static s64 xe_shrinker_walk(struct xe_device *xe,
5700c8efc3SThomas Hellström 			    struct ttm_operation_ctx *ctx,
5800c8efc3SThomas Hellström 			    const struct xe_bo_shrink_flags flags,
5900c8efc3SThomas Hellström 			    unsigned long to_scan, unsigned long *scanned)
6000c8efc3SThomas Hellström {
6100c8efc3SThomas Hellström 	unsigned int mem_type;
6200c8efc3SThomas Hellström 	s64 freed = 0, lret;
6300c8efc3SThomas Hellström 
6400c8efc3SThomas Hellström 	for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) {
6500c8efc3SThomas Hellström 		struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type);
6600c8efc3SThomas Hellström 		struct ttm_bo_lru_cursor curs;
6700c8efc3SThomas Hellström 		struct ttm_buffer_object *ttm_bo;
6800c8efc3SThomas Hellström 
6900c8efc3SThomas Hellström 		if (!man || !man->use_tt)
7000c8efc3SThomas Hellström 			continue;
7100c8efc3SThomas Hellström 
7200c8efc3SThomas Hellström 		ttm_bo_lru_for_each_reserved_guarded(&curs, man, ctx, ttm_bo) {
7300c8efc3SThomas Hellström 			if (!ttm_bo_shrink_suitable(ttm_bo, ctx))
7400c8efc3SThomas Hellström 				continue;
7500c8efc3SThomas Hellström 
7600c8efc3SThomas Hellström 			lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned);
7700c8efc3SThomas Hellström 			if (lret < 0)
7800c8efc3SThomas Hellström 				return lret;
7900c8efc3SThomas Hellström 
8000c8efc3SThomas Hellström 			freed += lret;
8100c8efc3SThomas Hellström 			if (*scanned >= to_scan)
8200c8efc3SThomas Hellström 				break;
8300c8efc3SThomas Hellström 		}
8400c8efc3SThomas Hellström 	}
8500c8efc3SThomas Hellström 
8600c8efc3SThomas Hellström 	return freed;
8700c8efc3SThomas Hellström }
8800c8efc3SThomas Hellström 
8900c8efc3SThomas Hellström static unsigned long
xe_shrinker_count(struct shrinker * shrink,struct shrink_control * sc)9000c8efc3SThomas Hellström xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
9100c8efc3SThomas Hellström {
9200c8efc3SThomas Hellström 	struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
9300c8efc3SThomas Hellström 	unsigned long num_pages;
9400c8efc3SThomas Hellström 	bool can_backup = !!(sc->gfp_mask & __GFP_FS);
9500c8efc3SThomas Hellström 
9600c8efc3SThomas Hellström 	num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT;
9700c8efc3SThomas Hellström 	read_lock(&shrinker->lock);
9800c8efc3SThomas Hellström 
9900c8efc3SThomas Hellström 	if (can_backup)
10000c8efc3SThomas Hellström 		num_pages = min_t(unsigned long, num_pages, shrinker->shrinkable_pages);
10100c8efc3SThomas Hellström 	else
10200c8efc3SThomas Hellström 		num_pages = 0;
10300c8efc3SThomas Hellström 
10400c8efc3SThomas Hellström 	num_pages += shrinker->purgeable_pages;
10500c8efc3SThomas Hellström 	read_unlock(&shrinker->lock);
10600c8efc3SThomas Hellström 
10700c8efc3SThomas Hellström 	return num_pages ? num_pages : SHRINK_EMPTY;
10800c8efc3SThomas Hellström }
10900c8efc3SThomas Hellström 
11000c8efc3SThomas Hellström /*
11100c8efc3SThomas Hellström  * Check if we need runtime pm, and if so try to grab a reference if
11200c8efc3SThomas Hellström  * already active. If grabbing a reference fails, queue a worker that
11300c8efc3SThomas Hellström  * does it for us outside of reclaim, but don't wait for it to complete.
11400c8efc3SThomas Hellström  * If bo shrinking needs an rpm reference and we don't have it (yet),
11500c8efc3SThomas Hellström  * that bo will be skipped anyway.
11600c8efc3SThomas Hellström  */
xe_shrinker_runtime_pm_get(struct xe_shrinker * shrinker,bool force,unsigned long nr_to_scan,bool can_backup)11700c8efc3SThomas Hellström static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force,
11800c8efc3SThomas Hellström 				       unsigned long nr_to_scan, bool can_backup)
11900c8efc3SThomas Hellström {
12000c8efc3SThomas Hellström 	struct xe_device *xe = shrinker->xe;
12100c8efc3SThomas Hellström 
12200c8efc3SThomas Hellström 	if (IS_DGFX(xe) || !xe_device_has_flat_ccs(xe) ||
12300c8efc3SThomas Hellström 	    !ttm_backup_bytes_avail())
12400c8efc3SThomas Hellström 		return false;
12500c8efc3SThomas Hellström 
12600c8efc3SThomas Hellström 	if (!force) {
12700c8efc3SThomas Hellström 		read_lock(&shrinker->lock);
12800c8efc3SThomas Hellström 		force = (nr_to_scan > shrinker->purgeable_pages && can_backup);
12900c8efc3SThomas Hellström 		read_unlock(&shrinker->lock);
13000c8efc3SThomas Hellström 		if (!force)
13100c8efc3SThomas Hellström 			return false;
13200c8efc3SThomas Hellström 	}
13300c8efc3SThomas Hellström 
13400c8efc3SThomas Hellström 	if (!xe_pm_runtime_get_if_active(xe)) {
13500c8efc3SThomas Hellström 		if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) {
13600c8efc3SThomas Hellström 			xe_pm_runtime_get(xe);
13700c8efc3SThomas Hellström 			return true;
13800c8efc3SThomas Hellström 		}
13900c8efc3SThomas Hellström 		queue_work(xe->unordered_wq, &shrinker->pm_worker);
14000c8efc3SThomas Hellström 		return false;
14100c8efc3SThomas Hellström 	}
14200c8efc3SThomas Hellström 
14300c8efc3SThomas Hellström 	return true;
14400c8efc3SThomas Hellström }
14500c8efc3SThomas Hellström 
xe_shrinker_runtime_pm_put(struct xe_shrinker * shrinker,bool runtime_pm)14600c8efc3SThomas Hellström static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm)
14700c8efc3SThomas Hellström {
14800c8efc3SThomas Hellström 	if (runtime_pm)
14900c8efc3SThomas Hellström 		xe_pm_runtime_put(shrinker->xe);
15000c8efc3SThomas Hellström }
15100c8efc3SThomas Hellström 
xe_shrinker_scan(struct shrinker * shrink,struct shrink_control * sc)15200c8efc3SThomas Hellström static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
15300c8efc3SThomas Hellström {
15400c8efc3SThomas Hellström 	struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
15500c8efc3SThomas Hellström 	struct ttm_operation_ctx ctx = {
15600c8efc3SThomas Hellström 		.interruptible = false,
15700c8efc3SThomas Hellström 		.no_wait_gpu = ttm_bo_shrink_avoid_wait(),
15800c8efc3SThomas Hellström 	};
15900c8efc3SThomas Hellström 	unsigned long nr_to_scan, nr_scanned = 0, freed = 0;
16000c8efc3SThomas Hellström 	struct xe_bo_shrink_flags shrink_flags = {
16100c8efc3SThomas Hellström 		.purge = true,
16200c8efc3SThomas Hellström 		/* Don't request writeback without __GFP_IO. */
16300c8efc3SThomas Hellström 		.writeback = !ctx.no_wait_gpu && (sc->gfp_mask & __GFP_IO),
16400c8efc3SThomas Hellström 	};
16500c8efc3SThomas Hellström 	bool runtime_pm;
16600c8efc3SThomas Hellström 	bool purgeable;
16700c8efc3SThomas Hellström 	bool can_backup = !!(sc->gfp_mask & __GFP_FS);
16800c8efc3SThomas Hellström 	s64 lret;
16900c8efc3SThomas Hellström 
17000c8efc3SThomas Hellström 	nr_to_scan = sc->nr_to_scan;
17100c8efc3SThomas Hellström 
17200c8efc3SThomas Hellström 	read_lock(&shrinker->lock);
17300c8efc3SThomas Hellström 	purgeable = !!shrinker->purgeable_pages;
17400c8efc3SThomas Hellström 	read_unlock(&shrinker->lock);
17500c8efc3SThomas Hellström 
17600c8efc3SThomas Hellström 	/* Might need runtime PM. Try to wake early if it looks like it. */
17700c8efc3SThomas Hellström 	runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup);
17800c8efc3SThomas Hellström 
17900c8efc3SThomas Hellström 	if (purgeable && nr_scanned < nr_to_scan) {
18000c8efc3SThomas Hellström 		lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags,
18100c8efc3SThomas Hellström 					nr_to_scan, &nr_scanned);
18200c8efc3SThomas Hellström 		if (lret >= 0)
18300c8efc3SThomas Hellström 			freed += lret;
18400c8efc3SThomas Hellström 	}
18500c8efc3SThomas Hellström 
18600c8efc3SThomas Hellström 	sc->nr_scanned = nr_scanned;
18700c8efc3SThomas Hellström 	if (nr_scanned >= nr_to_scan || !can_backup)
18800c8efc3SThomas Hellström 		goto out;
18900c8efc3SThomas Hellström 
19000c8efc3SThomas Hellström 	/* If we didn't wake before, try to do it now if needed. */
19100c8efc3SThomas Hellström 	if (!runtime_pm)
19200c8efc3SThomas Hellström 		runtime_pm = xe_shrinker_runtime_pm_get(shrinker, true, 0, can_backup);
19300c8efc3SThomas Hellström 
19400c8efc3SThomas Hellström 	shrink_flags.purge = false;
19500c8efc3SThomas Hellström 	lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags,
19600c8efc3SThomas Hellström 				nr_to_scan, &nr_scanned);
19700c8efc3SThomas Hellström 	if (lret >= 0)
19800c8efc3SThomas Hellström 		freed += lret;
19900c8efc3SThomas Hellström 
20000c8efc3SThomas Hellström 	sc->nr_scanned = nr_scanned;
20100c8efc3SThomas Hellström out:
20200c8efc3SThomas Hellström 	xe_shrinker_runtime_pm_put(shrinker, runtime_pm);
20300c8efc3SThomas Hellström 	return nr_scanned ? freed : SHRINK_STOP;
20400c8efc3SThomas Hellström }
20500c8efc3SThomas Hellström 
20600c8efc3SThomas Hellström /* Wake up the device for shrinking. */
xe_shrinker_pm(struct work_struct * work)20700c8efc3SThomas Hellström static void xe_shrinker_pm(struct work_struct *work)
20800c8efc3SThomas Hellström {
20900c8efc3SThomas Hellström 	struct xe_shrinker *shrinker =
21000c8efc3SThomas Hellström 		container_of(work, typeof(*shrinker), pm_worker);
21100c8efc3SThomas Hellström 
21200c8efc3SThomas Hellström 	xe_pm_runtime_get(shrinker->xe);
21300c8efc3SThomas Hellström 	xe_pm_runtime_put(shrinker->xe);
21400c8efc3SThomas Hellström }
21500c8efc3SThomas Hellström 
21600c8efc3SThomas Hellström /**
21700c8efc3SThomas Hellström  * xe_shrinker_create() - Create an xe per-device shrinker
21800c8efc3SThomas Hellström  * @xe: Pointer to the xe device.
21900c8efc3SThomas Hellström  *
22000c8efc3SThomas Hellström  * Returns: A pointer to the created shrinker on success,
22100c8efc3SThomas Hellström  * Negative error code on failure.
22200c8efc3SThomas Hellström  */
xe_shrinker_create(struct xe_device * xe)22300c8efc3SThomas Hellström struct xe_shrinker *xe_shrinker_create(struct xe_device *xe)
22400c8efc3SThomas Hellström {
22500c8efc3SThomas Hellström 	struct xe_shrinker *shrinker = kzalloc(sizeof(*shrinker), GFP_KERNEL);
22600c8efc3SThomas Hellström 
22700c8efc3SThomas Hellström 	if (!shrinker)
22800c8efc3SThomas Hellström 		return ERR_PTR(-ENOMEM);
22900c8efc3SThomas Hellström 
230*2d2f82e1SThomas Hellström 	shrinker->shrink = shrinker_alloc(0, "drm-xe_gem:%s", xe->drm.unique);
23100c8efc3SThomas Hellström 	if (!shrinker->shrink) {
23200c8efc3SThomas Hellström 		kfree(shrinker);
23300c8efc3SThomas Hellström 		return ERR_PTR(-ENOMEM);
23400c8efc3SThomas Hellström 	}
23500c8efc3SThomas Hellström 
23600c8efc3SThomas Hellström 	INIT_WORK(&shrinker->pm_worker, xe_shrinker_pm);
23700c8efc3SThomas Hellström 	shrinker->xe = xe;
23800c8efc3SThomas Hellström 	rwlock_init(&shrinker->lock);
23900c8efc3SThomas Hellström 	shrinker->shrink->count_objects = xe_shrinker_count;
24000c8efc3SThomas Hellström 	shrinker->shrink->scan_objects = xe_shrinker_scan;
24100c8efc3SThomas Hellström 	shrinker->shrink->private_data = shrinker;
24200c8efc3SThomas Hellström 	shrinker_register(shrinker->shrink);
24300c8efc3SThomas Hellström 
24400c8efc3SThomas Hellström 	return shrinker;
24500c8efc3SThomas Hellström }
24600c8efc3SThomas Hellström 
24700c8efc3SThomas Hellström /**
24800c8efc3SThomas Hellström  * xe_shrinker_destroy() - Destroy an xe per-device shrinker
24900c8efc3SThomas Hellström  * @shrinker: Pointer to the shrinker to destroy.
25000c8efc3SThomas Hellström  */
xe_shrinker_destroy(struct xe_shrinker * shrinker)25100c8efc3SThomas Hellström void xe_shrinker_destroy(struct xe_shrinker *shrinker)
25200c8efc3SThomas Hellström {
25300c8efc3SThomas Hellström 	xe_assert(shrinker->xe, !shrinker->shrinkable_pages);
25400c8efc3SThomas Hellström 	xe_assert(shrinker->xe, !shrinker->purgeable_pages);
25500c8efc3SThomas Hellström 	shrinker_free(shrinker->shrink);
25600c8efc3SThomas Hellström 	flush_work(&shrinker->pm_worker);
25700c8efc3SThomas Hellström 	kfree(shrinker);
25800c8efc3SThomas Hellström }
259