On Fri, Sep 04, 2026 at 03:48:46PM +0100, Tvrtko Ursulin wrote:
>
> 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.
>
Ah, the REQUIREMENTS are the same for the current test, so this is outdated
and amdgpu is another acceptable driver/device too.
> > + */
> > +
> > +/**
> > + * 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?
>
I will look into it.
Thomas, any comment about this?
> > +
> > + /* 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, ¤t);
> > + 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)?
>
The NONBLOCK write will trigger eviction and can be interrupted. Then, when
we try to allocate again, it will trigger such eviction and dmem.max should
be respected.
Thanks.
Cascardo.
> 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 },
> > { }
> > };
> >
>