public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
From: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
To: igt-dev@lists.freedesktop.org
Cc: dev@lankhorst.se, "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
Subject: [PATCH i-g-t 1/5] lib/igt_cgroup: add cgroup v2 and dmem controller helpers
Date: Thu, 26 Mar 2026 17:10:03 +0100	[thread overview]
Message-ID: <20260326161007.39294-2-thomas.hellstrom@linux.intel.com> (raw)
In-Reply-To: <20260326161007.39294-1-thomas.hellstrom@linux.intel.com>

Add igt_cgroup, a library module providing helpers to create and manage
cgroup v2 sub-cgroups from IGT tests, with support for the dmem
controller that governs device memory (e.g. GPU VRAM) limits.

The API covers:
 - igt_cgroup_new() / igt_cgroup_free(): create and destroy a named
   sub-cgroup under the unified cgroupv2 hierarchy, enabling the dmem
   controller automatically.
 - igt_cgroup_move_current(): move the calling process into a cgroup.
 - igt_cgroup_dmem_set/get_max/min/low(): write and read dmem.max,
   dmem.min and dmem.low for a named device memory region.
 - igt_cgroup_dmem_get_current(): read current per-cgroup device memory
   usage.
 - igt_cgroup_dmem_get_system_current(): read system-wide device memory
   usage from the root cgroup.
 - igt_cgroup_dmem_get_capacity(): read total region capacity from the
   root cgroup's dmem.capacity file.
 - igt_cgroup_dmem_regions() / igt_cgroup_dmem_regions_free(): enumerate
   all registered device memory regions.

All public API functions that can fail use igt_assert internally rather
than returning error codes, following the IGT convention.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 lib/igt.h        |   1 +
 lib/igt_cgroup.c | 644 +++++++++++++++++++++++++++++++++++++++++++++++
 lib/igt_cgroup.h |  58 +++++
 lib/meson.build  |   1 +
 4 files changed, 704 insertions(+)
 create mode 100644 lib/igt_cgroup.c
 create mode 100644 lib/igt_cgroup.h

diff --git a/lib/igt.h b/lib/igt.h
index 173ca70bf..d8e5de7dc 100644
--- a/lib/igt.h
+++ b/lib/igt.h
@@ -27,6 +27,7 @@
 #include "drmtest.h"
 #include "i915_3d.h"
 #include "igt_aux.h"
+#include "igt_cgroup.h"
 #include "igt_configfs.h"
 #include "igt_core.h"
 #include "igt_debugfs.h"
