[LTP] [PATCH v6 2/4] tst_kconfig: load and unload kernel modules for CONFIG_*=m

Li Wang (Hieco) li.wang@linux.dev
Mon Nov 2 03:43:26 CET 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
and not already loaded while parsing the kernel config. When
needs_kconfigs is set, load them via tst_modprobe() after a
successful tst_kconfig_check(), and unload them in reverse order
from do_cleanup(). Only modules that were actually loaded are
unloaded; modules still in use are skipped. Dependencies pulled in
by modprobe are not unloaded.

Modules are only loaded when running as root.

Signed-off-by: Li Wang (Hieco) <li.wang@linux.dev>
---
 include/tst_kconfig.h | 21 +++++++++++
 lib/tst_kconfig.c     | 84 +++++++++++++++++++++++++++++++++++++++++++
 lib/tst_test.c        | 13 +++++--
 3 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/include/tst_kconfig.h b/include/tst_kconfig.h
index c454f3467..d603919d9 100644
--- a/include/tst_kconfig.h
+++ b/include/tst_kconfig.h
@@ -105,4 +105,25 @@ 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 the modules for CONFIG_* options found as =m in the kernel config
+ * that are not already loaded, in the order they were found.
+ * Does nothing when not running as root.
+ */
+void tst_kconfig_module_load(void);
+
+/**
+ * tst_kconfig_module_unload() - Unload modules loaded by
+ *                               tst_kconfig_module_load().
+ *
+ * Unloads, in reverse order, only the modules that were actually loaded.
+ * Modules still in use are skipped silently. Dependencies pulled in by
+ * modprobe are not unloaded.
+ *
+ * Called from do_cleanup(), so it runs regardless of the test result.
+ */
+void tst_kconfig_module_unload(void);
+
 #endif	/* TST_KCONFIG_H__ */
diff --git a/lib/tst_kconfig.c b/lib/tst_kconfig.c
index f055c2e5f..a0985912c 100644
--- a/lib/tst_kconfig.c
+++ b/lib/tst_kconfig.c
@@ -16,8 +16,15 @@
 #include "tst_bool_expr.h"
 #include "tst_safe_stdio.h"
 
+#include "lapi/syscalls.h"
+#include "tst_module.h"
 #include "tst_kconfig_checks.h"
 
+#define MAX_LOADED_MODULES 32
+
+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 +196,36 @@ static void kconfig_module_check(struct tst_kconfig_var *var)
 	}
 }
 
+static void kconfig_module_record(struct tst_kconfig_var *var)
+{
+	unsigned int i;
+	const char *mod = NULL;
+
+	if (var->choice != 'm')
+		return;
+
+	for (i = 0; config_module_maps[i].config; i++) {
+		if (!strcmp(config_module_maps[i].config, var->id)) {
+			mod = config_module_maps[i].module_name;
+			break;
+		}
+	}
+
+	if (!mod)
+		return;
+
+	if (tst_module_is_loaded(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 +305,7 @@ out:
 				vars[i].choice = 'm';
 				kconfig_runtime_check(&vars[i]);
 				kconfig_module_check(&vars[i]);
+				kconfig_module_record(&vars[i]);
 				return 1;
 			}
 		}
@@ -300,6 +338,52 @@ 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(void)
+{
+	char name[64], *p;
+
+	if (!loaded_module_count)
+		return;
+
+	while (loaded_module_count > 0) {
+		snprintf(name, sizeof(name), "%s",
+			 loaded_modules[--loaded_module_count]);
+
+		for (p = name; (p = strchr(p, '-')); )
+			*p = '_';
+
+		tst_res(TDEBUG, "Unloading module '%s'", name);
+
+		if (!syscall(__NR_delete_module, name, O_NONBLOCK))
+			continue;
+
+		switch (errno) {
+		case ENOENT:		/* already unloaded */
+		case EWOULDBLOCK:	/* in use */
+		case EBUSY:		/* still loading or unloading */
+		case ENOSYS:		/* no CONFIG_MODULE_UNLOAD */
+			break;
+		default:
+			tst_res(TWARN, "Failed to unload module '%s'", name);
+		}
+	}
+
+	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..a9e679bee 100644
--- a/lib/tst_test.c
+++ b/lib/tst_test.c
@@ -1471,12 +1471,16 @@ static void do_setup(int argc, char *argv[])
 
 	check_supported_kver();
 
-	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_root && geteuid() != 0)
 		tst_brk(TCONF, "Test needs to be run as root");
 
+	if (tst_test->needs_kconfigs) {
+		if (tst_kconfig_check(tst_test->needs_kconfigs))
+			tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!");
+
+		tst_kconfig_module_load();
+	}
+
 	if (tst_test->min_kver)
 		check_kver(tst_test->min_kver, 1);
 
@@ -1690,6 +1694,9 @@ static void do_cleanup(void)
 	if (tst_test->restore_wallclock)
 		tst_wallclock_restore();
 
+	if (tst_test->needs_kconfigs)
+		tst_kconfig_module_unload();
+
 	cleanup_ipc();
 }
 
-- 
2.56.0



More information about the ltp mailing list