[PATCH 7/7] boot: add a minimal bootmeth for the Boot Loader Specification

Alexey Charkov alchark at flipper.net
Thu Jun 4 17:31:12 CEST 2026


Add a bootmeth that finds and boots Boot Loader Specification (BLS)
type #1 entry files [1]. On each block-device partition it scans, the
bootmeth looks for files matching '<prefix>loader/entries/*.conf'
(where <prefix> comes from bootstd_get_prefixes(), typically '/' and
'/boot/'), picks the highest-sorting filename, parses it, and exposes
it as a bootflow.

Implementation reuses the existing pxelinux infrastructure.

For now the entry chosen on a partition is purely the lexicographic
maximum of *.conf filenames; sort-key / version field handling
(spec-mandated tiebreakers) and boot-counting (the '+TRIES_LEFT'
filename suffix) are left as TODOs. Likewise, only the top-sorted entry
is surfaced because the bootstd framework currently allows one bootflow
per (bootmeth, partition); exposing every discovered entry will require
a framework extension.

Type #2 BLS (drop-in directory of EFI binaries) is out of scope here;
existing EFI bootmeths cover that use case.

[1] https://uapi-group.org/specifications/specs/boot_loader_specification/

Signed-off-by: Alexey Charkov <alchark at flipper.net>
---
 boot/Kconfig        |  17 +++
 boot/Makefile       |   1 +
 boot/bootmeth_bls.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 351 insertions(+)

diff --git a/boot/Kconfig b/boot/Kconfig
index e1114aea843e..fa871da46640 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -636,6 +636,23 @@ config BOOTMETH_EXTLINUX_PXE
 
 	  This provides a way to try out standard boot on an existing boot flow.
 
+config BOOTMETH_BLS
+	bool "Bootdev support for Boot Loader Specification entries"
+	select PXE_UTILS
+	default y
+	help
+	  Enables support for booting via Boot Loader Specification type #1
+	  entry files. The bootmeth scans each filesystem it finds for files
+	  matching 'loader/entries/*.conf' (or '/boot/loader/entries/*.conf')
+	  and boots the highest-sorting entry.
+
+	  The specification is here:
+
+	    https://uapi-group.org/specifications/specs/boot_loader_specification/
+
+	  Type #2 BLS (drop-in directory of EFI binaries) is not handled here;
+	  use BOOTMETH_EFI_BOOTMGR for that.
+
 config BOOTMETH_EFILOADER
 	bool "Bootdev support for EFI boot"
 	depends on EFI_BINARY_EXEC
