[Concept,05/12] bls: Enable multi-entry bootflow scanning

Message ID 20260324221911.3678307-6-sjg@u-boot.org
State New
Headers
Series bootstd: Support multiple bootflows per partition |

Commit Message

Simon Glass March 24, 2026, 10:18 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Update the BLS bootmeth to support multiple entry files in the
loader/entries/ directory. Pass bflow->entry to bls_scan_entries_dir()
so it returns the Nth .conf file, and set BOOTMETHF_MULTI so the
iterator produces one bootflow per entry file.

Only fall back to the single loader/entry.conf path for entry 0, since
that path cannot produce multiple bootflows.

Update the test image to place two BLS entries in loader/entries/ and
verify that both appear as separate bootflows with correct OS names.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 boot/bootmeth_bls.c  | 106 +++++++++++++++++++++++++++++++++++++++----
 test/boot/bootflow.c |  20 ++++++--
 test/py/img/bls.py   |  24 +++++++---
 3 files changed, 130 insertions(+), 20 deletions(-)
  

Patch

diff --git a/boot/bootmeth_bls.c b/boot/bootmeth_bls.c
index 13353906fff..fc9c1bb70a2 100644
--- a/boot/bootmeth_bls.c
+++ b/boot/bootmeth_bls.c
@@ -9,7 +9,8 @@ 
  * https://uapi-group.org/specifications/specs/boot_loader_specification/
  *
  * Supported features:
- * - Single BLS entry file at loader/entry.conf
+ * - Scans loader/entries/ directory for .conf files
+ * - Falls back to single loader/entry.conf if no entries/ directory
  * - Fields: title, version, linux, options, initrd, devicetree
  * - Multiple options lines (concatenated with spaces)
  * - Multiple initrd lines (only first used, PXE limitation)
@@ -17,7 +18,7 @@ 
  * - Zero-copy parsing (fields point into bootflow buffer)
  *
  * Current limitations:
- * - Single entry file only, not multiple entries in loader/entries/
+ * - Only the first entry file in loader/entries/ is used
  * - Only first initrd used (PXE infrastructure supports one)
  * - No devicetree-overlay support
  * - No architecture/machine-id filtering
@@ -34,6 +35,8 @@ 
 #include <bootmeth.h>
 #include <bootstd.h>
 #include <dm.h>
+#include <env.h>
+#include <fs_common.h>
 #include <fs_legacy.h>
 #include <malloc.h>
 #include <mapmem.h>
@@ -41,7 +44,8 @@ 
 #include <pxe_utils.h>
 #include <linux/string.h>
 
