[Concept,03/12] extlinux: Enable multi-entry bootflow scanning

Message ID 20260324221911.3678307-4-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>

Use the BOOTMETHF_MULTI flag to produce one bootflow per label in an
extlinux configuration file.

Adjust extlinux_fill_info() to walk to the Nth label based on
bflow->entry, returning -ENOENT when the index exceeds the available
labels.

This allows bootflow scanning to discover all kernels defined in an
extlinux config, rather than only exposing the first label.

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

 boot/bootmeth_extlinux.c | 29 ++++++++++++++++++++---------
 test/boot/bootdev.c      | 13 ++++++++++++-
 test/boot/bootflow.c     | 33 ++++++++++++++++++++++++++-------
 3 files changed, 58 insertions(+), 17 deletions(-)
  

Patch

diff --git a/boot/bootmeth_extlinux.c b/boot/bootmeth_extlinux.c
index 3ffe21d96a3..bfc816f3628 100644
--- a/boot/bootmeth_extlinux.c
+++ b/boot/bootmeth_extlinux.c
@@ -117,11 +117,12 @@  static int extlinux_check_luks(struct bootflow *bflow)
 /**
  * extlinux_fill_info() - Decode the extlinux file to find out its info
  *
- * Uses pxe_parse() to parse the configuration file and extract the first
- * label's name to use as the bootflow OS name.
+ * Uses pxe_parse() to parse the configuration file and extract the label
+ * selected by @bflow->entry to use as the bootflow OS name.
  *
- * @bflow: Bootflow to process
- * Return: 0 if OK, -ve on error
+ * @bflow: Bootflow to process (entry selects which label)
+ * Return: 0 if OK, -ENOENT if entry index exceeds available labels, other
+ * -ve on error
  */
 static int extlinux_fill_info(struct bootflow *bflow)
 {
@@ -129,19 +130,28 @@  static int extlinux_fill_info(struct bootflow *bflow)
 	struct pxe_label *label;
 	const char *name;
 	ulong addr;
+	int i;
 
-	log_debug("parsing bflow file size %x\n", bflow->size);
+	log_debug("parsing bflow file size %x entry %d\n", bflow->size,
+		  bflow->entry);
 	addr = map_to_sysmem(bflow->buf);
 	ctx = pxe_parse(addr, bflow->size, bflow->fname);
 	if (!ctx)
 		return log_msg_ret("prs", -EINVAL);
 
-	if (list_empty(&ctx->cfg->labels)) {
-		pxe_cleanup(ctx);
-		return log_msg_ret("lab", -ENOENT);
+	/* Walk to the requested label */
+	i = 0;
+	list_for_each_entry(label, &ctx->cfg->labels, list) {
+		if (i == bflow->entry)
+			goto found;
+		i++;
 	}
 
-	label = list_first_entry(&ctx->cfg->labels, struct pxe_label, list);
+	/* No more entries at this index */
+	pxe_cleanup(ctx);
+	return -ENOENT;
+
+found:
 	name = label->menu ? label->menu : label->name;
 	if (name) {
 		bflow->os_name = strdup(name);
@@ -267,6 +277,7 @@  static int extlinux_bootmeth_bind(struct udevice *dev)
 
 	plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
 		"Extlinux boot from a block device" : "extlinux";
+	plat->flags = BOOTMETHF_MULTI;
 
 	return 0;
 }
diff --git a/test/boot/bootdev.c b/test/boot/bootdev.c
index 81610563141..06ef695256e 100644
--- a/test/boot/bootdev.c
+++ b/test/boot/bootdev.c
@@ -228,6 +228,9 @@  static int bootdev_test_order(struct unit_test_state *uts)
 	/* get the second usb device which has a backing file (flash3.img) */
 	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
 
+	/* get the second entry from flash3 (Ubuntu has two extlinux labels) */
+	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
+
 	ut_asserteq(-ENODEV, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(6, iter.num_devs);
 	ut_asserteq_str("mmc1.bootdev", iter.dev_used[0]->name);
@@ -269,10 +272,12 @@  static int bootdev_test_order(struct unit_test_state *uts)
 
 	/*
 	 * Now scan past mmc1 and make sure that the 4 USB devices show up. The
-	 * first two have a backing file so returns success
+	 * first two have a backing file so returns success. flash3 has two
+	 * extlinux labels so produces an extra bootflow.
 	 */
 	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
+	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(-ENODEV, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(7, iter.num_devs);
 	ut_asserteq_str("mmc2.bootdev", iter.dev_used[0]->name);
@@ -341,6 +346,9 @@  static int bootdev_test_prio(struct unit_test_state *uts)
 	/* get the second usb device which has a backing file (flash3.img) */
 	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
 
+	/* get the second entry from flash3 (Ubuntu has two extlinux labels) */
+	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
+
 	ut_asserteq(-ENODEV, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(7, iter.num_devs);
 	ut_asserteq_str("mmc2.bootdev", iter.dev_used[0]->name);
@@ -365,6 +373,9 @@  static int bootdev_test_prio(struct unit_test_state *uts)
 	/* get the second usb device which has a backing file (flash3.img) */
 	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
 
+	/* get the second entry from flash3 (Ubuntu has two extlinux labels) */
+	ut_asserteq(0, bootflow_scan_next(&iter, &bflow));
+
 	ut_asserteq(-ENODEV, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(9, iter.num_devs);
 	ut_asserteq_str("hub1.p1.usb_mass_storage.lun0.bootdev",
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index 3bea1cc6634..2e0cf5d309e 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -211,17 +211,20 @@  static int bootflow_cmd_scan_e(struct unit_test_state *uts)
 	ut_assert_nextline("  5  vbe          media   mmc          0     mmc1.bootdev.whole        ");
 	ut_assert_nextline("     ** No partition found, err=-2: No such file or directory");
 	ut_assert_nextline("  6  extlinux     ready   mmc          1     mmc1.bootdev.part_1       /extlinux/extlinux.conf");
+	/* multi-entry tries entry 1 on extlinux (Fedora has one label) */
+	ut_assert_nextline("  7  extlinux     ready   mmc          1     mmc1.bootdev.part_1       /extlinux/extlinux.conf");
+	ut_assert_nextline("     ** Ready, err=-2: No such file or directory");
 	ut_assert_nextline(
-		"  7  efi          fs      mmc          1     mmc1.bootdev.part_1       /EFI/BOOT/%s",
+		"  8  efi          fs      mmc          1     mmc1.bootdev.part_1       /EFI/BOOT/%s",
 		efi_get_basename());
 
 	ut_assert_skip_to_line("Scanning bootdev 'mmc0.bootdev':");
 	ut_assert_skip_to_line(
-		" 5f  vbe          media   mmc          0     mmc0.bootdev.whole        ");
+		" 60  vbe          media   mmc          0     mmc0.bootdev.whole        ");
 	ut_assert_nextline("     ** No partition found, err=-93: Protocol not supported");
 	ut_assert_nextline("No more bootdevs");
 	ut_assert_nextlinen("---");
-	ut_assert_nextline("(96 bootflows, 1 valid)");
+	ut_assert_nextline("(97 bootflows, 1 valid)");
 	ut_assert_console_end();
 
 	ut_assertok(run_command("bootflow list", 0));
@@ -230,9 +233,9 @@  static int bootflow_cmd_scan_e(struct unit_test_state *uts)
 	ut_assert_nextlinen("---");
 	ut_assert_nextline("  0  extlinux     media   mmc          0     mmc2.bootdev.whole        ");
 	ut_assert_nextline("  1  efi          media   mmc          0     mmc2.bootdev.whole        ");
-	ut_assert_skip_to_line(" 5f  vbe          media   mmc          0     mmc0.bootdev.whole        ");
+	ut_assert_skip_to_line(" 60  vbe          media   mmc          0     mmc0.bootdev.whole        ");
 	ut_assert_nextlinen("---");
-	ut_assert_nextline("(96 bootflows, 1 valid)");
+	ut_assert_nextline("(97 bootflows, 2 valid)");
 	ut_assert_console_end();
 
 	return 0;
@@ -406,6 +409,17 @@  static int bootflow_iter(struct unit_test_state *uts)
 	ut_asserteq(BOOTFLOWST_READY, bflow.state);
 	bootflow_free(&bflow);
 
+	/*
+	 * With BOOTMETHF_MULTI, the iterator tries entry 1 on extlinux.
+	 * Fedora has only one label, so entry 1 fails. Since BOOTFLOWIF_ALL
+	 * is set, this failed attempt is returned.
+	 */
+	ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow));
+	ut_asserteq(0, iter.cur_method);
+	ut_asserteq(1, iter.entry);
+	ut_asserteq(1, iter.part);
+	bootflow_free(&bflow);
+
 	ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow));
 	ut_asserteq(3, iter.num_methods);
 	ut_asserteq(1, iter.cur_method);
@@ -1435,8 +1449,10 @@  static int bootflow_efi(struct unit_test_state *uts)
 		"  1  efi          ready   usb          1     hub1.p2.usb_mass_storage. /EFI/BOOT/BOOTSBOX.EFI");
 	ut_assert_nextlinen(
 		"  2  extlinux     ready   usb          1     hub1.p4.usb_mass_storage. /extlinux/extlinux.conf");
+	ut_assert_nextlinen(
+		"  3  extlinux     ready   usb          1     hub1.p4.usb_mass_storage. /extlinux/extlinux.conf");
 	ut_assert_nextlinen("---");
-	ut_assert_skip_to_line("(3 bootflows, 3 valid)");
+	ut_assert_skip_to_line("(4 bootflows, 4 valid)");
 	ut_assert_console_end();
 
 	ut_assertok(run_command("bootflow select 1", 0));
@@ -1812,8 +1828,11 @@  static int bootflow_cmd_info_encrypted(struct unit_test_state *uts)
 	ut_assert_nextline(
 		"  1  extlinux     ready   mmc          1  %c  mmc12.bootdev.part_1      /extlinux/extlinux.conf",
 		IS_ENABLED(CONFIG_BLK_LUKS) ? 'E' : ' ');
+	ut_assert_nextline(
+		"  2  extlinux     ready   mmc          1  %c  mmc12.bootdev.part_1      /extlinux/extlinux.conf",
+		IS_ENABLED(CONFIG_BLK_LUKS) ? 'E' : ' ');
 	ut_assert_nextline("---  -----------  ------  --------  ----  -  ------------------------  ----------------");
-	ut_assert_nextline("(2 bootflows, 2 valid)");
+	ut_assert_nextline("(3 bootflows, 3 valid)");
 	ut_assert_console_end();
 
 	/* Select the mmc12 bootflow and check info shows encryption */