Add test_cma_memcg with two simple tests to exercise CMA memcg
accounting using the dma-buf CMA heap
(/dev/dma_heap/default_cma_region).

test_cma_charge allocates a 4MB buffer in the default CMA area, verifies
memory.cma.reserved.current increased by that amount, then frees the
buffer and verifies that the counter returns to its original value.

test_cma_limit sets memory.cma.reserved.max to 2MB, attempts a 4MB
allocation in the default CMA area and verifies it fails as expected.

Skip the test if memory_cma_accounting is not enabled on the cgroupfs.

Signed-off-by: Eric Chanudet <[email protected]>
---
 tools/testing/selftests/cgroup/.gitignore       |   1 +
 tools/testing/selftests/cgroup/Makefile         |   2 +
 tools/testing/selftests/cgroup/test_cma_memcg.c | 193 ++++++++++++++++++++++++
 3 files changed, 196 insertions(+)

diff --git a/tools/testing/selftests/cgroup/.gitignore 
b/tools/testing/selftests/cgroup/.gitignore
index 952e4448bf07..b9a4298d4944 100644
--- a/tools/testing/selftests/cgroup/.gitignore
+++ b/tools/testing/selftests/cgroup/.gitignore
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 test_core
+test_cma_memcg
 test_cpu
 test_cpuset
 test_freezer
diff --git a/tools/testing/selftests/cgroup/Makefile 
b/tools/testing/selftests/cgroup/Makefile
index e01584c2189a..601d930160a4 100644
--- a/tools/testing/selftests/cgroup/Makefile
+++ b/tools/testing/selftests/cgroup/Makefile
@@ -8,6 +8,7 @@ TEST_PROGS     := test_stress.sh test_cpuset_prs.sh 
test_cpuset_v1_hp.sh
 TEST_GEN_FILES := wait_inotify
 # Keep the lists lexicographically sorted
 TEST_GEN_PROGS  = test_core
+TEST_GEN_PROGS += test_cma_memcg
 TEST_GEN_PROGS += test_cpu
 TEST_GEN_PROGS += test_cpuset
 TEST_GEN_PROGS += test_freezer
@@ -24,6 +25,7 @@ include ../lib.mk
 include lib/libcgroup.mk
 
 $(OUTPUT)/test_core: $(LIBCGROUP_O)
+$(OUTPUT)/test_cma_memcg: $(LIBCGROUP_O)
 $(OUTPUT)/test_cpu: $(LIBCGROUP_O)
 $(OUTPUT)/test_cpuset: $(LIBCGROUP_O)
 $(OUTPUT)/test_freezer: $(LIBCGROUP_O)
