From patchwork Tue Mar 24 22:18:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2051 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774390789; bh=3FivoXkzDEC5vk1duYuw/yGPNORJOuNFyLqFNbLPk1E=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=eVfhfwqfRuQEIlpI56jZNBBMXVV0zh/rAyHVZ1v4k7UjMrxjwe2vslG6orQ6WW6zb E+T/fuBTqnAFfnOmQP9hem9iRSLISfRC7kx3Wq2bZBZCyg9K+TNjL7DvH4oefywg8O QZZOi7a8bYaYrhBeAhZGFAP4z1+UevYOT1K1r5/9YyUWRflC8v5O+QjJ2OZZqZDKVo zyAKhuMGHTzV8m8BhB16emOzDL5qepoH+2i7w/TBGNvfXh0BMYouY8q+Tkmx9WdhIR EdC4g31hRE/9u3YNWf0Sytd/KcYHXmAeHdtw2dpeWahRObFANnZMvJUQ3MctgDYC49 j+Yo7b7Uo5Okg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8703F6A238 for ; Tue, 24 Mar 2026 16:19:49 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id jTa14Kk2Ndx1 for ; Tue, 24 Mar 2026 16:19:49 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774390789; bh=3FivoXkzDEC5vk1duYuw/yGPNORJOuNFyLqFNbLPk1E=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=eVfhfwqfRuQEIlpI56jZNBBMXVV0zh/rAyHVZ1v4k7UjMrxjwe2vslG6orQ6WW6zb E+T/fuBTqnAFfnOmQP9hem9iRSLISfRC7kx3Wq2bZBZCyg9K+TNjL7DvH4oefywg8O QZZOi7a8bYaYrhBeAhZGFAP4z1+UevYOT1K1r5/9YyUWRflC8v5O+QjJ2OZZqZDKVo zyAKhuMGHTzV8m8BhB16emOzDL5qepoH+2i7w/TBGNvfXh0BMYouY8q+Tkmx9WdhIR EdC4g31hRE/9u3YNWf0Sytd/KcYHXmAeHdtw2dpeWahRObFANnZMvJUQ3MctgDYC49 j+Yo7b7Uo5Okg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3EB816A228 for ; Tue, 24 Mar 2026 16:19:49 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774390787; bh=Ou94pT80H6m4OYNTv2OHznYnCdeo0+Cg4QZlzcW4kg8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oze/NIi3M9ReUwN5QR0jpsLjMpJ5a5JZX9zjKOlEvoIkNsWru0bzrRMoccmZH5m+i BbQYQ3/BHjLIBnSyqixiJH2t+iRAhhJUqUnBPFnFYcl2m2I0rU3AxiGyBe3161ztOO D2CXgAYGT42+abDOHfEDkyzHvyDJDoRMUrTXPU85pPSC//zQccpQ9zwqtZ9GzGfcrC CX1jfuRU/TALbqfC4QjhxN2LtWGRjhlmoJbDvY7ZPmaPnSjhcG93n+5YVtECLSfqXu 3HNW4tAy1zW7In08yqGCKwnqx1y2ZWD1b+9Ikj0KE9b6527RKLGWRpuSr6ECHaLzL6 UIKepqj7xcn4w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 372DA6A23D; Tue, 24 Mar 2026 16:19:47 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id qg-y81yimrSR; Tue, 24 Mar 2026 16:19:47 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1774390779; bh=dvQSGQ/0lztgSvw27eQ1VrL1MVokOC95NbOAt5E6B70=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IdCCK9tVgNVcBvcYMjGHTcST48Ww69FPPu27ZxaI+GIcAzVQ+/Nw+6BLjfpw2SPvI H0bjQyHClxVFe7P7DyC/eI4u4DJlTIDsEI4TOfdMjaYCwxnaouCNNXkppo+xUVsHdY bJ32RmsYr8GgTv1qDWh8QHiZ04LTe4wWevAqvKOhTgR7F0RWlTGzdmpgeLhNuGb807 r+mI1iVy4zvrWBjXx/Ui9ZQEnrph9DBxC/W+wHnoGyiA7gqcMPz7IyRj0deiXWtzDA O0O2GU2BUWyPCvS1xXu+Qh0Uyes2FT1ggtuEt7jPoLbg7Zum0MtkH+Dp0K9045smDh 7f6F+XPP7iAlQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 165C26A248; Tue, 24 Mar 2026 16:19:39 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Tue, 24 Mar 2026 16:18:59 -0600 Message-ID: <20260324221911.3678307-6-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260324221911.3678307-1-sjg@u-boot.org> References: <20260324221911.3678307-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: GBDNNXIOKKDM6VOILOTSG2VYKKZNATDA X-Message-ID-Hash: GBDNNXIOKKDM6VOILOTSG2VYKKZNATDA X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 05/12] bls: Enable multi-entry bootflow scanning List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass 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 --- boot/bootmeth_bls.c | 106 +++++++++++++++++++++++++++++++++++++++---- test/boot/bootflow.c | 20 ++++++-- test/py/img/bls.py | 24 +++++++--- 3 files changed, 130 insertions(+), 20 deletions(-) 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 #include #include +#include +#include #include #include #include @@ -41,7 +44,8 @@ #include #include -/* 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')