Currently, dmem charging is driver-driven through direct
calls to dmem_cgroup_try_charge(), so cgroup selftests
do not have a generic way to trigger charge and uncharge
paths from userspace.

This limits any selftest coverage to configuration/readout
checks unless a specific driver exposing charge hooks is
present in the test environment.

Add kernel/cgroup/dmem_selftest.c as a helper module
(CONFIG_DMEM_SELFTEST) that registers a synthetic dmem region
(dmem_selftest) and exposes debugfs control files:
/sys/kernel/debug/dmem_selftest/charge
/sys/kernel/debug/dmem_selftest/uncharge

Writing a size to charge triggers dmem_cgroup_try_charge() for
the calling task's cgroup (the module calls kstrtou64()).
Writing to uncharge releases the outstanding charge via
dmem_cgroup_uncharge(). Only a single outstanding charge
is supported.

This provides a deterministic, driver-independent mechanism
for exercising dmem accounting paths in selftests.

Signed-off-by: Albert Esteve <[email protected]>
---
 init/Kconfig                  |  12 +++
 kernel/cgroup/Makefile        |   1 +
 kernel/cgroup/dmem_selftest.c | 192 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 205 insertions(+)

diff --git a/init/Kconfig b/init/Kconfig
index 444ce811ea674..060ba8ca49333 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1238,6 +1238,18 @@ config CGROUP_DMEM
          As an example, it allows you to restrict VRAM usage for applications
          in the DRM subsystem.
 
+config DMEM_SELFTEST
+       tristate "dmem cgroup selftest helper module"
+       depends on CGROUP_DMEM && DEBUG_FS
+       default n
+       help
+         Builds a small loadable module that registers a dmem region named
+         "dmem_selftest" and exposes debugfs files under
+         /sys/kernel/debug/dmem_selftest/ so kselftests can trigger
+         dmem charge/uncharge operations from userspace.
+
+         Say N unless you run dmem selftests or develop the dmem controller.
+
 config CGROUP_FREEZER
        bool "Freezer controller"
        help
diff --git a/kernel/cgroup/Makefile b/kernel/cgroup/Makefile
index ede31601a363a..febc36e60f9f9 100644
--- a/kernel/cgroup/Makefile
+++ b/kernel/cgroup/Makefile
@@ -8,4 +8,5 @@ obj-$(CONFIG_CPUSETS) += cpuset.o
 obj-$(CONFIG_CPUSETS_V1) += cpuset-v1.o
 obj-$(CONFIG_CGROUP_MISC) += misc.o
 obj-$(CONFIG_CGROUP_DMEM) += dmem.o
+obj-$(CONFIG_DMEM_SELFTEST) += dmem_selftest.o
 obj-$(CONFIG_CGROUP_DEBUG) += debug.o