-/* Single BLS entry file to check */
+/* BLS entry directory and fallback single file */
+#define BLS_ENTRIES_DIR		"loader/entries"
 #define BLS_ENTRY_FILE		"loader/entry.conf"
 
 /**
@@ -117,10 +121,12 @@  static int bls_to_pxe_label(struct bootflow *bflow,
 
 	INIT_LIST_HEAD(&label->list);
 	alist_init_struct(&label->files, struct pxe_file);
+	alist_init_struct(&label->initrds, char *);
 
+	label->name = strdup("");
 	label->menu = strdup(bflow->os_name ?: "");
 	label->append = strdup(bflow->cmdline ?: "");
-	if (!label->menu || !label->append) {
+	if (!label->name || !label->menu || !label->append) {
 		ret = -ENOMEM;
 		goto err;
 	}
@@ -137,10 +143,12 @@  static int bls_to_pxe_label(struct bootflow *bflow,
 
 		switch ((int)img->type) {
 		case IH_TYPE_KERNEL:
-			if (!label->kernel)
+			if (!label->kernel) {
 				label->kernel = fname;
-			else
+				label->kernel_label = strdup(fname);
+			} else {
 				free(fname);
+			}
 			break;
 		case IH_TYPE_RAMDISK:
 			if (!alist_add(&label->initrds, fname)) {
@@ -231,6 +239,59 @@  static int bls_entry_init(struct bls_entry *entry, struct bootflow *bflow,
 	return 0;
 }
 
+/**
+ * bls_scan_entries_dir() - Scan loader/entries/ for a .conf file
+ *
+ * Looks for the Nth .conf file in the BLS entries directory, where N is
+ * given by @entry. The filesystem must already be set up for the partition.
+ *
+ * @prefix: Prefix to prepend to the directory path (e.g. "/boot")
+ * @entry: Entry index (0 for first .conf file, 1 for second, etc.)
+ * @fname: Buffer to store the full path of the found entry
+ * @fname_size: Size of @fname buffer
+ * Return: 0 on success, -ENOENT if no more entries
+ */
+static int bls_scan_entries_dir(const char *prefix, int entry, char *fname,
+				int fname_size)
+{
+	struct fs_dir_stream *dirs;
+	struct fs_dirent *dent;
+	char dirpath[200];
+	int ret = -ENOENT;
+	int found = 0;
+
+	snprintf(dirpath, sizeof(dirpath), "%s%s", prefix ? prefix : "",
+		 BLS_ENTRIES_DIR);
+	log_debug("BLS: scanning dir %s entry %d\n", dirpath, entry);
+
+	dirs = fs_opendir(dirpath);
+	if (!dirs)
+		return log_msg_ret("opn", -ENOENT);
+
+	while ((dent = fs_readdir(dirs))) {
+		int len;
+
+		if (dent->type != FS_DT_REG)
+			continue;
+		len = strlen(dent->name);
+		if (len < 6 || strcmp(dent->name + len - 5, ".conf"))
+			continue;
+
+		if (found == entry) {
+			snprintf(fname, fname_size, "%s%s/%s",
+				 prefix ? prefix : "", BLS_ENTRIES_DIR,
+				 dent->name);
+			log_debug("BLS: found entry %s\n", fname);
+			ret = 0;
+			break;
+		}
+		found++;
+	}
+	fs_closedir(dirs);
+
+	return ret;
+}
+
 static int bls_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 {
 	struct bls_entry entry;
@@ -259,13 +320,33 @@  static int bls_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 	prefixes = bootstd_get_prefixes(bootstd);
 	desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL;
 
-	/* Try each prefix to find the BLS entry file */
+	/* Try each prefix: first scan entries/, then fall back to entry.conf */
 	i = 0;
+	ret = -ENOENT;
 	do {
+		char fname[200];
+
 		prefix = prefixes ? prefixes[i] : NULL;
 		log_debug("trying prefix %s\n", prefix);
 
-		ret = bootmeth_try_file(bflow, desc, prefix, BLS_ENTRY_FILE);
+		ret = bootmeth_setup_fs(bflow, desc);
+		if (ret)
+			return log_msg_ret("bfs", ret);
+
+		if (!bls_scan_entries_dir(prefix, bflow->entry, fname,
+					  sizeof(fname))) {
+			/* fs_closedir() closes the fs, so re-open it */
+			ret = bootmeth_setup_fs(bflow, desc);
+			if (!ret)
+				ret = bootmeth_try_file(bflow, desc, NULL,
+							fname);
+		} else if (!bflow->entry) {
+			/* fs_opendir() closes the fs, so re-open it */
+			ret = bootmeth_setup_fs(bflow, desc);
+			if (!ret)
+				ret = bootmeth_try_file(bflow, desc, prefix,
+							BLS_ENTRY_FILE);
+		}
 	} while (ret && prefixes && prefixes[++i]);
 
 	if (ret) {
@@ -306,8 +387,8 @@  static int bls_load_files(struct udevice *dev, struct bootflow *bflow,
 	bool already_loaded;
 	int ret;
 
-	/* Check if files are already loaded (first image has address) */
-	first_img = alist_get(&bflow->images, 0, struct bootflow_img);
+	/* Check if kernel is already loaded (skip the BLS config image) */
+	first_img = alist_get(&bflow->images, 1, struct bootflow_img);
 	already_loaded = first_img && first_img->addr;
 
 	/* Set up PXE context */
@@ -410,6 +491,10 @@  static int bls_boot(struct udevice *dev, struct bootflow *bflow)
 	if (ret)
 		return ret;
 
+	/* Set bootargs from BLS options before booting */
+	if (label->append)
+		env_set("bootargs", label->append);
+
 	/* Boot the label */
 	pxe_ctx.label = label;
 	ret = pxe_boot(&pxe_ctx);
@@ -427,6 +512,7 @@  static int bls_bootmeth_bind(struct udevice *dev)
 
 	plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
 		"Boot Loader Specification (BLS) Type #1" : "bls";
+	plat->flags = BOOTMETHF_MULTI;
 
 	return 0;
 }
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index 673bdbe33de..73ea1102af5 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -1874,12 +1874,13 @@  static int bootflow_cmd_bls(struct unit_test_state *uts)
 	ut_assert_nextline("Seq  Method       State   Uclass    Part  E  Name                      Filename");
 	ut_assert_nextlinen("---");
 	ut_assert_nextlinen("  0  extlinux");
-	ut_assert_nextline("  1  bls          ready   mmc          1     mmc15.bootdev.part_1      /loader/entry.conf");
+	ut_assert_nextline("  1  bls          ready   mmc          1     mmc15.bootdev.part_1      /loader/entries/6.8.0.conf");
+	ut_assert_nextline("  2  bls          ready   mmc          1     mmc15.bootdev.part_1      /loader/entries/6.8.0-rescue.conf");
 	ut_assert_nextlinen("---");
-	ut_assert_nextline("(2 bootflows, 2 valid)");
+	ut_assert_nextline("(3 bootflows, 3 valid)");
 	ut_assert_console_end();
 
-	/* Select the BLS bootflow and check info */
+	/* Select the first BLS bootflow and check info */
 	ut_assertok(run_command("bootflow select 1", 0));
 	ut_assert_console_end();
 	ut_assertok(run_command("bootflow info", 0));
@@ -1892,7 +1893,18 @@  static int bootflow_cmd_bls(struct unit_test_state *uts)
 	if (IS_ENABLED(CONFIG_BLK_LUKS))
 		ut_assert_nextline("Encrypted: no");
 	ut_assert_nextline("Subdir:    (none)");
-	ut_assert_nextline("Filename:  /loader/entry.conf");
+	ut_assert_nextline("Filename:  /loader/entries/6.8.0.conf");
+	ut_assert_skip_to_line("OS:        Test Boot");
+	ut_assert_skip_to_line("Error:     0");
+	ut_assert_console_end();
+
+	/* Select the second BLS bootflow and check info */
+	ut_assertok(run_command("bootflow select 2", 0));
+	ut_assert_console_end();
+	ut_assertok(run_command("bootflow info", 0));
+	ut_assert_nextline("Name:      mmc15.bootdev.part_1");
+	ut_assert_skip_to_line("Filename:  /loader/entries/6.8.0-rescue.conf");
+	ut_assert_skip_to_line("OS:        Rescue Boot");
 	ut_assert_skip_to_line("Error:     0");
 	ut_assert_console_end();
 
diff --git a/test/py/img/bls.py b/test/py/img/bls.py
index 0f55aad8ba5..3cf6ee21cd6 100644
--- a/test/py/img/bls.py
+++ b/test/py/img/bls.py
@@ -25,24 +25,36 @@  def setup_bls_image(config, log, devnum, basename):
     dtb = 'sandbox.dtb'
 
     # BLS Type #1 entry format
-    script = f'''title Test Boot
+    entry1 = f'''title Test Boot
 version 6.8.0
 linux /{vmlinux}
 options root=/dev/mmcblk0p2 ro quiet
 initrd /{initrd}
+devicetree /{dtb}'''
+
+    entry2 = f'''title Rescue Boot
+version 6.8.0-rescue
+linux /{vmlinux}
+options root=/dev/mmcblk0p2 ro quiet single
+initrd /{initrd}
 devicetree /{dtb}'''
 
     fsh = FsHelper(config, 'vfat', 18, prefix=basename)
     fsh.setup()
 
-    # Create loader directory for BLS entry
+    # Create loader/entries directory for BLS entries
     loader = os.path.join(fsh.srcdir, 'loader')
     mkdir_cond(loader)
+    entries = os.path.join(loader, 'entries')
+    mkdir_cond(entries)
 
-    # Create BLS entry file
-    conf = os.path.join(loader, 'entry.conf')
-    with open(conf, 'w', encoding='ascii') as fd:
-        print(script, file=fd)
+    # Create two BLS entry files
+    with open(os.path.join(entries, '6.8.0.conf'), 'w',
+              encoding='ascii') as fd:
+        print(entry1, file=fd)
+    with open(os.path.join(entries, '6.8.0-rescue.conf'), 'w',
+              encoding='ascii') as fd:
+        print(entry2, file=fd)
 
     # Create compressed kernel image
     inf = os.path.join(config.persistent_data_dir, 'inf')