[LTP] [PATCH 1/3] tst_kconfig: load and unload kernel modules for CONFIG_*=m

Li Wang li.wang@linux.dev
Wed Oct 7 12:57:28 CEST 2026


Tests that depend on a config set to =m must currently load the
module themselves, and may fail with misleading errors when it is
not loaded, e.g. bind() returning EADDRNOTAVAIL for AF_VSOCK when
vsock_loopback is missing.

Record the module for every config_module_maps[] entry seen as =m
during kconfig_parse_line(), load them in
tst_kconfig_module_load() via tst_modprobe(), and unload them in
reverse order from tst_kconfig_module_unload_all(), called by
do_test_cleanup(). Modules still in use are skipped without
aborting the test.

Loading and unloading are performed only as root; otherwise the
recorded list is left untouched.

Signed-off-by: Li Wang <li.wang@linux.dev>
---
 include/tst_kconfig.h | 28 +++++++++++++++++
 lib/tst_kconfig.c     | 70 +++++++++++++++++++++++++++++++++++++++++++
 lib/tst_test.c        |  4 +++
 3 files changed, 102 insertions(+)

diff --git a/include/tst_kconfig.h b/include/tst_kconfig.h
index c454f3467..540ae369c 100644
--- a/include/tst_kconfig.h
+++ b/include/tst_kconfig.h
@@ -105,4 +105,32 @@ void tst_kcmdline_parse(struct tst_kcmdline_var params[], size_t params_len);
  */
 int tst_has_slow_kconfig(void);
 
+/*
+ * tst_kconfig_module_load() - Load kernel modules for CONFIG_*=m entries.
+ *
+ * Loads every module recorded during the last kernel config check, in
+ * the order they were discovered. The list is populated by
+ * kconfig_module_record() from kconfig_parse_line() for each CONFIG_*
+ * entry that is set to =m and has a matching entry in
+ * config_module_maps[].
+ *
+ * Loading is skipped when not running as root, and does nothing when
+ * the recorded list is empty.
+ */
+void tst_kconfig_module_load(void);
+
+/*
+ * tst_kconfig_module_unload_all() - Unload modules loaded by
+ *                                   tst_kconfig_module_load().
+ *
+ * Unloads every module recorded by tst_kconfig_module_load(), in
+ * reverse load order so that inter-module dependencies are respected.
+ * Modules still in use are skipped silently, leaving the system state
+ * as close as possible to what it was before the test started.
+ *
+ * Called from do_test_cleanup() so that the modules are released when
+ * the test finishes, regardless of the test result.
+ */
+void tst_kconfig_module_unload_all(void);
+
 #endif	/* TST_KCONFIG_H__ */
diff --git a/lib/tst_kconfig.c b/lib/tst_kconfig.c
index 49957e28d..7e3ee1fe8 100644
--- a/lib/tst_kconfig.c
+++ b/lib/tst_kconfig.c
@@ -16,8 +16,14 @@
 #include "tst_bool_expr.h"
 #include "tst_safe_stdio.h"
 
+#include "tst_module.h"
 #include "tst_kconfig_checks.h"
 
+#define MAX_LOADED_MODULES 16
+
+static const char *loaded_modules[MAX_LOADED_MODULES];
+static unsigned int loaded_module_count;
+
 static int kconfig_skip_check(void)
 {
 	char *skipped = getenv("KCONFIG_SKIP_CHECK");
@@ -189,6 +195,35 @@ static void kconfig_module_check(struct tst_kconfig_var *var)
 	}
 }
 
+static const char *module_name_for_config(const char *cfg)
+{
+	size_t i;
+
+	for (i = 0; config_module_maps[i].config; i++) {
+		if (!strcmp(config_module_maps[i].config, cfg))
+			return config_module_maps[i].module_name;
+	}
+
+	return NULL;
+}
+
+static void kconfig_module_record(const char *cfg)
+{
+	unsigned int i;
+	const char *mod = module_name_for_config(cfg);
+
+	if (!mod)
+		return;
+
+	for (i = 0; i < loaded_module_count; i++) {
+		if (!strcmp(loaded_modules[i], mod))
+			return;
+	}
+
+	if (loaded_module_count < MAX_LOADED_MODULES)
+		loaded_modules[loaded_module_count++] = mod;
+}
+
 static inline int kconfig_parse_line(const char *line,
                                      struct tst_kconfig_var *vars,
                                      unsigned int vars_len)
@@ -268,6 +303,8 @@ out:
 				vars[i].choice = 'm';
 				kconfig_runtime_check(&vars[i]);
 				kconfig_module_check(&vars[i]);
+				if (vars[i].choice == 'm')
+					kconfig_module_record(vars[i].id);
 				return 1;
 			}
 		}
@@ -300,6 +337,39 @@ exit:
 	close_kconfig(fp);
 }
 
+void tst_kconfig_module_load(void)
+{
+	unsigned int i;
+
+	if (geteuid() != 0)
+		return;
+
+	for (i = 0; i < loaded_module_count; i++) {
+		tst_res(TDEBUG, "Loading module '%s'", loaded_modules[i]);
+		tst_modprobe(loaded_modules[i], NULL);
+	}
+}
+
+void tst_kconfig_module_unload_all(void)
+{
+	unsigned int i;
+
+	if (!loaded_module_count)
+		return;
+
+	if (geteuid() != 0)
+		return;
+
+	for (i = loaded_module_count; i > 0; i--) {
+		const char *const argv[] = { "rmmod", loaded_modules[i - 1], NULL };
+
+		tst_res(TDEBUG, "Unloading module '%s'", loaded_modules[i - 1]);
+		tst_cmd(argv, "/dev/null", "/dev/null", TST_CMD_PASS_RETVAL);
+	}
+
+	loaded_module_count = 0;
+}
+
 static size_t array_len(const char *const kconfigs[])
 {
 	size_t i = 0;
diff --git a/lib/tst_test.c b/lib/tst_test.c
index 10ff73397..04a85a781 100644
--- a/lib/tst_test.c
+++ b/lib/tst_test.c
@@ -425,6 +425,7 @@ static void do_test_cleanup(void)
 		tst_test->cleanup();
 
 	tst_free_all();
+	tst_kconfig_module_unload_all();
 
 	tst_brk_handler = tst_vbrk_;
 }
@@ -1474,6 +1475,9 @@ static void do_setup(int argc, char *argv[])
 	if (tst_test->needs_kconfigs && tst_kconfig_check(tst_test->needs_kconfigs))
 		tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!");
 
+	if (tst_test->needs_kconfigs)
+		tst_kconfig_module_load();
+
 	if (tst_test->needs_root && geteuid() != 0)
 		tst_brk(TCONF, "Test needs to be run as root");
 
-- 
2.55.0



More information about the ltp mailing list