[Concept,4/8] boot: Add BLS Type #1 bootmethod

Message ID 20260213202417.223068-5-sjg@u-boot.org
State New
Headers
Series Add BLS Type #1 bootmethod with FIT and multi-initrd support |

Commit Message

Simon Glass Feb. 13, 2026, 8:24 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add a bootmethod for Boot Loader Specification (BLS) Type #1 entries,
enabling U-Boot to discover and boot from BLS configuration files.

The bootmethod looks for a single BLS entry file at loader/entry.conf
on each bootdev partition. When found, it parses the entry using the
BLS parser and creates a bootflow that can be booted or displayed in
menus.

Implementation details:
- Searches for loader/entry.conf with standard bootdev prefix support
- Parses the BLS entry and registers discovered images (kernel, initrd,
  FDT)
- Converts the BLS entry to a PXE label for boot execution
- Reuses the existing PXE infrastructure for file loading and boot
- Supports FITs with #config syntax in the linux field
- Handles multiple initrd lines (uses the first due to a PXE limitation)
- Skips reloading files if already loaded (an optimisation)

Current limitations:
- Single entry file only (not multiple entries in loader/entries/)
- Only first initrd used (PXE infrastructure limitation)
- No devicetree-overlay support
- No architecture/machine-id filtering
- No version-based sorting
- No UKI/EFI support (Type #2)

The bootmethod integrates with the standard bootflow framework and
appears in bootflow menus alongside extlinux and EFI entries.

Documentation is provided in doc/usage/bls.rst covering the BLS format,
supported features, and current limitations.

Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 boot/Makefile       |   2 +-
 boot/bootflow.c     |   1 +
 boot/bootmeth_bls.c | 438 ++++++++++++++++++++++++++++++++++++++++++++
 doc/usage/bls.rst   | 128 +++++++++++++
 doc/usage/index.rst |   1 +
 include/bootflow.h  |   2 +
 6 files changed, 571 insertions(+), 1 deletion(-)
 create mode 100644 boot/bootmeth_bls.c
 create mode 100644 doc/usage/bls.rst
  

Patch

diff --git a/boot/Makefile b/boot/Makefile
index 1e75f7ece79..7cb28e52e50 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -29,7 +29,7 @@  obj-$(CONFIG_$(PHASE_)BOOTSTD_PROG) += prog_boot.o
 
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX) += ext_pxe_common.o bootmeth_extlinux.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX_PXE) += ext_pxe_common.o bootmeth_pxe.o
-obj-$(CONFIG_$(PHASE_)BOOTMETH_BLS) += bls_parse.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_BLS) += bls_parse.o bootmeth_bls.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EFI) += bootmeth_efi.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_CROS) += bootmeth_cros.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_FEL) += bootmeth_fel.o
diff --git a/boot/bootflow.c b/boot/bootflow.c
index 0c389f78a28..0925c879e3d 100644
--- a/boot/bootflow.c
+++ b/boot/bootflow.c
@@ -30,6 +30,7 @@  enum {
 
 static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = {
 	"extlinux_cfg",
+	"bls_cfg",
 	"logo",
 	"efi",
 	"cmdline",
diff --git a/boot/bootmeth_bls.c b/boot/bootmeth_bls.c
new file mode 100644
index 00000000000..c9a2afbb6f3
--- /dev/null
+++ b/boot/bootmeth_bls.c
@@ -0,0 +1,438 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for Boot Loader Specification (BLS) Type #1
+ *
+ * Copyright 2026 Canonical Ltd
+ * Written by Simon Glass <simon.glass@canonical.com>
+ *
+ * This implements support for BLS Type #1 entries as defined in:
+ * https://uapi-group.org/specifications/specs/boot_loader_specification/
+ *
+ * Supported features:
+ * - Single BLS entry file at loader/entry.conf
+ * - Fields: title, version, linux, options, initrd, devicetree
+ * - Multiple options lines (concatenated with spaces)
+ * - Multiple initrd lines (only first used, PXE limitation)
+ * - FITs with #config syntax in linux field
+ * - Zero-copy parsing (fields point into bootflow buffer)
+ *
+ * Current limitations:
+ * - Single entry file only, not multiple entries in loader/entries/
+ * - Only first initrd used (PXE infrastructure supports one)
+ * - No devicetree-overlay support
+ * - No architecture/machine-id filtering
+ * - No version-based sorting
+ * - No UKI/EFI support (Type #2)
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <asm/cache.h>
+#include <bls.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <fs_legacy.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <part.h>
+#include <pxe_utils.h>
+#include <linux/string.h>
+
+/* Single BLS entry file to check */
+#define BLS_ENTRY_FILE		"loader/entry.conf"
+
+/**
+ * struct bls_info - context information for BLS getfile callback
+ *
+ * @dev: Bootmethod device being used to boot
+ * @bflow: Bootflow being booted
+ */
+struct bls_info {
+	struct udevice *dev;
+	struct bootflow *bflow;
+};
+
+static int bls_get_state_desc(struct udevice *dev, char *buf, int maxsize)
+{
+	if (IS_ENABLED(CONFIG_SANDBOX)) {
+		int len;
+
+		len = snprintf(buf, maxsize, "OK");
+
+		return len + 1 < maxsize ? 0 : -ENOSPC;
+	}
+
+	return 0;
+}
+
+static int bls_getfile(struct pxe_context *ctx, const char *file_path,
+		       ulong *addrp, ulong align, enum bootflow_img_t type,
+		       ulong *sizep)
+{
+	struct bls_info *info = ctx->userdata;
+	int ret;
+
+	/* Allow up to 1GB */
+	*sizep = 1 << 30;
+	ret = bootmeth_read_file(info->dev, info->bflow, file_path, addrp,
+				 align, type, sizep);
+	if (ret)
+		return log_msg_ret("read", ret);
+
+	return 0;
+}
+
+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;
+}
+
+/**
+ * bls_to_pxe_label() - Convert bootflow to PXE label for boot execution
+ *
+ * @bflow: Bootflow containing BLS entry and discovered images
+ * @labelp: Returns allocated PXE label structure
+ * Return: 0 on success, -ENOMEM if out of memory
+ */
+static int bls_to_pxe_label(struct bootflow *bflow,
+			    struct pxe_label **labelp)
+{
+	struct pxe_label *label;
+	struct bootflow_img *img;
+	int ret;
+
+	label = calloc(1, sizeof(*label));
+	if (!label)
+		return log_msg_ret("alloc", -ENOMEM);
+
+	INIT_LIST_HEAD(&label->list);
+	alist_init_struct(&label->files, struct pxe_file);
+
+	label->menu = strdup(bflow->os_name ?: "");
+	label->append = strdup(bflow->cmdline ?: "");
+	if (!label->menu || !label->append) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	/* Extract kernel, initrd and FDT from the bootflow images */
+	alist_for_each(img, &bflow->images) {
+		char **fieldp;
+
+		if (img->type == (enum bootflow_img_t)IH_TYPE_KERNEL)
+			fieldp = &label->kernel;
+		else if (img->type == (enum bootflow_img_t)IH_TYPE_RAMDISK)
+			fieldp = &label->initrd;
+		else if (img->type == (enum bootflow_img_t)IH_TYPE_FLATDT)
+			fieldp = &label->fdt;
+		else
+			continue;
+
+		if (!*fieldp) {
+			*fieldp = strdup(img->fname);
+			if (!*fieldp) {
+				ret = -ENOMEM;
+				goto err;
+			}
+		}
+	}
+
+	*labelp = label;
+	return 0;
+
+err:
+	label_destroy(label);
+	return ret;
+}
+
+/**
+ * bls_entry_init() - Parse entry and register images with bootflow
+ *
+ * @entry: Entry structure to initialize
+ * @bflow: Bootflow to populate
+ * @size: Size of BLS entry file in bflow->buf
+ * Return: 0 on success, -ve on error
+ */
+static int bls_entry_init(struct bls_entry *entry, struct bootflow *bflow,
+			  loff_t size)
+{
+	char **initrd;
+	int ret;
+
+	/* Parse BLS entry (fields point into bflow->buf) */
+	ret = bls_parse_entry(bflow->buf, size, entry);
+	if (ret)
+		return log_msg_ret("parse", ret);
+
+	/* Save title as os_name */
+	if (entry->title) {
+		bflow->os_name = strdup(entry->title);
+		if (!bflow->os_name)
+			return log_msg_ret("name", -ENOMEM);
+	}
+
+	/* Transfer cmdline ownership to bflow */
+	if (entry->options) {
+		bflow->cmdline = entry->options;
+		entry->options = NULL;
+	}
+
+	/* Register discovered images (not yet loaded, addr=0) */
+	if (entry->kernel) {
+		if (!bootflow_img_add(bflow, entry->kernel,
+				      (enum bootflow_img_t)IH_TYPE_KERNEL,
+				      0, 0))
+			return log_msg_ret("imk", -ENOMEM);
+	}
+
+	alist_for_each(initrd, &entry->initrds) {
+		if (!bootflow_img_add(bflow, *initrd,
+				      (enum bootflow_img_t)IH_TYPE_RAMDISK,
+				      0, 0))
+			return log_msg_ret("imi", -ENOMEM);
+	}
+
+	if (entry->devicetree) {
+		if (!bootflow_img_add(bflow, entry->devicetree,
+				      (enum bootflow_img_t)IH_TYPE_FLATDT,
+				      0, 0))
+			return log_msg_ret("imf", -ENOMEM);
+	}
+
+	return 0;
+}
+
+static int bls_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+	struct bls_entry entry;
+	struct blk_desc *desc;
+	const char *const *prefixes;
+	struct udevice *bootstd;
+	const char *prefix;
+	loff_t size;
+	int ret, i;
+
+	log_debug("BLS: starting part %d\n", bflow->part);
+
+	/* Get bootstd device for prefixes */
+	ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+	if (ret) {
+		log_debug("no bootstd\n");
+		return log_msg_ret("std", ret);
+	}
+
+	/* Block devices require a partition table */
+	if (bflow->blk && !bflow->part) {
+		log_debug("no partition table\n");
+		return -ENOENT;
+	}
+
+	prefixes = bootstd_get_prefixes(bootstd);
+	desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL;
+
+	/* Try each prefix to find the BLS entry file */
+	i = 0;
+	do {
+		prefix = prefixes ? prefixes[i] : NULL;
+		log_debug("trying prefix %s\n", prefix);
+
+		ret = bootmeth_try_file(bflow, desc, prefix, BLS_ENTRY_FILE);
+	} while (ret && prefixes && prefixes[++i]);
+
+	if (ret) {
+		log_debug("no BLS entry file found\n");
+		return log_msg_ret("try", ret);
+	}
+
+	size = bflow->size;
+
+	/* Read the file */
+	ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN,
+				  BFI_BLS_CFG);
+	if (ret)
+		return log_msg_ret("read", ret);
+
+	ret = bls_entry_init(&entry, bflow, size);
+	bls_entry_uninit(&entry);
+
+	return ret;
+}
+
+/**
+ * bls_load_files() - Load files using an existing label
+ *
+ * @dev: Bootmethod device
+ * @bflow: Bootflow to load files for
+ * @pxe_ctx: Returns initialized PXE context (caller must destroy)
+ * @label: PXE label to use for loading
+ * Return: 0 on success, -ve on error
+ */
+static int bls_load_files(struct udevice *dev, struct bootflow *bflow,
+			  struct pxe_context *pxe_ctx,
+			  struct pxe_label *label)
+{
+	const struct bootflow_img *first_img;
+	struct bls_info info;
+	struct pxe_file *file;
+	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);
+	already_loaded = first_img && first_img->addr;
+
+	/* Set up PXE context */
+	info.dev = dev;
+	info.bflow = bflow;
+	ret = pxe_setup_ctx(pxe_ctx, bls_getfile, &info, true, bflow->fname,
+			    false, false, bflow);
+	if (ret)
+		return log_msg_ret("ctx", ret);
+
+	if (!already_loaded) {
+		/* Load files (kernel, initrd, FDT) */
+		ret = pxe_load_files(pxe_ctx, label, NULL);
+		if (ret) {
+			pxe_destroy_ctx(pxe_ctx);
+			return log_msg_ret("load", ret);
+		}
+
+		/* Update loaded images with their addresses */
+		alist_for_each(file, &label->files) {
+			struct bootflow_img *img;
+
+			/* Find the corresponding image in bootflow */
+			alist_for_each(img, &bflow->images) {
+				if (!strcmp(img->fname, file->path)) {
+					img->addr = file->addr;
+					img->size = file->size;
+					break;
+				}
+			}
+		}
+	}
+
+	/* Process FDT (apply overlays, etc.) */
+	ret = pxe_setup_label(pxe_ctx, label);
+	if (ret) {
+		pxe_destroy_ctx(pxe_ctx);
+		return log_msg_ret("setup", ret);
+	}
+
+	return 0;
+}
+
+/**
+ * bls_load_all() - Load all files needed for boot
+ *
+ * @dev: Bootmethod device
+ * @bflow: Bootflow to load files for
+ * @pxe_ctx: Returns initialized PXE context (caller must destroy)
+ * @labelp: Returns PXE label (caller must destroy)
+ * Return: 0 on success, -ve on error
+ */
+static int bls_load_all(struct udevice *dev, struct bootflow *bflow,
+			struct pxe_context *pxe_ctx,
+			struct pxe_label **labelp)
+{
+	struct pxe_label *label;
+	int ret;
+
+	/* Convert bootflow to PXE label for boot execution */
+	ret = bls_to_pxe_label(bflow, &label);
+	if (ret)
+		return log_msg_ret("label", ret);
+
+	ret = bls_load_files(dev, bflow, pxe_ctx, label);
+	if (ret) {
+		label_destroy(label);
+		return ret;
+	}
+
+	*labelp = label;
+
+	return 0;
+}
+
+static int __maybe_unused bls_read_all(struct udevice *dev,
+				       struct bootflow *bflow)
+{
+	struct pxe_context pxe_ctx;
+	struct pxe_label *label;
+	int ret;
+
+	ret = bls_load_all(dev, bflow, &pxe_ctx, &label);
+	if (ret)
+		return ret;
+
+	pxe_destroy_ctx(&pxe_ctx);
+	label_destroy(label);
+
+	return 0;
+}
+
+static int bls_boot(struct udevice *dev, struct bootflow *bflow)
+{
+	struct pxe_context pxe_ctx;
+	struct pxe_label *label;
+	int ret;
+
+	ret = bls_load_all(dev, bflow, &pxe_ctx, &label);
+	if (ret)
+		return ret;
+
+	/* Boot the label */
+	pxe_ctx.label = label;
+	ret = pxe_boot(&pxe_ctx);
+
+	/* Cleanup */
+	pxe_destroy_ctx(&pxe_ctx);
+	label_destroy(label);
+
+	return log_msg_ret("boot", ret);
+}
+
+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) Type #1" : "bls";
+
+	return 0;
+}
+
+static struct bootmeth_ops bls_bootmeth_ops = {
+	.get_state_desc	= bls_get_state_desc,
+	.check		= bls_check,
+	.read_bootflow	= bls_read_bootflow,
+	.read_file	= bootmeth_common_read_file,
+#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
+	.read_all	= bls_read_all,
+#endif
+	.boot		= bls_boot,
+};
+
+static const struct udevice_id bls_bootmeth_ids[] = {
+	{ .compatible = "u-boot,boot-loader-specification" },
+	{ }
+};
+
+/* Put a number before 'bls' to provide a default ordering */
+U_BOOT_DRIVER(bootmeth_2bls) = {
+	.name		= "bootmeth_bls",
+	.id		= UCLASS_BOOTMETH,
+	.of_match	= bls_bootmeth_ids,
+	.ops		= &bls_bootmeth_ops,
+	.bind		= bls_bootmeth_bind,
+};
diff --git a/doc/usage/bls.rst b/doc/usage/bls.rst
new file mode 100644
index 00000000000..5f5f0efb8a4
--- /dev/null
+++ b/doc/usage/bls.rst
@@ -0,0 +1,128 @@ 
+.. SPDX-License-Identifier: GPL-2.0+
+
+Boot Loader Specification (BLS) Type #1 Support
+================================================
+
+U-Boot supports Boot Loader Specification (BLS) Type #1 boot entries as defined
+in the `Boot Loader Specification`_.
+
+.. _Boot Loader Specification: https://uapi-group.org/specifications/specs/boot_loader_specification/
+
+Overview
+--------
+
+BLS provides a standardised way to describe boot entries. U-Boot's BLS support
+allows it to boot operating systems configured with BLS entries, which is used
+by Fedora, RHEL, and other distributions.
+
+The current implementation supports a single BLS entry file at
+``loader/entry.conf``. Future versions may support multiple entries in
+``loader/entries/``.
+
+Configuration
+-------------
+
+Enable BLS support with::
+
+    CONFIG_BOOTMETH_BLS=y
+
+This automatically selects ``CONFIG_PXE_UTILS`` for boot execution.
+
+BLS Entry Format
+----------------
+
+BLS entries use a simple key-value format, one field per line. Lines starting
+with ``#`` are comments. Example::
+
+    title Fedora Linux 39
+    version 6.7.0-1.fc39.x86_64
+    options root=/dev/sda3 ro quiet
+    linux /vmlinuz-6.7.0-1.fc39.x86_64
+    initrd /initramfs-6.7.0-1.fc39.x86_64.img
+    devicetree /dtbs/6.7.0-1.fc39.x86_64/board.dtb
+
+Supported Fields
+----------------
+
+**Required (at least one):**
+
+* ``linux`` - Path to Linux kernel image (Type #1); supports FITs with
+  ``path#config`` syntax
+
+**Optional:**
+
+* ``title`` - Human-readable menu display name
+* ``version`` - OS version identifier (parsed but not used for sorting)
+* ``options`` - Kernel command line parameters (may appear multiple times; all
+  occurrences are concatenated)
+* ``initrd`` - Initial ramdisk path (may appear multiple times, but only first
+  is used due to PXE limitation)
+* ``devicetree`` - Device tree blob path
+* ``devicetree-overlay`` - Device tree overlays (parsed but not yet supported)
+* ``architecture`` - Target architecture (parsed but not used for filtering)
+* ``machine-id`` - OS identifier (parsed but not used for filtering)
+* ``sort-key`` - Primary sorting key (parsed but not used for sorting)
+
+**Not supported (out of scope for Type #1):**
+
+* ``efi`` - EFI program path (Type #2/UKI)
+* ``uki`` - Unified Kernel Image path
+* ``uki-url`` - Remote UKI reference
+* ``profile`` - Multi-profile UKI selector
+
+FIT Support
+-----------
+
+U-Boot's BLS implementation works seamlessly with FITs using the standard
+``path#config`` syntax in the ``linux`` field::
+
+    linux /boot/image.fit#config-1
+
+The PXE boot infrastructure handles FIT parsing automatically.
+
+Multiple Values
+---------------
+
+Fields that support multiple occurrences:
+
+* ``options`` - All values are concatenated with spaces
+* ``initrd`` - Multiple paths can be specified, but only the first is used
+  (limitation of PXE boot infrastructure)
+
+Usage
+-----
+
+BLS boot entries are discovered automatically during standard boot::
+
+    => bootflow scan
+    => bootflow list
+    => bootflow select 0
+    => bootflow boot
+
+The BLS entry at ``loader/entry.conf`` is discovered as a bootflow.
+
+Implementation Notes
+--------------------
+
+* Single BLS entry file support (``loader/entry.conf``)
+* Boot execution reuses U-Boot's PXE infrastructure for kernel loading
+* Unknown fields are ignored for forward compatibility
+* The bootmethod is ordered as ``bootmeth_2bls`` (after extlinux)
+* Zero-copy parsing: most fields point into bootflow buffer (except ``options``
+  which is allocated for concatenation)
+
+Current Limitations
+-------------------
+
+* Only single entry file, not multiple entries directory scanning
+* Only first initrd used (PXE infrastructure limitation)
+* No devicetree-overlay support
+* No architecture/machine-id filtering
+* No version-based or sort-key sorting
+* No UKI/Type #2 support
+
+See Also
+--------
+
+* doc/develop/bootstd.rst - Standard boot framework
+* doc/usage/cmd/bootflow.rst - Bootflow command reference
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index 1e0ffacebaf..4e2089389f0 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -6,6 +6,7 @@  Use U-Boot
 
    spl_boot
    blkmap
+   bls
    console
    dfu
    environment
diff --git a/include/bootflow.h b/include/bootflow.h
index be94fba80d3..6c6f07db97d 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -135,6 +135,7 @@  struct bootflow {
  * bootflow_img[]
  *
  * @BFI_EXTLINUX_CFG: extlinux configuration-file
+ * @BFI_BLS_CFG: Boot Loader Specification (BLS) configuration-file
  * @BFI_LOGO: logo image
  * @BFI_EFI: EFI PE image
  * @BFI_CMDLINE: OS command-line string
@@ -145,6 +146,7 @@  struct bootflow {
 enum bootflow_img_t {
 	BFI_FIRST = IH_TYPE_COUNT,
 	BFI_EXTLINUX_CFG = BFI_FIRST,
+	BFI_BLS_CFG,
 	BFI_LOGO,
 	BFI_EFI,
 	BFI_CMDLINE,