[LTP] [PATCH v7 2/4] tst_kconfig: load and unload kernel modules for CONFIG_*=m
Li Wang (Hieco)
li.wang@linux.dev
Tue Nov 3 08:57:20 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 | 89 +++++++++++++++++++++++++++++++++++++++++++
lib/tst_test.c | 13 +++++--
3 files changed, 120 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..1b5fa104a 100644
--- a/lib/tst_kconfig.c
+++ b/lib/tst_kconfig.c
@@ -16,8 +16,16 @@
#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 module_count;
+static unsigned int loaded_module_count;
+
static int kconfig_skip_check(void)
{
char *skipped = getenv("KCONFIG_SKIP_CHECK");
@@ -189,6 +197,38 @@ 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 < module_count; i++) {
+ if (!strcmp(loaded_modules[i], mod))
+ return;
+ }
+
+ if (module_count >= MAX_LOADED_MODULES)
+ tst_brk(TBROK, "Too many modules, increase MAX_LOADED_MODULES");
+
+ loaded_modules[module_count++] = mod;
+}
+
static inline int kconfig_parse_line(const char *line,
struct tst_kconfig_var *vars,
unsigned int vars_len)
@@ -268,6 +308,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 +341,54 @@ exit:
close_kconfig(fp);
}
+void tst_kconfig_module_load(void)
+{
+ unsigned int i;
+
+ if (geteuid() != 0)
+ return;
+
+ for (i = 0; i < module_count; i++) {
+ tst_res(TDEBUG, "Loading module '%s'", loaded_modules[i]);
+ tst_modprobe(loaded_modules[i], NULL);
+
+ loaded_module_count++;
+ }
+}
+
+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 | TERRNO, "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