[PATCH v7 4/4] fw_load: add fw_load02 for custom firmware path

Andrea Cervesato <[email protected]>
Newsgroups gmane.linux.ltp
Message-ID <[email protected]>
From: Andrea Cervesato <andrea.cervesato-IBi9RG/[email protected]>

Add fw_load02 which points the kernel firmware loader at the writable
LTP temporary directory via /sys/module/firmware_class/parameters/path.
Unlike fw_load01, it does not rely on a writable /lib/firmware and so
works on read-only and immutable root filesystems while still exercising
request_firmware() for both successful loads and the not-found case.

Move test inside the header so both fw_load01 and fw_load02 will share
the same test function.

Signed-off-by: Andrea Cervesato <andrea.cervesato-IBi9RG/[email protected]>
---
 runtest/kernel_misc                           |  1 +
 testcases/kernel/firmware/fw_load/.gitignore  |  1 +
 testcases/kernel/firmware/fw_load/Makefile    |  2 +-
 testcases/kernel/firmware/fw_load/fw_load.h   | 69 ++++++++++++++++++++
 testcases/kernel/firmware/fw_load/fw_load01.c | 72 +++------------------
 testcases/kernel/firmware/fw_load/fw_load02.c | 93 +++++++++++++++++++++++++++
 6 files changed, 174 insertions(+), 64 deletions(-)

diff --git a/runtest/kernel_misc b/runtest/kernel_misc
index 19caee1d81da9cf0503088138ecfda13436b96cf..cc3562cb78cfa9b657692fcbd8fb732f2879092e 100644
--- a/runtest/kernel_misc
+++ b/runtest/kernel_misc
@@ -1,6 +1,7 @@
 cn_pec_sh cn_pec.sh
 kmsg01 kmsg01
 fw_load01 fw_load01
+fw_load02 fw_load02
 rtc01 rtc01
 rtc02 rtc02
 block_dev block_dev
diff --git a/testcases/kernel/firmware/fw_load/.gitignore b/testcases/kernel/firmware/fw_load/.gitignore
index 57eca444c161e6ba642561664cc7de3a8f134841..0bacefd371fc3ac6cf043102398b5a0c74b0b66d 100644
--- a/testcases/kernel/firmware/fw_load/.gitignore
+++ b/testcases/kernel/firmware/fw_load/.gitignore
@@ -6,3 +6,4 @@
 /.tmp_versions/
 modules.livepatch
 fw_load01
+fw_load02
diff --git a/testcases/kernel/firmware/fw_load/Makefile b/testcases/kernel/firmware/fw_load/Makefile
index 91e8d8d153b43c459a899aff2124db612ec9e960..bf6f9abd7517654066a5a92ac481e8eb1f959a41 100644
--- a/testcases/kernel/firmware/fw_load/Makefile
+++ b/testcases/kernel/firmware/fw_load/Makefile
@@ -17,7 +17,7 @@ include $(top_srcdir)/include/mk/testcases.mk
 REQ_VERSION_MAJOR   := 3
 REQ_VERSION_PATCH   := 7
 
-MAKE_TARGETS	:= fw_load01 ltp_fw_load.ko
+MAKE_TARGETS	:= fw_load01 fw_load02 ltp_fw_load.ko
 
 include $(top_srcdir)/include/mk/module.mk
 include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/firmware/fw_load/fw_load.h b/testcases/kernel/firmware/fw_load/fw_load.h
index 39f207cc30810e2ed9ea76176cedd98aa902d887..9692c0c90e046d1c13d3082d187076b2b43f84f5 100644
--- a/testcases/kernel/firmware/fw_load/fw_load.h
+++ b/testcases/kernel/firmware/fw_load/fw_load.h
@@ -6,6 +6,8 @@
 #ifndef FW_LOAD_H
 #define FW_LOAD_H
 
+#include "tst_test.h"
+
 #define MNAME_KO "ltp_fw_load.ko"
 #define FW_NAME	"load_tst.fw"
 #define FW_SIZE	0x1000
@@ -15,4 +17,71 @@
 #define DEV_RESULT "/sys/devices/ltp_fw_load/result"
 #define LIB_PATH "/lib/firmware"
 
