On 03/09/2026 23:00, Thadeu Lima de Souza Cascardo wrote:
From: Thomas Hellström <[email protected]>

The write_eviction subtest:
  - Creates a sub-cgroup and moves the test process into it.
  - Sets a dmem.max limit on the first VRAM region (up to 4 GiB, or
    the full capacity if smaller).
- Fills VRAM by repeatedly creating BOs placed in VRAM, depending on card
   support.
  - Verifies that cgroup current usage is within the expected range when
    the limit is hit.
  - Lowers dmem.max in 128 MiB steps, waiting for usage to follow each
    reduction.

The write_eviction_interruptible subtest runs the same test with
SIGCONT signals injected via igt_fork_signal_helper() and reports the
number of signals received.  When a signal interrupts kernel-side
eviction, a small BO allocation is used to re-trigger it.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <[email protected]>
Signed-off-by: Thadeu Lima de Souza Cascardo <[email protected]>
---
  tests/cgroup_dmem.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++
  1 file changed, 149 insertions(+)

diff --git a/tests/cgroup_dmem.c b/tests/cgroup_dmem.c
index ba5e6a2e3deb..0d3b415acd54 100644
--- a/tests/cgroup_dmem.c
+++ b/tests/cgroup_dmem.c
@@ -36,9 +36,12 @@
#define BO_SIZE SZ_64M
  #define MAX_LIMIT             ((uint64_t)4 * SZ_1G)
+#define EVICT_STEP             SZ_128M
  #define USAGE_POLL_MS         10
  #define USAGE_DROP_TIMEOUT_MS 1000
+#define TEST_INTERRUPTIBLE (1 << 0)
+
  /**
   * SUBTEST: simple
   * DESCRIPTION:
@@ -61,6 +64,62 @@
   * REQUIREMENTS: xe or amdgpu device with at least one VRAM region
   */