diff --git a/boot/Makefile b/boot/Makefile
index 7fb56e7ef379..0ce6fd1cd050 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_$(PHASE_)BOOTSTD_PROG) += prog_boot.o
 
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX) += bootmeth_extlinux.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX_PXE) += bootmeth_pxe.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_BLS) += bootmeth_bls.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EFILOADER) += bootmeth_efi.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_CROS) += bootm.o bootm_os.o bootmeth_cros.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_QFW) += bootmeth_qfw.o
diff --git a/boot/bootmeth_bls.c b/boot/bootmeth_bls.c
new file mode 100644
index 000000000000..3df4f20a11dd
--- /dev/null
+++ b/boot/bootmeth_bls.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for the Boot Loader Specification (type #1 entry files)
+ *
+ * Reuses the pxelinux parser/boot path: each on-disk entry is read into a
+ * struct pxe_label via parse_label_keys() and booted via label_boot().
+ *
+ * Spec: https://uapi-group.org/specifications/specs/boot_loader_specification/
+ *
+ * TODO: a partition typically holds several BLS entries, but the bootstd
+ * framework currently allows only one bootflow per (bootmeth, partition)
+ * pair, so this bootmeth surfaces only the highest-sorting entry. Once the
+ * framework grows a way for a bootmeth to emit multiple bootflows from a
+ * single partition, this should expose every discovered entry so the user
+ * can pick from the standard 'bootflow menu' UI rather than be limited to
+ * the default pick.
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <command.h>
+#include <dm.h>
+#include <extlinux.h>
+#include <fs.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <pxe_utils.h>
+#include <linux/sizes.h>
+
+#define BLS_DIR		"loader/entries"
+#define BLS_SUFFIX	".conf"
+
+static int bls_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+	int ret;
+
+	/* This only works on block devices */
+	ret = bootflow_iter_check_blk(iter);
+	if (ret)
+		return log_msg_ret("blk", ret);
+
+	return 0;
+}
+
+static int bls_getfile(struct pxe_context *ctx, const char *file_path,
+		       char *file_addr, enum bootflow_img_t type, ulong *sizep)
+{
+	struct extlinux_info *info = ctx->userdata;
+	ulong addr;
+	int ret;
+
+	addr = simple_strtoul(file_addr, NULL, 16);
+
+	/* Allow up to 1GB */
+	*sizep = 1 << 30;
+	ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr,
+				 type, sizep);
+	if (ret)
+		return log_msg_ret("read", ret);
+
+	return 0;
+}
+
+/**
+ * bls_pick_entry() - Find the highest-sorting *.conf across bootstd prefixes
+ *
+ * Walks ``<prefix>/loader/entries/`` for each prefix in @prefixes and
+ * returns the lexicographically maximum full path seen.
+ *
+ * The spec leaves ordering between prefixes unspecified; comparing full
+ * paths is a deterministic-and-cheap stand-in.
+ *
+ * The Boot Loader Specification says entries should be sorted by sort-key
+ * (descending), then version (descending), then filename (descending). For
+ * the time being only the filename criterion is implemented, which is
+ * sufficient for most distros that encode kernel version into the filename.
+ *
+ * TODO: implement proper spec-compliant ordering. That requires reading
+ * each candidate entry, parsing its 'sort-key' and 'version' fields (the
+ * latter compared with strverscmp()-style logic), and only falling back to
+ * filename order when those tie.
+ *
+ * @prefixes:	NULL-terminated array of bootstd prefixes to search
+ * @desc:	Block descriptor (used to re-mount per prefix)
+ * @bflow:	Bootflow being populated (used to re-mount per prefix)
+ * @fullp:	Returns the chosen full path (allocated), or NULL if none
+ * Return: 0 on success, -ENOENT if no entry was found, < 0 on other error
+ */
+static int bls_pick_entry(const char *const *prefixes, struct blk_desc *desc,
+			  struct bootflow *bflow, char **fullp)
+{
+	char dirpath[256];
+	char *best = NULL;
+	int ret;
+	int i;
+
+	for (i = 0; prefixes && prefixes[i]; i++) {
+		struct fs_dir_stream *dirs;
+		struct fs_dirent *dent;
+
+		/* fs_closedir() below resets the global fs_type. */
+		ret = bootmeth_setup_fs(bflow, desc);
+		if (ret) {
+			free(best);
+			return log_msg_ret("fs", ret);
+		}
+
+		snprintf(dirpath, sizeof(dirpath), "%s%s",
+			 prefixes[i], BLS_DIR);
+		dirs = fs_opendir(dirpath);
+		if (!dirs)
+			continue;
+
+		while ((dent = fs_readdir(dirs))) {
+			size_t len = strlen(dent->name);
+			char *full;
+
+			if (dent->type != FS_DT_REG)
+				continue;
+			if (len <= strlen(BLS_SUFFIX))
+				continue;
+			if (strcmp(dent->name + len - strlen(BLS_SUFFIX),
+				   BLS_SUFFIX))
+				continue;
+
+			full = malloc(strlen(dirpath) + 1 + len + 1);
+			if (!full) {
+				free(best);
+				fs_closedir(dirs);
+				return -ENOMEM;
+			}
+			sprintf(full, "%s/%s", dirpath, dent->name);
+
+			if (!best || strcmp(full, best) > 0) {
+				free(best);
+				best = full;
+			} else {
+				free(full);
+			}
+		}
+		fs_closedir(dirs);
+	}
+
+	if (!best)
+		return -ENOENT;
+
+	*fullp = best;
+
+	return 0;
+}
+
+/*
+ * TODO: BLS entry filenames may carry a boot-counter suffix of the form
+ * '+TRIES_LEFT[-TRIES_DONE]' immediately before the .conf extension (see
+ * the spec section on "Boot counting"). When that is implemented, this
+ * bootmeth should:
+ *   - parse and strip the suffix from the displayed entry name,
+ *   - skip entries whose TRIES_LEFT has reached zero,
+ *   - decrement TRIES_LEFT (renaming the file) on each boot attempt.
+ * For now the suffix is left intact in the entry name and ignored.
+ */
+static int bls_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+	struct blk_desc *desc;
+	const char *const *prefixes;
+	struct udevice *bootstd;
+	struct pxe_label *label = NULL;
+	struct pxe_menu scratch = {};
+	char *fpath = NULL;
+	const char *base;
+	char *body;
+	int ret;
+
+	ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+	if (ret)
+		return log_msg_ret("std", ret);
+
+	/* We require a partitioned block device */
+	if (!bflow->blk || !bflow->part)
+		return -ENOENT;
+
+	desc = dev_get_uclass_plat(bflow->blk);
+	prefixes = bootstd_get_prefixes(bootstd);
+
+	ret = bls_pick_entry(prefixes, desc, bflow, &fpath);
+	if (ret)
+		return log_msg_ret("scan", ret);
+
+	base = strrchr(fpath, '/');
+	base = base ? base + 1 : fpath;
+
+	/*
+	 * bls_pick_entry() finished with fs_closedir(), which resets the
+	 * global fs_type. Re-mount the partition so bootmeth_try_file()'s
+	 * internal fs_size() call can find the right filesystem driver.
+	 */
+	ret = bootmeth_setup_fs(bflow, desc);
+	if (ret) {
+		free(fpath);
+		return log_msg_ret("fs", ret);
+	}
+
+	ret = bootmeth_try_file(bflow, desc, NULL, fpath);
+	if (ret) {
+		free(fpath);
+		return log_msg_ret("try", ret);
+	}
+
+	ret = bootmeth_alloc_file(bflow, SZ_64K, 1, BFI_EXTLINUX_CFG);
+	if (ret) {
+		free(fpath);
+		return log_msg_ret("read", ret);
+	}
+
+	label = label_create();
+	if (!label) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	/*
+	 * BLS files have no 'label NAME' header — derive the label name from
+	 * the basename (without the .conf suffix) so messages are useful.
+	 */
+	label->name = strndup(base, strlen(base) - strlen(BLS_SUFFIX));
+	if (!label->name) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	body = bflow->buf;
+	ret = parse_label_keys(&body, &scratch, label, true);
+	if (ret < 0)
+		goto err;
+
+	/*
+	 * scratch is only used to give parse_label_keys() somewhere safe to
+	 * stash menu-level state (e.g. a stray 'menu default' line). BLS
+	 * entry files don't contain such lines but defensively free anything
+	 * that did get allocated.
+	 */
+	free(scratch.default_label);
+
+	/*
+	 * label->menu was populated either from a BLS 'title' line (the
+	 * spec-mandated human-readable name) or from a stray 'menu label'
+	 * the parser may have picked up. Fall back to the filename-derived
+	 * label name when neither is present.
+	 */
+	bflow->os_name = strdup(label->menu ? label->menu : label->name);
+	if (!bflow->os_name) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	bflow->bootmeth_priv = label;
+	free(fpath);
+
+	return 0;
+
+err:
+	if (label)
+		label_destroy(label);
+	free(fpath);
+	return log_msg_ret("bls", ret);
+}
+
+static int bls_boot(struct udevice *dev, struct bootflow *bflow)
+{
+	struct cmd_tbl cmdtp = {};	/* dummy */
+	struct pxe_context ctx;
+	struct extlinux_info info;
+	struct pxe_label *label = bflow->bootmeth_priv;
+	int ret;
+
+	if (!label)
+		return log_msg_ret("lbl", -ENOENT);
+
+	info.dev = dev;
+	info.bflow = bflow;
+
+	/*
+	 * BLS paths are absolute relative to the filesystem root of the
+	 * partition the entry lives on. allow_abs_path=true honours that;
+	 * passing NULL as the bootfile keeps the prefix empty so absolute
+	 * paths are not rebased.
+	 */
+	ret = pxe_setup_ctx(&ctx, &cmdtp, bls_getfile, &info, true,
+			    NULL, false, false);
+	if (ret)
+		return log_msg_ret("ctx", -EINVAL);
+
+	ret = label_boot(&ctx, label);
+	pxe_destroy_ctx(&ctx);
+	if (ret)
+		return log_msg_ret("boot", -EINVAL);
+
+	return 0;
+}
+
+static int bls_bootmeth_bind(struct udevice *dev)
+{
+	struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+	plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+		"Boot Loader Specification" : "bls";
+
+	return 0;
+}
+
+static struct bootmeth_ops bls_bootmeth_ops = {
+	.check		= bls_check,
+	.read_bootflow	= bls_read_bootflow,
+	.read_file	= bootmeth_common_read_file,
+	.boot		= bls_boot,
+};
+
+static const struct udevice_id bls_bootmeth_ids[] = {
+	{ .compatible = "u-boot,bls" },
+	{ }
+};
+
+U_BOOT_DRIVER(bootmeth_2bls) = {
+	.name		= "bootmeth_bls",
+	.id		= UCLASS_BOOTMETH,
+	.of_match	= bls_bootmeth_ids,
+	.ops		= &bls_bootmeth_ops,
+	.bind		= bls_bootmeth_bind,
+};

-- 
2.53.0



More information about the U-Boot mailing list