+struct fw_data {
+	char dir[PATH_MAX];
+	char file[PATH_MAX];
+	int fake;
+	int created_dir;
+};
+
+static inline void create_firmware(struct fw_data *firmware,
+				   int *fw_count, const char *dir)
+{
+	struct fw_data *fw = &firmware[*fw_count];
+	char buf[FW_SIZE];
+	int fd;
+
+	snprintf(fw->dir, sizeof(fw->dir), "%s", dir);
+
+	if (access(dir, X_OK) == -1) {
+		SAFE_MKDIR(dir, 0755);
+		fw->created_dir = 1;
+	}
+
+	snprintf(fw->file, sizeof(fw->file), "%s/n%d_%s", dir, *fw_count, FW_NAME);
+	memset(buf, *fw_count, FW_SIZE);
+
+	fd = SAFE_OPEN(fw->file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	SAFE_WRITE(SAFE_WRITE_ALL, fd, buf, FW_SIZE);
+	SAFE_CLOSE(fd);
+
+	(*fw_count)++;
+}
+
+static inline void create_fake_firmware(struct fw_data *firmware, int *fw_count)
+{
+	snprintf(firmware[*fw_count].file, sizeof(firmware[*fw_count].file),
+		 "/n%d_%s", *fw_count, FW_NAME);
+
+	firmware[*fw_count].fake = 1;
+	(*fw_count)++;
+}
+
+static inline void do_test(struct fw_data *firmware, int fw_count)
+{
+	struct fw_data *fw;
+	int result = 0;
+	int pass, offset;
+
+	SAFE_FILE_PRINTF(DEV_FWNUM, "%d", fw_count);
+	SAFE_FILE_SCANF(DEV_RESULT, "%d", &result);
+
+	for (int i = 0; i < fw_count; i++) {
+		fw = &firmware[i];
+
+		pass = result & (1 << i);
+		offset = fw->dir[0] ? strlen(fw->dir) : 0;
+
+		if (fw->fake) {
+			tst_res(pass ? TFAIL : TPASS,
+				"Firmware '%s' correctly not loaded",
+				fw->file + offset);
+		} else {
+			tst_res(pass ? TPASS : TFAIL,
+				"Firmware '%s' loaded",
+				fw->file + offset);
+		}
+	}
+}
+
 #endif
diff --git a/testcases/kernel/firmware/fw_load/fw_load01.c b/testcases/kernel/firmware/fw_load/fw_load01.c
index 402e2c71f508fdbb9c3f73fcd42bf87da280137b..4b30a3b40fb1e3db8f300b2c56d5f29e8e37a7ff 100644
--- a/testcases/kernel/firmware/fw_load/fw_load01.c
+++ b/testcases/kernel/firmware/fw_load/fw_load01.c
@@ -40,61 +40,11 @@
 
 static int module_loaded;
 static int fw_count;
-
-static struct fw_data {
-	char dir[PATH_MAX];
-	char file[PATH_MAX];
-	int fake;
-	int created_dir;
-} firmware[FW_NUM];
-
-static void create_firmware(const char *dir)
-{
-	struct fw_data *fw = &firmware[fw_count];
-	char buf[FW_SIZE];
-	int fd = -1;
-
-	snprintf(fw->dir, sizeof(fw->dir), "%s", dir);
-	if (access(dir, X_OK) == -1) {
-		SAFE_MKDIR(dir, 0755);
-		fw->created_dir = 1;
-	}
-
-	snprintf(fw->file, sizeof(fw->file), "%s/n%d_%s", dir, fw_count, FW_NAME);
-	memset(buf, fw_count, FW_SIZE);
-
-	fd = SAFE_OPEN(fw->file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
-	SAFE_WRITE(SAFE_WRITE_ALL, fd, buf, FW_SIZE);
-	SAFE_CLOSE(fd);
-
-	fw_count++;
-}
+static struct fw_data firmware[FW_NUM];
 
 static void run(void)
 {
-	struct fw_data *fw;
-	int result = 0;
-	int pass, offset;
-
-	SAFE_FILE_PRINTF(DEV_FWNUM, "%d", fw_count);
-	SAFE_FILE_SCANF(DEV_RESULT, "%d", &result);
-
-	for (int i = 0; i < fw_count; i++) {
-		fw = &firmware[i];
-
-		pass = result & (1 << i);
-		offset = fw->dir[0] ? strlen(fw->dir) : 0;
-
-		if (fw->fake) {
-			tst_res(pass ? TFAIL : TPASS,
-				"Firmware '%s' correctly not loaded",
-				fw->file + offset);
-		} else {
-			tst_res(pass ? TPASS : TFAIL,
-				"Firmware '%s' loaded",
-				fw->file + offset);
-		}
-	}
+	do_test(firmware, fw_count);
 }
 
 static void setup(void)
@@ -104,7 +54,8 @@ static void setup(void)
 	struct utsname name;
 
 	if (access(LIB_PATH, W_OK) == -1)
-		tst_brk(TCONF, "Skipping test due to read-only %s", LIB_PATH);
+		tst_brk(TCONF, "Skipping test due to read-only %s",
+			LIB_PATH);
 
 	tst_requires_module_signature_disabled();
 
@@ -114,25 +65,20 @@ static void setup(void)
 	tst_module_load(MNAME_KO, mod_params);
 	module_loaded = 1;
 
-	create_firmware(LIB_PATH);
+	create_firmware(firmware, &fw_count, LIB_PATH);
 
 	uname(&name);
 
 	snprintf(fw_dir, sizeof(fw_dir), "%s/%s", LIB_PATH, name.release);
-	create_firmware(fw_dir);
+	create_firmware(firmware, &fw_count, fw_dir);
 
 	snprintf(fw_dir, sizeof(fw_dir), "%s/updates", LIB_PATH);
-	create_firmware(fw_dir);
+	create_firmware(firmware, &fw_count, fw_dir);
 
 	snprintf(fw_dir, sizeof(fw_dir), "%s/updates/%s", LIB_PATH, name.release);
-	create_firmware(fw_dir);
-
-	/* add a fake file */
-	snprintf(firmware[fw_count].file, sizeof(firmware[fw_count].file),
-		 "/n%d_%s", fw_count, FW_NAME);
+	create_firmware(firmware, &fw_count, fw_dir);
 
-	firmware[fw_count].fake = 1;
-	fw_count++;
+	create_fake_firmware(firmware, &fw_count);
 }
 
 static void cleanup(void)
diff --git a/testcases/kernel/firmware/fw_load/fw_load02.c b/testcases/kernel/firmware/fw_load/fw_load02.c
new file mode 100644
index 0000000000000000000000000000000000000000..8d2063772c0f0aadeb5e46fcba6bf2a6bba4d327
--- /dev/null
+++ b/testcases/kernel/firmware/fw_load/fw_load02.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved.
+ *	Alexey Kodanev <[email protected]>
+ * Copyright (c) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato-IBi9RG/[email protected]>
+ */
+
+/*\
+ * Verifies that the kernel firmware loader (``CONFIG_FW_LOADER``)
+ * can find and load firmware files from a custom firmware search
+ * path.
+ *
+ * A helper kernel module (``ltp_fw_load.ko``) registers a virtual
+ * device that calls :kernel_doc:`request_firmware` for a set of
+ * numbered firmware blobs. Each blob is verified in-kernel against
+ * its expected size and byte pattern.
+ *
+ * The kernel firmware loader is pointed at the writable LTP
+ * temporary directory through
+ * ``/sys/module/firmware_class/parameters/path``.
+ * This avoids writing into ``/lib/firmware`` and therefore works
+ * on read-only or immutable root filesystems. The original value
+ * is saved and restored automatically.
+ *
+ * [Algorithm]
+ *
+ * - Set the firmware search path to the LTP temporary directory
+ * - Load the helper module with ``fw_size`` matching the blob size
+ * - Create ``FW_NUM - 1`` firmware files there, each named
+ *   ``n<i>_load_tst.fw`` and filled with a known byte pattern
+ * - Add one fake firmware entry that has no file on disk
+ * - Write the firmware count to ``/sys/devices/ltp_fw_load/fwnum``
+ *   to trigger :kernel_doc:`request_firmware` calls in-kernel
+ * - Read the result bitmask from ``/sys/devices/ltp_fw_load/result``
+ * - Verify that every real firmware file was loaded successfully
+ *   and that the fake entry was correctly rejected
+ */
+
+#include "tst_test.h"
+#include "tst_module.h"
+#include "fw_load.h"
+
+static int module_loaded;
+static int fw_count;
+static struct fw_data firmware[FW_NUM];
+
+static void run(void)
+{
+	do_test(firmware, fw_count);
+}
+
+static void setup(void)
+{
+	char fw_size_param[32];
+	char *tmpdir = tst_tmpdir_path();
+
+	tst_requires_module_signature_disabled();
+
+	SAFE_FILE_PRINTF(FW_PATH, "%s", tmpdir);
+
+	snprintf(fw_size_param, sizeof(fw_size_param), "fw_size=%d", FW_SIZE);
+	char *const mod_params[] = {fw_size_param, NULL};
+
+	tst_module_load(MNAME_KO, mod_params);
+	module_loaded = 1;
+
+	for (int i = 0; i < FW_NUM - 1; i++)
+		create_firmware(firmware, &fw_count, tmpdir);
+
+	create_fake_firmware(firmware, &fw_count);
+}
+
+static void cleanup(void)
+{
+	if (module_loaded)
+		tst_module_unload(MNAME_KO);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_tmpdir = 1,
+	.needs_kconfigs = (const char *[]) {
+		"CONFIG_FW_LOADER=y|CONFIG_FW_LOADER=m",
+		NULL,
+	},
+	.save_restore = (const struct tst_path_val[]) {
+		{FW_PATH, NULL, TST_SR_TCONF},
+		{},
+	},
+};

-- 
2.51.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.