+/**
+ * SUBTEST: write_eviction
+ * DESCRIPTION:
+ *   Create a dmem cgroup, move the current process into it and set the max
+ *   device memory limit for the first VRAM region to 4 GiB.  Then fill VRAM
+ *   by creating BOs with %DRM_XE_GEM_CREATE_FLAG_DEFER_BACKING (so that the
+ *   physical allocation is deferred until VM_BIND) and binding them into an
+ *   LR VM until the cgroup limit is hit.  Verify that the reported cgroup
+ *   current usage is within the expected range when the error occurs.
+ *   Finally lower the max limit in 256 MiB steps and verify that the cgroup
+ *   usage follows.
+ * REQUIREMENTS: must run as root; xe device with at least one VRAM region

It feels that adding an explicit igt_require(DRIVER_XE) to the test would be a good move.

+ */
+
+/**
+ * SUBTEST: write_eviction_interruptible
+ * DESCRIPTION:
+ *   Same as write_eviction but with SIGCONT signals injected throughout via
+ *   igt_fork_signal_helper() to verify that the dmem.max write path handles
+ *   signal interruption correctly.  A signal handler counts received signals
+ *   and the count is reported as debug output at the end of the test.
+ *   A signal interrupts the set-time eviction, and further eviction can be
+ *   triggered by an explicit allocation.
+ * REQUIREMENTS: must run as root; xe device with at least one VRAM region
+ */
+
+static atomic_int signal_count;
+static struct sigaction sigcont_oldact;
+
+static void sigcont_handler(int sig)
+{
+       atomic_fetch_add(&signal_count, 1);
+
+       /* Chain to the previous handler (IGT's dummy sig_handler) */
+       if (sigcont_oldact.sa_handler &&
+           sigcont_oldact.sa_handler != SIG_IGN &&
+           sigcont_oldact.sa_handler != SIG_DFL)
+               sigcont_oldact.sa_handler(sig);
+}
+
+static void install_sigcont_counter(void)
+{
+       struct sigaction sa;
+
+       atomic_store(&signal_count, 0);
+       igt_fork_signal_helper();
+       /*
+        * Install the counter after igt_fork_signal_helper() so our handler
+        * is not overwritten.  Save the old handler so we can chain to it.
+        */
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = sigcont_handler;
+       sigemptyset(&sa.sa_mask);
+       sigaction(SIGCONT, &sa, &sigcont_oldact);
+}
+
  static uint64_t wait_for_usage_drop(struct igt_cgroup *cg, const char *region,
                                    uint64_t limit)
  {
@@ -213,12 +272,102 @@ static void test_current(int fd, char *cg_region, 
unsigned int flags, const stru
        igt_cgroup_free(cg);
  }
+static void test_write_eviction(int fd, char *cg_region, unsigned int flags, const struct igt_dmem_driver *drv, void *ctx)
+{
+       struct igt_cgroup *cg;
+       void **handles;
+       int max_bo;
+       uint64_t current, capacity, cg_max, limit, after;
+       int err;
+
+       igt_cgroup_dmem_get_capacity(cg_region, &capacity);
+       igt_require_f(capacity >= 4 * BO_SIZE,
+                     "VRAM capacity (%"PRIu64" MiB) too small to test\n",
+                     capacity / SZ_1M);
+
+       /*
+        * Use up to 4 GiB, or the full capacity if the device has less.
+        * Leave one BO_SIZE worth of headroom so the device isn't completely
+        * exhausted before the cgroup limit is hit.
+        */
+       cg_max = min(MAX_LIMIT, capacity - BO_SIZE);
+       cg_max = ALIGN_DOWN(cg_max, EVICT_STEP);
+
+       if (flags & TEST_INTERRUPTIBLE)
+               install_sigcont_counter();

I stumbled upon IGT docs recommending igt_while_interruptible as a gentler alternative. Would that still work here and be simpler?

+
+       /* Create cgroup and move into it */
+       cg = igt_cgroup_new("igt_cgroups_test");
+       igt_cgroup_move_current(cg);
+       igt_cgroup_dmem_set_max(cg, cg_region, cg_max, false);
+
+       max_bo = (cg_max / BO_SIZE) + 8; /* headroom for overcommit */
+
+       handles = calloc(max_bo, sizeof(handles[0]));
+       igt_assert_f(handles, "failed to allocate handles array");
+
+       allocate_vram(handles, drv, ctx, max_bo, BO_SIZE);
+
+       igt_cgroup_dmem_get_current(cg, cg_region, &current);
+       igt_debug("After fill: cgroup current = %"PRIu64" MiB, "
+                 "max = %"PRIu64" MiB\n",
+                 current / SZ_1M, cg_max / SZ_1M);
+
+       igt_assert_f(current <= cg_max,
+                    "Usage %"PRIu64" MiB exceeds max %"PRIu64" MiB + slack\n",
+                    current / SZ_1M, cg_max / SZ_1M);
+
+       /* Phase 2: lower max in 256 MiB steps, verify usage follows */
+       limit = cg_max;
+       while (limit >= EVICT_STEP) {
+
+               limit -= EVICT_STEP;
+               igt_cgroup_dmem_set_max(cg, cg_region, limit, false);
+
+               igt_cgroup_dmem_get_current(cg, cg_region, &after);
+               igt_debug("Lowered max to %"PRIu64" MiB: usage = %"PRIu64" 
MiB\n",
+                         limit / SZ_1M, after / SZ_1M);
+
+               if (limit > EVICT_STEP) {
+                       if ((flags & TEST_INTERRUPTIBLE) && after > limit) {
+                               /* Let a new bo creation trigger eviction. */
+                               void *handle;
+                               err = drv->allocate_vram(ctx, BO_SIZE / 8, 
&handle);
+                               igt_assert_f(err == 0,
+                                       "Error trying to allocate more VRAM to 
trigger eviction.");
+                               drv->free_vram(ctx, handle);
+
+                               igt_cgroup_dmem_get_current(cg, cg_region, 
&after);
+                               igt_debug("Forced eviction max is %"PRIu64
+                                         " MiB: usage = %"PRIu64" MiB\n",
+                                         limit / SZ_1M, after / SZ_1M);
+                       }
+
+                       igt_assert_f(after <= limit,
+                                    "Usage %"PRIu64" MiB did not follow max 
%"PRIu64" MiB\n",
+                                    after / SZ_1M, limit / SZ_1M);

I was quite confused by what is the interrupt by signal business about. My best guess is the point is not to interrupt the write to the sysfs on the superficial level, but to interrupt the actual eviction process which happens during the write? Is that a cgroup requirement that the new limit has to become effective (or attempted at least) during the write itself? And once interrupted it will not re-try it until new "activity" in the dmem cgroup happens (the dummy allocation above)?

Regards,

Tvrtko

+               }
+       }
+
+       if (flags & TEST_INTERRUPTIBLE) {
+               igt_stop_signal_helper();
+               igt_info("Signals received during test: %d\n",
+                        atomic_load(&signal_count));
+       }
+
+       /* Cleanup */
+       igt_cgroup_dmem_set_max(cg, cg_region, IGT_CGROUP_DMEM_MAX, false);
+       igt_cgroup_free(cg);
+}
+
  static const struct {
        const char *name;
        void (*test_fn)(int fd, char *cg_region, unsigned int flags, const 
struct igt_dmem_driver *drv, void *ctx);
        unsigned int flags;
  } subtests[] = {
        { "current", test_current, 0 },
+       { "write_eviction", test_write_eviction, 0 },
+       { "write_eviction_interruptible", test_write_eviction, 
TEST_INTERRUPTIBLE },
        { }
  };

Reply via email to