[PATCH 10/11] selftests: cgroup: add memcg hugetlb_cma tests
From: Eric Chanudet
Date: Fri Aug 21 2026 - 15:04:06 EST
test_hugetlb_cma_charge gets a hugepage by incrementing nr_hugepages,
confirms memory.cma.hugetlb0.current increased as expected, then
releases it and checks the counter returns to its initial value.
test_hugetlb_cma_limit sets memory.cma.hugetlb0.max to 0, tries to
allocate a hugepage and verifies it fails.
Signed-off-by: Eric Chanudet <echanude@xxxxxxxxxx>
---
tools/testing/selftests/cgroup/test_cma_memcg.c | 209 ++++++++++++++++++++++++
1 file changed, 209 insertions(+)
diff --git a/tools/testing/selftests/cgroup/test_cma_memcg.c b/tools/testing/selftests/cgroup/test_cma_memcg.c
index b5b5ea140518..9bd41d9a6177 100644
--- a/tools/testing/selftests/cgroup/test_cma_memcg.c
+++ b/tools/testing/selftests/cgroup/test_cma_memcg.c
@@ -4,7 +4,9 @@
#include <linux/limits.h>
#include <sys/mman.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <dirent.h>
#include <fcntl.h>
#include <linux/dma-heap.h>
#include <sys/ioctl.h>
@@ -15,6 +17,10 @@
#define CMA_RESERVED_CURRENT "memory.cma.reserved.current"
#define CMA_RESERVED_MAX "memory.cma.reserved.max"
+#define HUGETLB_CMA_CURRENT "memory.cma.hugetlb0.current"
+#define HUGETLB_CMA_MAX "memory.cma.hugetlb0.max"
+#define HUGEPAGES_DIR "/sys/kernel/mm/hugepages"
+
static int cma_heap_alloc(int heap_fd, size_t size)
{
struct dma_heap_allocation_data data = {
@@ -145,6 +151,207 @@ static int test_cma_limit(const char *root)
return rc;
}
+static long find_largest_hugepage_size_kb(void)
+{
+ DIR *dir;
+ struct dirent *entry;
+ long max_size = 0;
+
+ dir = opendir(HUGEPAGES_DIR);
+ if (!dir)
+ return -1;
+
+ while ((entry = readdir(dir)) != NULL) {
+ long size;
+
+ if (sscanf(entry->d_name, "hugepages-%ldkB", &size) == 1) {
+ if (size > max_size)
+ max_size = size;
+ }
+ }
+ closedir(dir);
+
+ return max_size > 0 ? max_size : -1;
+}
+
+static long read_sysfs_long(const char *path)
+{
+ FILE *f;
+ long val = -1;
+
+ f = fopen(path, "r");
+ if (!f)
+ return -1;
+ if (fscanf(f, "%ld", &val) != 1)
+ val = -1;
+ fclose(f);
+ return val;
+}
+
+static int write_sysfs_long(const char *path, long val)
+{
+ FILE *f;
+ int rc;
+
+ f = fopen(path, "w");
+ if (!f)
+ return -1;
+ rc = fprintf(f, "%ld", val);
+ fclose(f);
+ return rc < 0 ? -1 : 0;
+}
+
+static int test_hugetlb_cma_charge(const char *root)
+{
+ int rc = KSFT_SKIP, in_cg = 0;
+ long hpage_size_kb, hpage_size_bytes;
+ long orig_nr = -1, new_nr, before, after;
+ char nr_path[PATH_MAX];
+ char *cg;
+
+ hpage_size_kb = find_largest_hugepage_size_kb();
+ if (hpage_size_kb < 0) {
+ ksft_print_msg("no hugepage size found\n");
+ return KSFT_SKIP;
+ }
+ hpage_size_bytes = hpage_size_kb * 1024;
+
+ snprintf(nr_path, sizeof(nr_path),
+ HUGEPAGES_DIR "/hugepages-%ldkB/nr_hugepages", hpage_size_kb);
+
+ cg = cg_name(root, "memcg_hugetlb_cma_charge");
+ if (!cg)
+ goto cleanup;
+
+ if (cg_create(cg))
+ goto cleanup;
+
+ if (cg_enter_current(cg))
+ goto cleanup;
+ in_cg = 1;
+
+ if (cg_read_long(cg, HUGETLB_CMA_CURRENT) < 0) {
+ ksft_print_msg("hugetlb CMA counters not available\n");
+ goto cleanup;
+ }
+
+ orig_nr = read_sysfs_long(nr_path);
+ if (orig_nr < 0) {
+ ksft_print_msg("cannot read %s\n", nr_path);
+ goto cleanup;
+ }
+
+ before = cg_read_long(cg, HUGETLB_CMA_CURRENT);
+
+ if (write_sysfs_long(nr_path, orig_nr + 1)) {
+ ksft_print_msg("cannot write %s\n", nr_path);
+ goto cleanup;
+ }
+
+ new_nr = read_sysfs_long(nr_path);
+ if (new_nr <= orig_nr) {
+ ksft_print_msg("failed to allocate a hugepage\n");
+ goto cleanup;
+ }
+
+ rc = KSFT_FAIL;
+ after = cg_read_long(cg, HUGETLB_CMA_CURRENT);
+
+ if (after - before < hpage_size_bytes) {
+ ksft_print_msg(
+ "hugetlb CMA counter not charged: before=%ld after=%ld expected +%ld\n",
+ before, after, hpage_size_bytes);
+ goto cleanup;
+ }
+
+ write_sysfs_long(nr_path, orig_nr);
+
+ after = cg_read_long(cg, HUGETLB_CMA_CURRENT);
+ if (!values_close(before, after, 3)) {
+ ksft_print_msg(
+ "hugetlb CMA counter not uncharged: before=%ld after=%ld\n",
+ before, after);
+ goto cleanup;
+ }
+
+ rc = KSFT_PASS;
+
+cleanup:
+ if (orig_nr >= 0)
+ write_sysfs_long(nr_path, orig_nr);
+
+ if (in_cg)
+ cg_enter_current(root);
+ cg_destroy(cg);
+ free(cg);
+
+ return rc;
+}
+
+static int test_hugetlb_cma_limit(const char *root)
+{
+ int rc = KSFT_SKIP, in_cg = 0;
+ long hpage_size_kb;
+ long orig_nr, new_nr;
+ char nr_path[PATH_MAX];
+ char *cg;
+
+ hpage_size_kb = find_largest_hugepage_size_kb();
+ if (hpage_size_kb < 0) {
+ ksft_print_msg("no hugepage size found\n");
+ return KSFT_SKIP;
+ }
+
+ snprintf(nr_path, sizeof(nr_path),
+ HUGEPAGES_DIR "/hugepages-%ldkB/nr_hugepages", hpage_size_kb);
+
+ cg = cg_name(root, "memcg_hugetlb_cma_limit");
+ if (!cg)
+ goto cleanup;
+
+ if (cg_create(cg))
+ goto cleanup;
+
+ if (cg_enter_current(cg))
+ goto cleanup;
+ in_cg = 1;
+
+ if (cg_read_long(cg, HUGETLB_CMA_MAX) < 0) {
+ ksft_print_msg("hugetlb CMA counters not available\n");
+ goto cleanup;
+ }
+
+ if (cg_write(cg, HUGETLB_CMA_MAX, "0")) {
+ ksft_print_msg("cannot set hugetlb CMA limit\n");
+ goto cleanup;
+ }
+
+ orig_nr = read_sysfs_long(nr_path);
+ if (orig_nr < 0) {
+ ksft_print_msg("cannot read %s\n", nr_path);
+ goto cleanup;
+ }
+
+ write_sysfs_long(nr_path, orig_nr + 1);
+ new_nr = read_sysfs_long(nr_path);
+
+ rc = KSFT_FAIL;
+ if (new_nr > orig_nr) {
+ ksft_print_msg("hugetlb CMA limit not enforced\n");
+ write_sysfs_long(nr_path, orig_nr);
+ } else {
+ rc = KSFT_PASS;
+ }
+
+cleanup:
+ if (in_cg)
+ cg_enter_current(root);
+ cg_destroy(cg);
+ free(cg);
+
+ return rc;
+}
+
#define T(x) { x, #x }
struct cma_memcg_tests {
int (*fn)(const char *root);
@@ -152,6 +359,8 @@ struct cma_memcg_tests {
} tests[] = {
T(test_cma_charge),
T(test_cma_limit),
+ T(test_hugetlb_cma_charge),
+ T(test_hugetlb_cma_limit),
};
#undef T
--
2.53.0