drm/xe: Defer buffer object shrinker write-backs and GPU waits
authorThomas Hellström <thomas.hellstrom@linux.intel.com>
Tue, 5 Aug 2025 07:48:42 +0000 (09:48 +0200)
committerRodrigo Vivi <rodrigo.vivi@intel.com>
Tue, 12 Aug 2025 16:52:26 +0000 (12:52 -0400)
When the xe buffer-object shrinker allows GPU waits and write-back,
(typically from kswapd), perform multiple passes, skipping
subsequent passes if the shrinker number of scanned objects target
is reached.

1) Without GPU waits and write-back
2) Without write-back
3) With both GPU-waits and write-back

This is to avoid stalls and costly write- and readbacks unless they
are really necessary.

v2:
- Don't test for scan completion twice. (Stuart Summers)
- Update tags.

Reported-by: melvyn <melvyn2@dnsense.pub>
Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/5557
Cc: Summers Stuart <stuart.summers@intel.com>
Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos")
Cc: <stable@vger.kernel.org> # v6.15+
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Stuart Summers <stuart.summers@intel.com>
Link: https://lore.kernel.org/r/20250805074842.11359-1-thomas.hellstrom@linux.intel.com
(cherry picked from commit 80944d334182ce5eb27d00e2bf20a88bfc32dea1)
Signed-off-by: Rodrigo Vivi <rodrigo.vivi@intel.com>
drivers/gpu/drm/xe/xe_shrinker.c

index 1c3c04d52f554f001bd50b58b5e25fc7bf1a3b4a..90244fe59b5993a9d29a24d5e806c8c32d6e7d9c 100644 (file)
@@ -54,10 +54,10 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea
        write_unlock(&shrinker->lock);
 }
 
-static s64 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)
+static s64 __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)
 {
        unsigned int mem_type;
        s64 freed = 0, lret;
@@ -93,6 +93,48 @@ static s64 xe_shrinker_walk(struct xe_device *xe,
        return freed;
 }
 
+/*
+ * Try shrinking idle objects without writeback first, then if not sufficient,
+ * try also non-idle objects and finally if that's not sufficient either,
+ * add writeback. This avoids stalls and explicit writebacks with light or
+ * moderate memory pressure.
+ */
+static s64 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)
+{
+       bool no_wait_gpu = true;
+       struct xe_bo_shrink_flags save_flags = flags;
+       s64 lret, freed;
+
+       swap(no_wait_gpu, ctx->no_wait_gpu);
+       save_flags.writeback = false;
+       lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned);
+       swap(no_wait_gpu, ctx->no_wait_gpu);
+       if (lret < 0 || *scanned >= to_scan)
+               return lret;
+
+       freed = lret;
+       if (!ctx->no_wait_gpu) {
+               lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned);
+               if (lret < 0)
+                       return lret;
+               freed += lret;
+               if (*scanned >= to_scan)
+                       return freed;
+       }
+
+       if (flags.writeback) {
+               lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned);
+               if (lret < 0)
+                       return lret;
+               freed += lret;
+       }
+
+       return freed;
+}
+
 static unsigned long
 xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
 {
@@ -199,6 +241,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con
                runtime_pm = xe_shrinker_runtime_pm_get(shrinker, true, 0, can_backup);
 
        shrink_flags.purge = false;
+
        lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags,
                                nr_to_scan, &nr_scanned);
        if (lret >= 0)