Hi Thadeu,
On 2026-09-03 at 19:00:30 -0300, 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
We do not have a subfield 'REQUIREMENTS', so why did you introduced this?
Second, almost all IGT tests should be run as root, so no point in
writing this down. imho just add this at end of description:
* usage follows. This test requires GPU device with at least one VRAM
region.
> + */
> +
> +/**
> + * 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
> + */
Same here.
Regards,
Kamil
> +
> +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();
> +
> + /* 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);
> + }
> + }
> +
> + 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 },
> { }
> };
>
>
> --
> 2.47.3
>