diff --git a/kernel/cgroup/dmem_selftest.c b/kernel/cgroup/dmem_selftest.c
new file mode 100644
index 0000000000000..09df70f718969
--- /dev/null
+++ b/kernel/cgroup/dmem_selftest.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kselftest helper for the dmem cgroup controller.
+ *
+ * Registers a dmem region and debugfs files so tests can trigger charges
+ * from the calling task's cgroup.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cgroup_dmem.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "../../tools/testing/selftests/kselftest_module.h"
+
+#define DM_SELFTEST_REGION_NAME        "dmem_selftest"
+#define DM_SELFTEST_REGION_SIZE        (256ULL * 1024 * 1024)
+
+KSTM_MODULE_GLOBALS();
+
+static struct dmem_cgroup_region *selftest_region;
+static struct dentry *dbg_dir;
+
+static struct dmem_cgroup_pool_state *charged_pool;
+static u64 charged_size;
+static DEFINE_MUTEX(charge_lock);
+
+static ssize_t dmem_selftest_charge_write(struct file *file, const char __user 
*user_buf,
+                                         size_t count, loff_t *ppos)
+{
+       struct dmem_cgroup_pool_state *pool = NULL, *limit = NULL;
+       u64 size;
+       char buf[32];
+       int ret;
+
+       if (!selftest_region)
+               return -ENODEV;
+
+       if (count == 0 || count >= sizeof(buf))
+               return -EINVAL;
+
+       if (copy_from_user(buf, user_buf, count))
+               return -EFAULT;
+       buf[count] = '\0';
+
+       ret = kstrtou64(strim(buf), 0, &size);
+       if (ret)
+               return ret;
+       if (!size)
+               return -EINVAL;
+
+       mutex_lock(&charge_lock);
+       if (charged_pool) {
+               mutex_unlock(&charge_lock);
+               return -EBUSY;
+       }
+
+       ret = dmem_cgroup_try_charge(selftest_region, size, &pool, &limit);
+       if (ret == -EAGAIN && limit)
+               dmem_cgroup_pool_state_put(limit);
+       if (ret) {
+               mutex_unlock(&charge_lock);
+               return ret;
+       }
+
+       charged_pool = pool;
+       charged_size = size;
+       mutex_unlock(&charge_lock);
+
+       return count;
+}
+
+static ssize_t dmem_selftest_uncharge_write(struct file *file, const char 
__user *user_buf,
+                                           size_t count, loff_t *ppos)
+{
+       if (!count)
+               return -EINVAL;
+
+       mutex_lock(&charge_lock);
+       if (!charged_pool) {
+               mutex_unlock(&charge_lock);
+               return -EINVAL;
+       }
+
+       dmem_cgroup_uncharge(charged_pool, charged_size);
+       charged_pool = NULL;
+       charged_size = 0;
+       mutex_unlock(&charge_lock);
+
+       return count;
+}
+
+static const struct file_operations dmem_selftest_charge_fops = {
+       .write = dmem_selftest_charge_write,
+       .llseek = noop_llseek,
+};
+
+static const struct file_operations dmem_selftest_uncharge_fops = {
+       .write = dmem_selftest_uncharge_write,
+       .llseek = noop_llseek,
+};
+
+static int __init dmem_selftest_register(void)
+{
+       selftest_region = dmem_cgroup_register_region(
+               DM_SELFTEST_REGION_SIZE, DM_SELFTEST_REGION_NAME);
+       if (IS_ERR(selftest_region))
+               return PTR_ERR(selftest_region);
+       if (!selftest_region)
+               return -EINVAL;
+
+       dbg_dir = debugfs_create_dir("dmem_selftest", NULL);
+       if (!dbg_dir) {
+               dmem_cgroup_unregister_region(selftest_region);
+               selftest_region = NULL;
+               return -ENOMEM;
+       }
+
+       debugfs_create_file("charge", 0200, dbg_dir, NULL, 
&dmem_selftest_charge_fops);
+       debugfs_create_file("uncharge", 0200, dbg_dir, NULL, 
&dmem_selftest_uncharge_fops);
+
+       pr_info("region '%s' registered; debugfs at 
dmem_selftest/{charge,uncharge}\n",
+               DM_SELFTEST_REGION_NAME);
+       return 0;
+}
+
+static void dmem_selftest_remove(void)
+{
+       debugfs_remove_recursive(dbg_dir);
+       dbg_dir = NULL;
+
+       if (selftest_region) {
+               dmem_cgroup_unregister_region(selftest_region);
+               selftest_region = NULL;
+       }
+}
+
+static void __init selftest(void)
+{
+       KSTM_CHECK_ZERO(!selftest_region);
+       KSTM_CHECK_ZERO(!dbg_dir);
+}
+
+static int __init dmem_selftest_init(void)
+{
+       int report_rc;
+       int err;
+
+       err = dmem_selftest_register();
+       if (err)
+               return err;
+
+       pr_info("loaded.\n");
+       add_taint(TAINT_TEST, LOCKDEP_STILL_OK);
+       selftest();
+       report_rc = kstm_report(total_tests, failed_tests, skipped_tests);
+       if (report_rc) {
+               dmem_selftest_remove();
+               return report_rc;
+       }
+
+       return 0;
+}
+
+static void __exit dmem_selftest_exit(void)
+{
+       pr_info("unloaded.\n");
+
+       mutex_lock(&charge_lock);
+       if (charged_pool) {
+               dmem_cgroup_uncharge(charged_pool, charged_size);
+               charged_pool = NULL;
+       }
+       mutex_unlock(&charge_lock);
+
+       dmem_selftest_remove();
+}
+
+module_init(dmem_selftest_init);
+module_exit(dmem_selftest_exit);
+
+MODULE_AUTHOR("Albert Esteve <[email protected]>");
+MODULE_DESCRIPTION("Kselftest helper for cgroup dmem controller");
+MODULE_LICENSE("GPL");

-- 
2.52.0


Reply via email to