From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 6D2D810A88E3 for ; Thu, 26 Mar 2026 16:10:40 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 1526210EA44; Thu, 26 Mar 2026 16:10:40 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.b="iXSxxHNL"; dkim-atps=neutral Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.21]) by gabe.freedesktop.org (Postfix) with ESMTPS id B179510E25F for ; Thu, 26 Mar 2026 16:10:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1774541429; x=1806077429; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=PJUnJ2i25JSjFGqx92VXgtGyynJvW+FtdPriTnu3RI8=; b=iXSxxHNLMWuLF+h7f0i2UHBgW3wr/4AvRXn1FM8a9pO3dDNOrzmncgx9 eVQAtxgW7NMh4rV/aNxJTbFPcWhNDoD9nWVFPa2eSc3QIT7G7+5dAo1y/ HLvpICoCIsleOcIUkr4N7TGKZzNo7/6H6TSTVxdKkAN2kZTIeTHKk8nvI gwGCt+3831o37kDwpSpPpmigNjR+Rcf7zhvRY729k00UzY2e/vlLWQaC3 pu2HfJt+IDAjeZh9ccDxe0ArPX7qfZrbX7tW+SRNpj2bttEA2LtZi7XFl CwOl32HuKHmOhEvIBk9J2HfEs4eFQySlfq6ZsN8LTQhntrEuny01Xpepq Q==; X-CSE-ConnectionGUID: nBZ4M/X3QRGrvGRVpEKRnA== X-CSE-MsgGUID: 2IOEc8FmSyKbqKJrQCjCWA== X-IronPort-AV: E=McAfee;i="6800,10657,11741"; a="75488968" X-IronPort-AV: E=Sophos;i="6.23,142,1770624000"; d="scan'208";a="75488968" Received: from orviesa004.jf.intel.com ([10.64.159.144]) by orvoesa113.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 26 Mar 2026 09:10:29 -0700 X-CSE-ConnectionGUID: KNhCvLwdQauwTlbYyCHqEA== X-CSE-MsgGUID: s5Lumu5lQ0y3akJjPHYzcA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.23,142,1770624000"; d="scan'208";a="229505934" Received: from klitkey1-mobl1.ger.corp.intel.com (HELO fedora) ([10.245.245.251]) by orviesa004-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 26 Mar 2026 09:10:28 -0700 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= To: igt-dev@lists.freedesktop.org Cc: dev@lankhorst.se, =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= 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 Message-ID: <20260326161007.39294-2-thomas.hellstrom@linux.intel.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260326161007.39294-1-thomas.hellstrom@linux.intel.com> References: <20260326161007.39294-1-thomas.hellstrom@linux.intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: igt-dev@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development mailing list for IGT GPU Tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: igt-dev-bounces@lists.freedesktop.org Sender: "igt-dev" 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +/** + * 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