diff --git a/tools/testing/selftests/cgroup/test_cma_memcg.c 
b/tools/testing/selftests/cgroup/test_cma_memcg.c
new file mode 100644
index 000000000000..b5b5ea140518
--- /dev/null
+++ b/tools/testing/selftests/cgroup/test_cma_memcg.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <linux/limits.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <linux/dma-heap.h>
+#include <sys/ioctl.h>
+#include "kselftest.h"
+#include "cgroup_util.h"
+
+#define CMA_HEAP_DEFAULT_PATH  "/dev/dma_heap/default_cma_region"
+#define CMA_RESERVED_CURRENT   "memory.cma.reserved.current"
+#define CMA_RESERVED_MAX       "memory.cma.reserved.max"
+
+static int cma_heap_alloc(int heap_fd, size_t size)
+{
+       struct dma_heap_allocation_data data = {
+               .len = size,
+               .fd_flags = O_RDWR | O_CLOEXEC,
+       };
+
+       if (ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0)
+               return -1;
+
+       return data.fd;
+}
+
+static int test_cma_charge(const char *root)
+{
+       int heap_fd = -1, buf_fd = -1;
+       int rc = KSFT_FAIL, in_cg = 0;
+       size_t size = 4UL << 20;
+       long before, after;
+       char *cg;
+
+       cg = cg_name(root, "memcg_cma_charge");
+       if (!cg)
+               goto cleanup;
+
+       if (cg_create(cg))
+               goto cleanup;
+
+       if (cg_enter_current(cg))
+               goto cleanup;
+       in_cg = 1;
+
+       heap_fd = open(CMA_HEAP_DEFAULT_PATH, O_RDONLY | O_CLOEXEC);
+       if (heap_fd < 0) {
+               ksft_print_msg("cannot open CMA heap (%m)\n");
+               goto cleanup;
+       }
+
+       before = cg_read_long(cg, CMA_RESERVED_CURRENT);
+
+       buf_fd = cma_heap_alloc(heap_fd, size);
+       if (buf_fd < 0) {
+               ksft_print_msg("cannot allocate in CMA heap (%m)\n");
+               goto cleanup;
+       }
+
+       after = cg_read_long(cg, CMA_RESERVED_CURRENT);
+
+       if (after - before < (long)size) {
+               ksft_print_msg(
+                       "invalid CMA counter after charge: before=%ld 
after=%ld\n",
+                       before, after);
+               goto cleanup;
+       }
+
+       close(buf_fd);
+       buf_fd = -1;
+
+       after = cg_read_long(cg, CMA_RESERVED_CURRENT);
+       if (!values_close(before, after, 3)) {
+               ksft_print_msg(
+                       "invalid CMA counter after uncharge: before=%ld 
after=%ld\n",
+                       before, after);
+               goto cleanup;
+       }
+
+       rc = KSFT_PASS;
+
+cleanup:
+       if (buf_fd >= 0)
+               close(buf_fd);
+       if (heap_fd >= 0)
+               close(heap_fd);
+
+       if (in_cg)
+               cg_enter_current(root);
+       cg_destroy(cg);
+       free(cg);
+
+       return rc;
+}
+
+static int test_cma_limit(const char *root)
+{
+       int heap_fd = -1, buf_fd = -1;
+       int rc = KSFT_FAIL, in_cg = 0;
+       size_t size = 4UL << 20;
+       char *cg;
+
+       cg = cg_name(root, "memcg_cma_limit");
+       if (!cg)
+               goto cleanup;
+
+       if (cg_create(cg))
+               goto cleanup;
+
+       if (cg_enter_current(cg))
+               goto cleanup;
+       in_cg = 1;
+
+       if (cg_write(cg, CMA_RESERVED_MAX, "2M")) {
+               ksft_print_msg("cannot set CMA limit (%m)\n");
+               goto cleanup;
+       }
+
+       heap_fd = open(CMA_HEAP_DEFAULT_PATH, O_RDONLY | O_CLOEXEC);
+       if (heap_fd < 0) {
+               ksft_print_msg("cannot open CMA heap (%m)\n");
+               goto cleanup;
+       }
+
+       buf_fd = cma_heap_alloc(heap_fd, size);
+       if (buf_fd >= 0) {
+               ksft_print_msg("CMA limit not enforced.\n");
+               close(buf_fd);
+       } else
+               rc = KSFT_PASS;
+
+cleanup:
+       if (heap_fd >= 0)
+               close(heap_fd);
+
+       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);
+       const char *name;
+} tests[] = {
+       T(test_cma_charge),
+       T(test_cma_limit),
+};
+#undef T
+
+int main(int argc, char *argv[])
+{
+       char root[PATH_MAX];
+       int has_memory_cma_acc, i;
+
+       ksft_print_header();
+       ksft_set_plan(ARRAY_SIZE(tests));
+
+       has_memory_cma_acc = proc_mount_contains("memory_cma_accounting");
+       if (has_memory_cma_acc < 0)
+               ksft_exit_skip("cannot query cgroup mount option.\n");
+       else if (!has_memory_cma_acc)
+               ksft_exit_skip("CMA accounting is disabled.\n");
+
+       if (cg_find_unified_root(root, sizeof(root), NULL))
+               ksft_exit_skip("cgroupfs is not available.\n");
+
+       if (cg_read_strstr(root, "cgroup.controllers", "memory"))
+               ksft_exit_skip("memory controller is not available 
(CONFIG_CGROUP_MEMCG?)\n");
+
+       for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+               switch (tests[i].fn(root)) {
+               case KSFT_PASS:
+                       ksft_test_result_pass("%s\n", tests[i].name);
+                       break;
+               case KSFT_SKIP:
+                       ksft_test_result_skip("%s\n", tests[i].name);
+                       break;
+               default:
+                       ksft_test_result_fail("%s\n", tests[i].name);
+                       break;
+               }
+       }
+
+       ksft_finished();
+}

-- 
2.53.0


Reply via email to