[PATCH i-g-t] tests/xe: Add xe_cgroup test
Maarten Lankhorst
maarten.lankhorst at linux.intel.com
Tue Dec 17 14:16:00 UTC 2024
Signed-off-by: Maarten Lankhorst <maarten.lankhorst at linux.intel.com>
---
tests/intel/xe_cgroup.c | 380 ++++++++++++++++++++++++++++++++++++++++
tests/meson.build | 1 +
2 files changed, 381 insertions(+)
create mode 100644 tests/intel/xe_cgroup.c
diff --git a/tests/intel/xe_cgroup.c b/tests/intel/xe_cgroup.c
new file mode 100644
index 000000000..e8899c16e
--- /dev/null
+++ b/tests/intel/xe_cgroup.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2023 Intel Corporation
+ */
+
+/**
+ * TEST: Check cgroup VRAM allocation limits
+ * Category: Software building block
+ * Sub-category: cgroup
+ * Test category: functionality test
+ * Run type: BAT
+ * Description: Test that the dmem cgroup works as intended.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "igt.h"
+#include "igt_device.h"
+#include "igt_sysfs.h"
+
+#include "xe_drm.h"
+#include "xe/xe_ioctl.h"
+#include "xe/xe_query.h"
+
+static int cgfd = -1, igtcg = -1, cg1 = -1, cg1_1 = -1, cg1_2 = -1, cg2 = -1;
+static char cgid[64];
+
+/*
+ * cgroups:
+ * / cg1_1
+ * /sys/fs/cgroup (cgfd) / igtcg / cg1 /- cg1_2
+ * / cg2
+ */
+
+static void set_cgrp(int cg)
+{
+ igt_sysfs_set_u32(cg, "cgroup.procs", getpid());
+}
+
+static inline void set_cg_val(int cg, const char *resource, int64_t val)
+{
+ if (val >= 0)
+ igt_assert(igt_sysfs_printf(cg, resource, "%s %"PRIu64, cgid, val));
+ else
+ igt_assert(igt_sysfs_printf(cg, resource, "%s max", cgid));
+}
+
+static void set_cgrp_min(int cg, int64_t min)
+{
+ set_cg_val(cg, "dmem.min", min);
+}
+
+static void set_cgrp_low(int cg, int64_t low)
+{
+ set_cg_val(cg, "dmem.low", low);
+}
+
+static void set_cgrp_max(int cg, int64_t max)
+{
+ set_cg_val(cg, "dmem.max", max);
+}
+
+static int64_t get_cg_val(int cg, const char *resource)
+{
+ char *devstr, *valstr, *str;
+ int64_t val;
+
+ str = igt_sysfs_get(cg, resource);
+ igt_assert(str);
+ devstr = strstr(str, cgid);
+ igt_assert(devstr);
+
+ /* Only care about the line describing this region, terminate if needed */
+ devstr = strtok(devstr, "\n");
+
+ valstr = strstr(devstr, " ") + 1;
+ if (!strcmp(valstr, "max"))
+ val = INT64_MAX;
+ else
+ val = strtoll(valstr, NULL, 10);
+
+ free(str);
+ return val;
+}
+
+static int64_t get_cgrp_min(int cg)
+{
+ return get_cg_val(cg, "dmem.min");
+}
+
+static int64_t get_cgrp_low(int cg)
+{
+ return get_cg_val(cg, "dmem.low");
+}
+
+static int64_t get_cgrp_max(int cg)
+{
+ return get_cg_val(cg, "dmem.max");
+}
+
+static int64_t get_cgrp_current(int cg)
+{
+ return get_cg_val(cg, "dmem.current");
+}
+
+static void assert_all_cgs_empty(void)
+{
+ igt_assert(!get_cgrp_current(igtcg));
+}
+
+static void reset_cgs(void)
+{
+ int i, fds[] = { igtcg, cg1, cg1_1, cg1_2, cg2 };
+ set_cgrp(cgfd);
+
+ for (i = 0; i < ARRAY_SIZE(fds); i++) {
+ int cg = fds[i];
+
+ set_cgrp_min(cg, 0);
+ set_cgrp_low(cg, 0);
+ set_cgrp_max(cg, -1);
+ }
+}
+
+static void cleanup_cg(int sig)
+{
+ /* Move self to root so we can rmdir */
+ set_cgrp(cgfd);
+
+ unlinkat(cg1, "cg1_1", AT_REMOVEDIR);
+ unlinkat(cg1, "cg1_2", AT_REMOVEDIR);
+ unlinkat(igtcg, "cg1", AT_REMOVEDIR);
+ unlinkat(igtcg, "cg2", AT_REMOVEDIR);
+ unlinkat(cgfd, "igtcg", AT_REMOVEDIR);
+
+ /* leak fd's at exit, nobody cares */
+}
+
+static int mkcgrp(int fd, const char *name)
+{
+ int err;
+
+ /* Check and enable controller */
+ igt_require(igt_sysfs_set(fd, "cgroup.subtree_control", "+dmem"));
+
+ err = mkdirat(fd, name, 0755);
+ if (!err || (err == -1 && errno == EEXIST))
+ err = openat(fd, name, O_RDONLY);
+ igt_assert(err >= 0);
+ return err;
+}
+
+static void create_cgroups(void)
+{
+ char *controllers;
+ cgfd = open("/sys/fs/cgroup", O_RDONLY);
+ igt_require(cgfd >= 0);
+
+ controllers = igt_sysfs_get(cgfd, "cgroup.controllers");
+ igt_require(controllers && strstr(controllers, "dmem"));
+ free(controllers);
+
+ igt_install_exit_handler(cleanup_cg);
+
+ /* Populate cg's */
+ igtcg = mkcgrp(cgfd, "igtcg");
+ cg1 = mkcgrp(igtcg, "cg1");
+ cg1_1 = mkcgrp(cg1, "cg1_1");
+ cg1_2 = mkcgrp(cg1, "cg1_2");
+ cg2 = mkcgrp(igtcg, "cg2");
+}
+
+static int bo_create_at(struct xe_device *xe, int cg, uint64_t bo_size)
+{
+ struct drm_xe_gem_create create = {
+ .placement = vram_if_possible(xe->fd, 0),
+ .cpu_caching = __xe_default_cpu_caching(xe->fd, create.placement, 0),
+ .size = bo_size,
+ };
+
+ set_cgrp(cg);
+
+ if (igt_ioctl(xe->fd, DRM_IOCTL_XE_GEM_CREATE, &create) < 0) {
+ igt_debug("Creating bo with size %"PRIu64" returned -1/%m\n", bo_size);
+ return -errno;
+ }
+
+ igt_debug("Creating bo with size %"PRIu64" and handle %u\n", bo_size, create.handle);
+ return create.handle;
+}
+
+/**
+ * SUBTEST: functional
+ * Description: Test if written values can be read back,
+ * to rule out copy/paste errors.
+ */
+static void test_functional(void)
+{
+ set_cgrp_min(igtcg, 12345);
+ set_cgrp_low(igtcg, 54321);
+ set_cgrp_max(igtcg, 67890);
+
+ igt_assert_eq_u64(get_cgrp_min(igtcg), 12345);
+ igt_assert_eq_u64(get_cgrp_low(igtcg), 54321);
+ igt_assert_eq_u64(get_cgrp_max(igtcg), 67890);
+
+ set_cgrp_min(igtcg, -1);
+ set_cgrp_low(igtcg, -1);
+ set_cgrp_max(igtcg, -1);
+
+ igt_assert_eq_u64(get_cgrp_min(igtcg), INT64_MAX);
+ igt_assert_eq_u64(get_cgrp_low(igtcg), INT64_MAX);
+ igt_assert_eq_u64(get_cgrp_max(igtcg), INT64_MAX);
+
+ set_cgrp_min(igtcg, 0);
+ set_cgrp_low(igtcg, 0);
+ set_cgrp_max(igtcg, 0);
+ igt_assert_eq_u64(get_cgrp_low(igtcg), 0);
+ igt_assert_eq_u64(get_cgrp_max(igtcg), 0);
+ igt_assert_eq_u64(get_cgrp_min(igtcg), 0);
+}
+
+/**
+ * SUBTEST: test-simple-max
+ * Description: Test that cgroup max limits are respected between
+ * various nestings of cgroups, and correct cgroups are evicted.
+ */
+static void test_simple_max(struct xe_device *xe)
+{
+ uint64_t bo_size = xe->default_alignment;
+ int bo1, bo1_1, bo1_2;
+
+ /* Test if we set cgroup limits, that they are respected */
+ assert_all_cgs_empty();
+
+ reset_cgs();
+
+ set_cgrp_max(igtcg, 0);
+ igt_assert(get_cgrp_max(igtcg) == 0);
+ set_cgrp_max(cg1, 2 * bo_size);
+ set_cgrp_max(cg1_1, 2 * bo_size);
+ set_cgrp_max(cg1_2, bo_size);
+ set_cgrp_max(cg2, -1);
+ igt_assert(get_cgrp_max(cg2) == INT64_MAX);
+
+ /* First check if top cg limit (of igtcg) is not ignored */
+ igt_assert_eq(-ENOMEM, bo_create_at(xe, cg1_1, bo_size));
+
+ assert_all_cgs_empty();
+ set_cgrp_max(igtcg, 4 * bo_size);
+
+ /* Same cg limit? */
+ bo1_1 = bo_create_at(xe, cg1_1, 2 * bo_size);
+ igt_assert(bo1_1 > 0);
+
+ set_cgrp_max(cg1, 3 * bo_size);
+ bo1_2 = bo_create_at(xe, cg1_2, bo_size);
+ igt_assert(bo1_2 > 0);
+
+ /* Create a too big bo and check if 1_2 is evicted */
+ igt_assert_eq(-ENOMEM, bo_create_at(xe, cg1_2, 4 * bo_size));
+ igt_assert_eq_u64(0, get_cgrp_current(cg1_2));
+
+ gem_close(xe->fd, bo1_2);
+ bo1_2 = bo_create_at(xe, cg1_2, bo_size);
+ igt_assert(bo1_2 > 0);
+
+ set_cgrp_max(cg1, 4 * bo_size);
+ bo1 = bo_create_at(xe, cg1_1, bo_size);
+ igt_assert(bo1 > 0);
+
+ gem_close(xe->fd, bo1_2);
+ gem_close(xe->fd, bo1_1);
+ gem_close(xe->fd, bo1);
+
+ assert_all_cgs_empty();
+}
+
+static void create_cg1_min(struct xe_device *xe, int *bo1_1, int *bo1_2)
+{
+ uint64_t bo_size = xe->default_alignment;
+
+ if (*bo1_1 > 0)
+ gem_close(xe->fd, *bo1_1);
+
+ if (*bo1_2 > 0)
+ gem_close(xe->fd, *bo1_2);
+
+ *bo1_1 = bo_create_at(xe, cg1_1, bo_size);
+ igt_assert(*bo1_1 > 0);
+
+ *bo1_2 = bo_create_at(xe, cg1_2, bo_size);
+ igt_assert(*bo1_2 > 0);
+}
+
+/**
+ * SUBTEST: test-simple-min
+ * Description: Test that cgroup min limits are respected between
+ * various nestings of cgroups, and correct cgroups are evicted.
+ */
+static void test_simple_min(struct xe_device *xe)
+{
+ uint64_t bo_size = xe->default_alignment;
+ int bo1_1 = -1, bo1_2 = -1, bo2;
+
+ assert_all_cgs_empty();
+ reset_cgs();
+
+ /* set static max of 2 bo's for testing, allow protection as well on cg1 */
+ set_cgrp_max(igtcg, 2 * bo_size);
+ set_cgrp_min(igtcg, 2 * bo_size);
+ set_cgrp_min(cg1, 2 * bo_size);
+ set_cgrp_min(cg1_1, bo_size);
+ set_cgrp_min(cg1_2, bo_size);
+
+ create_cg1_min(xe, &bo1_1, &bo1_2);
+
+ igt_assert_eq_u64(get_cgrp_current(cg1), 2 * bo_size);
+
+ bo2 = bo_create_at(xe, cg2, bo_size);
+ igt_assert_eq_u64(get_cgrp_current(cg1), 2 * bo_size);
+ igt_assert_eq(bo2, -ENOMEM);
+
+ /* Unprotect one */
+ set_cgrp_min(cg1, bo_size);
+ set_cgrp_min(cg1_2, 0);
+
+ bo2 = bo_create_at(xe, cg2, bo_size);
+ /* Verify cg1_2 is evicted, cg1_1 not */
+ igt_assert_eq_u64(get_cgrp_current(cg1_2), 0);
+ igt_assert_eq_u64(get_cgrp_current(cg1), bo_size);
+ igt_assert_eq_u64(get_cgrp_current(igtcg), 2 * bo_size);
+ igt_assert(bo2 > 0);
+
+ gem_close(xe->fd, bo2);
+ gem_close(xe->fd, bo1_1);
+ gem_close(xe->fd, bo1_2);
+ assert_all_cgs_empty();
+}
+
+igt_main
+{
+ struct xe_device *xe;
+ int fd;
+
+ igt_fixture {
+ char *data;
+ char busid[32];
+
+ fd = drm_open_driver(DRIVER_XE);
+ xe = xe_device_get(fd);
+
+ /* XXX: Easier way to determine busid? */
+ igt_device_get_pci_slot_name(fd, busid);
+
+ /* For now, require VRAM as shared memory lacks support */
+ igt_require(xe->has_vram);
+ sprintf(cgid, "drm/%s/vram0", busid);
+
+ create_cgroups();
+
+ data = igt_sysfs_get(cgfd, "dmem.capacity");
+ igt_debug("Contents: %s\n", data);
+ /* Ensure our driver is found" */
+ igt_require_f(strstr(data, busid), "Is driver xe/%s supported by dmem controller?\n", busid);
+ free(data);
+ }
+
+ igt_subtest("functional")
+ test_functional();
+
+ igt_subtest("test-simple-max")
+ test_simple_max(xe);
+
+ igt_subtest("test-simple-min")
+ test_simple_min(xe);
+}
diff --git a/tests/meson.build b/tests/meson.build
index 2724c7a9a..84b3e955c 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -273,6 +273,7 @@ intel_kms_progs = [
intel_xe_progs = [
'xe_wedged',
'xe_ccs',
+ 'xe_cgroup',
'xe_create',
'xe_compute',
'xe_compute_preempt',
--
2.43.0
More information about the igt-dev
mailing list