diff --git a/lib/igt_cgroup.c b/lib/igt_cgroup.c
new file mode 100644
index 000000000..86aa537b5
--- /dev/null
+++ b/lib/igt_cgroup.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2025 Intel Corporation
+ */
+
+/**
+ * SECTION:igt_cgroup
+ * @short_description: cgroup v2 helpers for IGT tests
+ * @title: cgroup
+ * @include: igt_cgroup.h
+ *
+ * This library provides helpers for creating and managing cgroup v2
+ * sub-cgroups from IGT tests, including support for the dmem controller
+ * which governs device memory (e.g. GPU VRAM) limits.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "igt_cgroup.h"
+#include "igt_core.h"
+#include "igt_fs.h"
+
+#ifndef CGROUP2_SUPER_MAGIC
+#define CGROUP2_SUPER_MAGIC 0x63677270
+#endif
+
+/**
+ * struct igt_cgroup - Opaque handle to a cgroup v2 sub-cgroup.
+ * @dirfd: File descriptor for the cgroup directory.
+ * @path: Absolute path to the cgroup directory.
+ * @parent_path: Absolute path to the parent cgroup directory.
+ *
+ * Allocated by igt_cgroup_new() and freed by igt_cgroup_free().
+ */
+struct igt_cgroup {
+	int dirfd;
+	char *path;
+	char *parent_path;
+};
+
+static const char *cgroupv2_mount(void)
+{
+	static const char *path;
+	static const char * const candidates[] = {
+		"/sys/fs/cgroup",
+		"/sys/fs/cgroup/unified",
+		NULL,
+	};
+	struct statfs st;
+	int i;
+
+	if (path)
+		return path;
+
+	for (i = 0; candidates[i]; i++) {
+		if (statfs(candidates[i], &st) == 0 &&
+		    (unsigned long)st.f_type == CGROUP2_SUPER_MAGIC) {
+			path = candidates[i];
+			return path;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Write "+controller" to @cgroup_path/cgroup.subtree_control to enable
+ * the named controller for children of that cgroup.
+ */
+static int enable_controller(const char *cgroup_path, const char *controller)
+{
+	char path[PATH_MAX];
+	char cmd[64];
+	ssize_t ret;
+	int fd;
+
+	snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path);
+	snprintf(cmd, sizeof(cmd), "+%s", controller);
+
+	fd = open(path, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+
+	ret = write(fd, cmd, strlen(cmd));
+	close(fd);
+
+	return (ret < 0) ? -errno : 0;
+}
+
+/*
+ * Move every PID listed in @cgroup_path/cgroup.procs to
+ * @parent_path/cgroup.procs.  Silently ignores individual failures
+ * (a PID may have exited between reading and writing).
+ */
+static void drain_procs_to_parent(const char *cgroup_path,
+				  const char *parent_path)
+{
+	char proc_path[PATH_MAX];
+	char parent_procs[PATH_MAX];
+	int parent_fd;
+	FILE *f;
+	int pid;
+
+	snprintf(proc_path, sizeof(proc_path), "%s/cgroup.procs", cgroup_path);
+	snprintf(parent_procs, sizeof(parent_procs), "%s/cgroup.procs", parent_path);
+
+	parent_fd = open(parent_procs, O_WRONLY);
+	if (parent_fd < 0)
+		return;
+
+	f = fopen(proc_path, "r");
+	if (f) {
+		while (fscanf(f, "%d", &pid) == 1) {
+			char pidbuf[32];
+			ssize_t len = snprintf(pidbuf, sizeof(pidbuf), "%d", pid);
+
+			write(parent_fd, pidbuf, len);
+		}
+		fclose(f);
+	}
+
+	close(parent_fd);
+}
+
+/**
+ * igt_cgroup_new() - Create a new cgroup v2 sub-cgroup.
+ * @name: Name for the new cgroup directory.
+ *
+ * Creates a sub-cgroup named @name under the system's unified cgroupv2
+ * hierarchy.  The dmem controller is enabled in the parent's
+ * subtree_control so that igt_cgroup_dmem_set_max() and friends take effect
+ * immediately.
+ *
+ * Return: Pointer to an &struct igt_cgroup on success, %NULL on failure.
+ */
+struct igt_cgroup *igt_cgroup_new(const char *name)
+{
+	struct igt_cgroup *cg;
+	const char *mount;
+	int ret;
+
+	mount = cgroupv2_mount();
+	if (!mount) {
+		igt_debug("cgroup v2 not found\n");
+		return NULL;
+	}
+
+	cg = calloc(1, sizeof(*cg));
+	if (!cg)
+		return NULL;
+
+	cg->parent_path = strdup(mount);
+	if (!cg->parent_path)
+		goto err_free;
+
+	if (asprintf(&cg->path, "%s/%s", mount, name) < 0) {
+		cg->path = NULL;
+		goto err_parent;
+	}
+
+	/*
+	 * Try to enable the dmem controller in the parent's subtree_control.
+	 * Ignore EINVAL which the kernel returns when the controller is already
+	 * listed (i.e. already enabled).
+	 */
+	ret = enable_controller(mount, "dmem");
+	if (ret < 0 && ret != -EINVAL)
+		igt_debug("Failed to enable dmem controller in %s: %d\n",
+			  mount, ret);
+
+	if (mkdir(cg->path, 0755) < 0 && errno != EEXIST) {
+		igt_debug("Failed to create cgroup %s: %m\n", cg->path);
+		goto err_path;
+	}
+
+	cg->dirfd = open(cg->path, O_RDONLY | O_DIRECTORY);
+	if (cg->dirfd < 0) {
+		igt_debug("Failed to open cgroup dir %s: %m\n", cg->path);
+		goto err_rmdir;
+	}
+
+	return cg;
+
+err_rmdir:
+	rmdir(cg->path);
+err_path:
+	free(cg->path);
+err_parent:
+	free(cg->parent_path);
+err_free:
+	free(cg);
+	return NULL;
+}
+
+/**
+ * igt_cgroup_free() - Destroy a cgroup and release its resources.
+ * @cg: The cgroup to destroy.
+ *
+ * Moves any processes still running inside @cg back to the parent cgroup,
+ * removes the cgroup directory, and frees all associated memory.
+ * After this call @cg must not be used.
+ */
+void igt_cgroup_free(struct igt_cgroup *cg)
+{
+	if (!cg)
+		return;
+
+	drain_procs_to_parent(cg->path, cg->parent_path);
+
+	close(cg->dirfd);
+
+	if (rmdir(cg->path) < 0)
+		igt_debug("Failed to remove cgroup %s: %m\n", cg->path);
+
+	free(cg->path);
+	free(cg->parent_path);
+	free(cg);
+}
+
+/**
+ * igt_cgroup_move_current() - Move the calling process into a cgroup.
+ * @cg: Target cgroup.
+ *
+ * Writes the calling process's PID to @cg's cgroup.procs file, transferring
+ * it into the cgroup.  All threads of the process move together.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_move_current(struct igt_cgroup *cg)
+{
+	char pidbuf[32];
+	ssize_t len;
+	int fd, ret;
+
+	len = snprintf(pidbuf, sizeof(pidbuf), "%d", (int)getpid());
+
+	fd = openat(cg->dirfd, "cgroup.procs", O_WRONLY);
+	igt_assert_f(fd >= 0, "Failed to open cgroup.procs: %m\n");
+
+	ret = write(fd, pidbuf, len);
+	close(fd);
+
+	igt_assert_f(ret == len, "Failed to write PID to cgroup.procs: %m\n");
+}
+
+/*
+ * Parse a single dmem interface file line of the form "region_name value\n"
+ * where value is either a decimal byte count or the string "max".
+ * Returns 0 and writes to *out on success, -EINVAL on parse error.
+ */
+static int dmem_parse_line(char *line, const char *region, uint64_t *out)
+{
+	char *space = strchr(line, ' ');
+
+	if (!space)
+		return -EINVAL;
+
+	*space = '\0';
+	if (strcmp(line, region) != 0)
+		return -ENOENT;
+
+	if (strcmp(space + 1, "max") == 0) {
+		*out = IGT_CGROUP_DMEM_MAX;
+		return 0;
+	}
+
+	errno = 0;
+	*out = strtoull(space + 1, &space, 10);
+	if (errno || *space != '\0')
+		return -EINVAL;
+
+	return 0;
+}
+
+/*
+ * Read a dmem interface file opened relative to @dirfd, searching for
+ * @region.  On success writes the region's value to @out and returns 0.
+ * Returns -ENOENT when @region is absent, or a negative errno otherwise.
+ */
+static int dmem_read_region(int dirfd, const char *file,
+			    const char *region, uint64_t *out)
+{
+	char buf[4096];
+	char *line, *saveptr;
+	ssize_t n;
+	int fd;
+
+	fd = openat(dirfd, file, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	n = igt_readn(fd, buf, sizeof(buf) - 1);
+	close(fd);
+	if (n < 0)
+		return (int)n;
+	buf[n] = '\0';
+
+	for (line = strtok_r(buf, "\n", &saveptr); line;
+	     line = strtok_r(NULL, "\n", &saveptr)) {
+		int ret = dmem_parse_line(line, region, out);
+
+		if (ret != -ENOENT)
+			return ret;
+	}
+
+	return -ENOENT;
+}
+
+/*
+ * Write "region_name value" (or "region_name max") to the dmem interface
+ * file @file opened relative to @dirfd.
+ * Returns 0 on success, negative errno on failure.
+ */
+static int dmem_write_region(int dirfd, const char *file,
+			     const char *region, uint64_t bytes)
+{
+	char buf[PATH_MAX + 64];
+	ssize_t len;
+	int fd, ret;
+
+	if (bytes == IGT_CGROUP_DMEM_MAX)
+		len = snprintf(buf, sizeof(buf), "%s max", region);
+	else
+		len = snprintf(buf, sizeof(buf), "%s %" PRIu64, region, bytes);
+
+	fd = openat(dirfd, file, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+
+	do {
+		ret = write(fd, buf, len);
+		if (ret < 0 && errno == EINTR)
+			igt_debug("dmem cgroup write interrupted by signal, retrying\n");
+	} while (ret < 0 && errno == EINTR);
+	close(fd);
+
+	return (ret < 0) ? -errno : 0;
+}
+
+/**
+ * __igt_cgroup_dmem_set_max() - Set the hard device memory limit, returning errors.
+ * @cg: Target cgroup.
+ * @region: Device memory region name (e.g. "drm/0000:03:00.0/vram0").
+ * @bytes: Hard limit in bytes.  Use %IGT_CGROUP_DMEM_MAX for no limit.
+ *
+ * Like igt_cgroup_dmem_set_max() but returns an error code instead of
+ * asserting, allowing callers to handle expected failures such as -%EBUSY
+ * (current usage already exceeds the requested limit).
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int __igt_cgroup_dmem_set_max(struct igt_cgroup *cg, const char *region,
+			      uint64_t bytes)
+{
+	return dmem_write_region(cg->dirfd, "dmem.max", region, bytes);
+}
+
+/**
+ * igt_cgroup_dmem_set_max() - Set the hard device memory limit for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name (e.g. "drm/0000:03:00.0/vram0").
+ * @bytes: Hard limit in bytes.  Use %IGT_CGROUP_DMEM_MAX for no limit.
+ *
+ * Writes @bytes to dmem.max for @region inside @cg.  Allocation attempts
+ * that would push usage past this limit fail with -EAGAIN in the kernel.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_set_max(struct igt_cgroup *cg, const char *region,
+			     uint64_t bytes)
+{
+	igt_assert_f(__igt_cgroup_dmem_set_max(cg, region, bytes) == 0,
+		     "Failed to set dmem.max for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_set_min() - Set the hard protection threshold for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name.
+ * @bytes: Hard protection threshold in bytes.  Pass 0 to disable.
+ *
+ * Writes @bytes to dmem.min for @region inside @cg.  Device memory below
+ * this threshold is never reclaimed regardless of system pressure.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_set_min(struct igt_cgroup *cg, const char *region,
+			     uint64_t bytes)
+{
+	igt_assert_f(dmem_write_region(cg->dirfd, "dmem.min", region, bytes) == 0,
+		     "Failed to set dmem.min for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_set_low() - Set the soft protection threshold for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name.
+ * @bytes: Soft protection threshold in bytes.  Pass 0 to disable.
+ *
+ * Writes @bytes to dmem.low for @region inside @cg.  Device memory below
+ * this threshold is only reclaimed when no unprotected memory remains.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_set_low(struct igt_cgroup *cg, const char *region,
+			     uint64_t bytes)
+{
+	igt_assert_f(dmem_write_region(cg->dirfd, "dmem.low", region, bytes) == 0,
+		     "Failed to set dmem.low for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_get_current() - Read current device memory usage for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name.
+ * @out: Receives the current usage in bytes.
+ *
+ * Reads dmem.current from @cg and returns the usage for @region.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_get_current(struct igt_cgroup *cg, const char *region,
+				 uint64_t *out)
+{
+	igt_assert_f(dmem_read_region(cg->dirfd, "dmem.current", region, out) == 0,
+		     "Failed to read dmem.current for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_get_max() - Read the configured hard limit for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name.
+ * @out: Receives the limit in bytes, or %IGT_CGROUP_DMEM_MAX if unset.
+ *
+ * Reads dmem.max from @cg for @region.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_get_max(struct igt_cgroup *cg, const char *region,
+			     uint64_t *out)
+{
+	igt_assert_f(dmem_read_region(cg->dirfd, "dmem.max", region, out) == 0,
+		     "Failed to read dmem.max for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_get_min() - Read the configured hard protection threshold for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name.
+ * @out: Receives the threshold in bytes.
+ *
+ * Reads dmem.min from @cg for @region.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_get_min(struct igt_cgroup *cg, const char *region,
+			     uint64_t *out)
+{
+	igt_assert_f(dmem_read_region(cg->dirfd, "dmem.min", region, out) == 0,
+		     "Failed to read dmem.min for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_get_low() - Read the configured soft protection threshold for a region.
+ * @cg: Target cgroup.
+ * @region: Device memory region name.
+ * @out: Receives the threshold in bytes.
+ *
+ * Reads dmem.low from @cg for @region.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_get_low(struct igt_cgroup *cg, const char *region,
+			     uint64_t *out)
+{
+	igt_assert_f(dmem_read_region(cg->dirfd, "dmem.low", region, out) == 0,
+		     "Failed to read dmem.low for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_available() - Check if the dmem cgroup controller is available.
+ *
+ * Probes the cgroup v2 hierarchy for the presence of a dmem.capacity file at
+ * the root, indicating that the kernel dmem controller is compiled in and at
+ * least one device memory region has been registered.
+ *
+ * Return: %true if the dmem controller is available, %false otherwise.
+ */
+bool igt_cgroup_dmem_available(void)
+{
+	char **regions = igt_cgroup_dmem_regions();
+
+	if (!regions)
+		return false;
+
+	igt_cgroup_dmem_regions_free(regions);
+	return true;
+}
+
+/**
+ * igt_cgroup_dmem_regions() - Enumerate all registered device memory regions.
+ *
+ * Reads the root cgroup's dmem.capacity file and returns a NULL-terminated
+ * array of region name strings.  Each name can be passed directly to
+ * igt_cgroup_dmem_get_capacity(), igt_cgroup_dmem_get_current(), and the
+ * igt_cgroup_dmem_set_*() / igt_cgroup_dmem_get_*() family.
+ *
+ * Free the returned array with igt_cgroup_dmem_regions_free().
+ *
+ * Return: A NULL-terminated array of strings on success, %NULL if cgroupv2
+ * is unavailable or no regions are registered.
+ */
+char **igt_cgroup_dmem_regions(void)
+{
+	char buf[4096];
+	char *line, *saveptr, *space, *name;
+	char **regions = NULL, **tmp;
+	int count = 0;
+	const char *mount;
+	ssize_t n;
+	int dirfd, fd;
+
+	mount = cgroupv2_mount();
+	if (!mount)
+		return NULL;
+
+	dirfd = open(mount, O_RDONLY | O_DIRECTORY);
+	if (dirfd < 0)
+		return NULL;
+
+	fd = openat(dirfd, "dmem.capacity", O_RDONLY);
+	close(dirfd);
+	if (fd < 0)
+		return NULL;
+
+	n = igt_readn(fd, buf, sizeof(buf) - 1);
+	close(fd);
+	if (n <= 0)
+		return NULL;
+	buf[n] = '\0';
+
+	for (line = strtok_r(buf, "\n", &saveptr); line;
+	     line = strtok_r(NULL, "\n", &saveptr)) {
+		space = strchr(line, ' ');
+
+		if (!space)
+			continue;
+		*space = '\0';
+
+		name = strdup(line);
+		if (!name)
+			goto err;
+
+		tmp = realloc(regions, (count + 2) * sizeof(*regions));
+		if (!tmp) {
+			free(name);
+			goto err;
+		}
+		regions = tmp;
+		regions[count++] = name;
+		regions[count] = NULL;
+	}
+
+	return regions;
+
+err:
+	igt_cgroup_dmem_regions_free(regions);
+	return NULL;
+}
+
+/**
+ * igt_cgroup_dmem_regions_free() - Free a region list returned by igt_cgroup_dmem_regions().
+ * @regions: NULL-terminated array returned by igt_cgroup_dmem_regions().
+ *
+ * Frees each string in @regions and the array itself.  Safe to call with
+ * %NULL.
+ */
+void igt_cgroup_dmem_regions_free(char **regions)
+{
+	int i;
+
+	if (!regions)
+		return;
+
+	for (i = 0; regions[i]; i++)
+		free(regions[i]);
+
+	free(regions);
+}
+
+/**
+ * igt_cgroup_dmem_get_capacity() - Read total device memory capacity for a region.
+ * @region: Device memory region name.
+ * @out: Receives the total capacity in bytes.
+ *
+ * Reads dmem.capacity from the root cgroup and returns the capacity for
+ * @region.  This reflects the maximum allocatable bytes, excluding memory
+ * reserved by the kernel for internal use.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_get_capacity(const char *region, uint64_t *out)
+{
+	const char *mount;
+	int dirfd, ret;
+
+	mount = cgroupv2_mount();
+	igt_assert_f(mount, "cgroup v2 not available\n");
+
+	dirfd = open(mount, O_RDONLY | O_DIRECTORY);
+	igt_assert_f(dirfd >= 0, "Failed to open cgroup root: %m\n");
+
+	ret = dmem_read_region(dirfd, "dmem.capacity", region, out);
+	close(dirfd);
+
+	igt_assert_f(ret == 0, "Failed to read dmem.capacity for region %s\n", region);
+}
+
+/**
+ * igt_cgroup_dmem_get_system_current() - Read system-wide device memory usage for a region.
+ * @region: Device memory region name.
+ * @out: Receives the total system-wide usage in bytes.
+ *
+ * Reads dmem.current from the root cgroup for @region.  This reflects the
+ * aggregate device memory usage across all cgroups on the system.
+ * Fails the test via igt_assert on error.
+ */
+void igt_cgroup_dmem_get_system_current(const char *region, uint64_t *out)
+{
+	const char *mount;
+	int dirfd, ret;
+
+	mount = cgroupv2_mount();
+	igt_assert_f(mount, "cgroup v2 not available\n");
+
+	dirfd = open(mount, O_RDONLY | O_DIRECTORY);
+	igt_assert_f(dirfd >= 0, "Failed to open cgroup root: %m\n");
+
+	ret = dmem_read_region(dirfd, "dmem.current", region, out);
+	close(dirfd);
+
+	igt_assert_f(ret == 0, "Failed to read root dmem.current for region %s\n", region);
+}
diff --git a/lib/igt_cgroup.h b/lib/igt_cgroup.h
new file mode 100644
index 000000000..1e67245a2
--- /dev/null
+++ b/lib/igt_cgroup.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2025 Intel Corporation
+ */
+
+#ifndef __IGT_CGROUP_H__
+#define __IGT_CGROUP_H__
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * IGT_CGROUP_DMEM_MAX - Sentinel value meaning "no device memory limit".
+ *
+ * Pass this to igt_cgroup_dmem_set_max() to remove a previously set limit,
+ * equivalent to writing "max" to the dmem.max interface file.
+ */
+#define IGT_CGROUP_DMEM_MAX UINT64_MAX
+
+/**
+ * struct igt_cgroup - Opaque handle to a cgroup v2 sub-cgroup.
+ *
+ * Allocated by igt_cgroup_new() and freed by igt_cgroup_free().
+ * All other functions in this module take a pointer to this type.
+ */
+struct igt_cgroup;
+
+struct igt_cgroup *igt_cgroup_new(const char *name);
+void igt_cgroup_free(struct igt_cgroup *cg);
+
+void igt_cgroup_move_current(struct igt_cgroup *cg);
+
+int  __igt_cgroup_dmem_set_max(struct igt_cgroup *cg, const char *region,
+			       uint64_t bytes);
+void igt_cgroup_dmem_set_max(struct igt_cgroup *cg, const char *region,
+			     uint64_t bytes);
+void igt_cgroup_dmem_set_min(struct igt_cgroup *cg, const char *region,
+			     uint64_t bytes);
+void igt_cgroup_dmem_set_low(struct igt_cgroup *cg, const char *region,
+			     uint64_t bytes);
+
+void igt_cgroup_dmem_get_max(struct igt_cgroup *cg, const char *region,
+			     uint64_t *out);
+void igt_cgroup_dmem_get_min(struct igt_cgroup *cg, const char *region,
+			     uint64_t *out);
+void igt_cgroup_dmem_get_low(struct igt_cgroup *cg, const char *region,
+			     uint64_t *out);
+
+void igt_cgroup_dmem_get_current(struct igt_cgroup *cg, const char *region,
+				 uint64_t *out);
+void igt_cgroup_dmem_get_capacity(const char *region, uint64_t *out);
+void igt_cgroup_dmem_get_system_current(const char *region, uint64_t *out);
+
+bool igt_cgroup_dmem_available(void);
+char **igt_cgroup_dmem_regions(void);
+void igt_cgroup_dmem_regions_free(char **regions);
+
+#endif /* __IGT_CGROUP_H__ */
diff --git a/lib/meson.build b/lib/meson.build
index 5c4829345..f26608b7a 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -18,6 +18,7 @@ lib_sources = [
 	'i915/i915_crc.c',
 	'igt_collection.c',
 	'igt_color_encoding.c',
+	'igt_cgroup.c',
 	'igt_configfs.c',
 	'igt_facts.c',
 	'igt_crc.c',
-- 
2.53.0


  reply	other threads:[~2026-03-26 16:10 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-26 16:10 [PATCH i-g-t 0/5] Initial dmem cgroup support Thomas Hellström
2026-03-26 16:10 ` Thomas Hellström [this message]
2026-03-26 16:10 ` [PATCH i-g-t 2/5] tests/cgroup_dmem: add dmem cgroup controller test Thomas Hellström
2026-03-26 16:10 ` [PATCH i-g-t 3/5] lib/xe: add xe_cgroup_region_name() helper Thomas Hellström
2026-03-26 16:10 ` [PATCH i-g-t 4/5] lib/xe: add __xe_vm_bind_lr_sync() failable bind helper Thomas Hellström
2026-03-26 16:10 ` [PATCH i-g-t 5/5] tests/xe_cgroups: add dmem cgroup eviction test Thomas Hellström
2026-03-26 23:42 ` ✓ Xe.CI.BAT: success for Initial dmem cgroup support Patchwork
2026-03-27  0:00 ` ✓ i915.CI.BAT: " Patchwork
2026-03-27 17:49 ` ✓ Xe.CI.FULL: " Patchwork
2026-03-28  0:45 ` ✗ i915.CI.Full: failure " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260326161007.39294-2-thomas.hellstrom@linux.intel.com \
    --to=thomas.hellstrom@linux.intel.com \
    --cc=dev@lankhorst.se \
    --cc=igt-dev@lists.freedesktop.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox