[PATCH 0/8] Add BLS Type #1 bootmethod with FIT and multi-initrd support
From: Simon Glass <simon.glass@canonical.com> Add a Boot Loader Specification (BLS) Type #1 bootmethod, enabling U-Boot to discover and boot from BLS entry files. This is used by Fedora, RHEL and other distributions. The series also extends PXE infrastructure to support multiple initrd files using an alist, with LMB-based memory allocation for placing subsequent initrds. The sysboot command gains the same LMB allocation support. Finally, a 'fit' field is added as a U-Boot extension to the BLS format, allowing entries to explicitly specify a FIT image path as an alternative to the 'linux' field. It also includes a fix for a long-standing linker-list bug, as well as a recently introduced Kconfig warning. Simon Glass (8): dm: Fix linker list alignment for ll_entry_get() part_find: Fix PARTITION_TYPE_GUID dependency warning boot: Add BLS Type #1 entry parser boot: Add BLS Type #1 bootmethod test: Add bootflow test for BLS bootmethod pxe: Support multiple initrd files using alist bls: Add 'fit' field for FIT image support doc: bls: Update for FIT and multi-initrd support arch/sandbox/dts/test.dts | 7 + boot/Kconfig | 38 ++++ boot/Makefile | 1 + boot/bls_parse.c | 303 +++++++++++++++++++++++++ boot/bootflow.c | 1 + boot/bootmeth_bls.c | 457 ++++++++++++++++++++++++++++++++++++++ boot/pxe_parse.c | 64 +++++- boot/pxe_utils.c | 114 ++++++++-- cmd/Kconfig | 2 +- doc/usage/bls.rst | 158 +++++++++++++ doc/usage/index.rst | 1 + include/bls.h | 103 +++++++++ include/bootflow.h | 2 + include/linker_lists.h | 3 +- include/pxe_utils.h | 5 +- test/boot/Makefile | 1 + test/boot/bls_parse.c | 200 +++++++++++++++++ test/boot/bootflow.c | 54 +++++ test/boot/pxe.c | 12 +- test/py/img/bls.py | 70 ++++++ test/py/tests/test_ut.py | 2 + 21 files changed, 1564 insertions(+), 34 deletions(-) create mode 100644 boot/bls_parse.c create mode 100644 boot/bootmeth_bls.c create mode 100644 doc/usage/bls.rst create mode 100644 include/bls.h create mode 100644 test/boot/bls_parse.c create mode 100644 test/py/img/bls.py -- 2.43.0 base-commit: d81d158208c9baf4ad093b3c8d74c37f84f6f268 branch: boob
From: Simon Glass <simon.glass@canonical.com> The extern declaration in ll_entry_get() lacks the __aligned(4) attribute present in ll_entry_declare(). When the compiler sees an unaligned extern reference to a linker list entry in the same compilation unit as its definition, it may increase the section alignment beyond the expected struct size. This causes gaps in the linker list array, which the alignment checker reports as failures. For example, sandbox_dir is both defined and referenced via DM_DRIVER_GET() in sandboxfs.c. The compiler applies 32-byte alignment to its section instead of the 4-byte alignment from the definition, creating an 8-byte gap before it in the driver list. Add __aligned(4) to the extern declaration in ll_entry_get() to match ll_entry_declare() Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linker_lists.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linker_lists.h b/include/linker_lists.h index 6a018f175ca..470dcfc621a 100644 --- a/include/linker_lists.h +++ b/include/linker_lists.h @@ -284,7 +284,8 @@ */ #define ll_entry_get(_type, _name, _list) \ ({ \ - extern _type _u_boot_list_2_##_list##_2_##_name; \ + extern _type _u_boot_list_2_##_list##_2_##_name \ + __aligned(4); \ _type *_ll_result = \ &_u_boot_list_2_##_list##_2_##_name; \ _ll_result; \ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> CMD_PART_FIND selects PARTITION_TYPE_GUID which depends on EFI_PARTITION, causing an unmet-dependency warning when EFI_PARTITION is not enabled. Since part_find searches by type GUID, which is a GPT concept, change the dependency from PARTITIONS to EFI_PARTITION. This satisfies the PARTITION_TYPE_GUID dependency and makes sense, since EFI_PARTITION already selects PARTITIONS Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/Kconfig b/cmd/Kconfig index 243fa55677b..09fc13626bd 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1637,7 +1637,7 @@ config CMD_PART config CMD_PART_FIND bool "part_find" - depends on PARTITIONS + depends on EFI_PARTITION default y if SANDBOX || EFI_APP select HAVE_BLOCK_DEVICE select PARTITION_UUIDS -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a parser for Boot Loader Specification (BLS) Type #1 entry files, which use a simple key-value format with one field per line. The parser modifies the input buffer in place, adding null terminators to delimit fields. Entry structure fields (except options) point directly into the caller's buffer, which must remain valid for the lifetime of the entry. The options field is allocated separately to support concatenation of multiple option lines. Supported fields: - title: Human-readable entry name - version: OS version string - linux: Kernel path (required) - options: Kernel command line (concatenated if multiple) - initrd: Initramfs paths (can appear multiple times) - devicetree: Device tree blob path - devicetree-overlay: Device tree overlay path - architecture: Target architecture - machine-id: OS identifier - sort-key: Sorting identifier Unknown fields are ignored for forward compatibility. Comments (lines starting with #) and blank lines are skipped. The implementation includes comprehensive unit tests covering basic parsing, multiple options/initrds, error handling, and edge cases. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/Kconfig | 21 +++ boot/Makefile | 1 + boot/bls_parse.c | 297 ++++++++++++++++++++++++++++++++++++++++++ include/bls.h | 100 ++++++++++++++ test/boot/Makefile | 1 + test/boot/bls_parse.c | 176 +++++++++++++++++++++++++ 6 files changed, 596 insertions(+) create mode 100644 boot/bls_parse.c create mode 100644 include/bls.h create mode 100644 test/boot/bls_parse.c diff --git a/boot/Kconfig b/boot/Kconfig index 63f7d228ea8..63a0cf0bd7a 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -690,6 +690,27 @@ config BOOTMETH_EXTLINUX_LOCALBOOT This attempts to find a kernel and initrd on the disk and boot it, in the case where there is no "localcmd" in the environment. +config BOOTMETH_BLS + bool "Bootdev support for Boot Loader Specification (BLS) Type #1" + select PXE_UTILS + default y if SANDBOX + help + Enables support for Boot Loader Specification (BLS) Type #1 entries. + This makes bootdevs look for '*.conf' files in 'loader/entries/' + directories on each filesystem they scan. + + BLS is a standard for boot entry configuration used by Fedora, RHEL, + and other distributions. Each .conf file describes a single boot + entry with fields like kernel path, initrd, and boot options. + + The specification is here: + + https://uapi-group.org/specifications/specs/boot_loader_specification/ + + This supports standard BLS fields (title, linux, initrd, options, + devicetree) and works with U-Boot FIT images using the path#config + syntax. + config BOOTMETH_EFI bool "Bootdev support for EFI boot" depends on EFI_BINARY_EXEC diff --git a/boot/Makefile b/boot/Makefile index b2a475d4917..1e75f7ece79 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -29,6 +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_EFI) += bootmeth_efi.o obj-$(CONFIG_$(PHASE_)BOOTMETH_CROS) += bootmeth_cros.o obj-$(CONFIG_$(PHASE_)BOOTMETH_FEL) += bootmeth_fel.o diff --git a/boot/bls_parse.c b/boot/bls_parse.c new file mode 100644 index 00000000000..9a067736655 --- /dev/null +++ b/boot/bls_parse.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Boot Loader Specification (BLS) Type #1 parser + * + * Copyright 2026 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <bls.h> +#include <log.h> +#include <malloc.h> +#include <linux/ctype.h> +#include <linux/string.h> + +/** + * enum bls_token_t - BLS Type #1 field tokens + * + * Token identifiers for BLS entry fields. Using an enum avoids repeated + * string comparisons during parsing. + * + * Note: The enum values correspond to indices in bls_token_names[] + */ +enum bls_token_t { + TOK_TITLE = 0, + TOK_VERSION, + TOK_LINUX, + TOK_OPTIONS, + TOK_INITRD, + TOK_DEVICETREE, + TOK_DEVICETREE_OVERLAY, + TOK_ARCHITECTURE, + TOK_MACHINE_ID, + TOK_SORT_KEY, + + TOK_COUNT, +}; + +/* BLS field names indexed by enum bls_token_t */ +static const char *const bls_token_names[] = { + [TOK_TITLE] = "title", + [TOK_VERSION] = "version", + [TOK_LINUX] = "linux", + [TOK_OPTIONS] = "options", + [TOK_INITRD] = "initrd", + [TOK_DEVICETREE] = "devicetree", + [TOK_DEVICETREE_OVERLAY] = "devicetree-overlay", + [TOK_ARCHITECTURE] = "architecture", + [TOK_MACHINE_ID] = "machine-id", + [TOK_SORT_KEY] = "sort-key", +}; + +/** + * bls_lookup_token() - Look up a token by name + * + * @key: Field name to look up + * Return: Token enum value, or TOK_COUNT if not recognized + */ +static enum bls_token_t bls_lookup_token(const char *key) +{ + int i; + + for (i = 0; i < TOK_COUNT; i++) { + if (!strcmp(key, bls_token_names[i])) + return i; + } + + return TOK_COUNT; +} + +/** + * bls_append_str() - Append a string to an existing field + * + * Used for fields that can appear multiple times (e.g., options). + * Concatenates with a space separator. + * + * @fieldp: Pointer to field to append to (may be NULL) + * @value: String to append + * Return: 0 on success, -ENOMEM if allocation fails + */ +static int bls_append_str(char **fieldp, const char *value) +{ + size_t old_len, val_len, new_len; + char *new_str; + + if (!*fieldp) { + *fieldp = strdup(value); + return *fieldp ? 0 : -ENOMEM; + } + + old_len = strlen(*fieldp); + val_len = strlen(value); + new_len = old_len + 1 + val_len + 1; /* +1 for space, +1 for nul */ + + new_str = realloc(*fieldp, new_len); + if (!new_str) + return -ENOMEM; + + new_str[old_len] = ' '; + memcpy(new_str + old_len + 1, value, val_len + 1); + + *fieldp = new_str; + + return 0; +} + +/** + * bls_skip_whitespace() - Skip leading whitespace + * + * @strp: Pointer to string pointer (updated to first non-whitespace char) + */ +static void bls_skip_whitespace(char **strp) +{ + char *p = *strp; + + while (*p && isspace(*p)) + p++; + *strp = p; +} + +/** + * bls_parse_line() - Parse a single line from a BLS entry file + * + * Parses one line of a BLS entry. Lines are in "key value" format where + * the key and value are separated by whitespace. The value extends to + * the end of the line. + * + * @line: Line to parse (will be modified) + * @keyp: Returns pointer to key string + * @valuep: Returns pointer to value string + * Return: 0 on success, -EINVAL if line is invalid + */ +static int bls_parse_line(char *line, char **keyp, char **valuep) +{ + char *p = line; + char *key, *value; + + /* Skip leading whitespace */ + bls_skip_whitespace(&p); + + /* Skip blank lines and comments */ + if (!*p || *p == '#') + return -EINVAL; + + /* Extract key */ + key = p; + while (*p && !isspace(*p)) + p++; + if (!*p) + return -EINVAL; /* No value */ + + *p++ = '\0'; + + /* Skip whitespace before value */ + bls_skip_whitespace(&p); + if (!*p) + return -EINVAL; /* No value */ + + value = p; + + /* Remove trailing whitespace from value */ + p = value + strlen(value) - 1; + while (p >= value && isspace(*p)) + *p-- = '\0'; + + *keyp = key; + *valuep = value; + + return 0; +} + +int bls_parse_entry(const char *buf, size_t size, struct bls_entry *entry) +{ + char *data = (char *)buf; + char *line, *next; + bool err = false; + + /* Initialize entry to zero */ + memset(entry, '\0', sizeof(*entry)); + + /* Initialize initrds list */ + alist_init_struct(&entry->initrds, char *); + + log_debug("parsing BLS entry, size %zx\n", size); + + /* Parse buffer line by line, modifying it in place */ + line = data; + while (line < data + size) { + enum bls_token_t token; + char *key, *value; + int ret; + + /* Find end of line */ + next = memchr(line, '\n', data + size - line); + if (next) { + *next = '\0'; + next++; + } else { + next = data + size; + } + + ret = bls_parse_line(line, &key, &value); + if (ret) { + line = next; + continue; /* Skip blank lines and comments */ + } + + log_debug("BLS field: '%s' = '%s'\n", key, value); + line = next; + + /* Look up token and parse supported fields */ + token = bls_lookup_token(key); + switch (token) { + case TOK_TITLE: + /* Point into buffer */ + entry->title = value; + break; + case TOK_VERSION: + /* Point into buffer */ + entry->version = value; + break; + case TOK_LINUX: + /* Point into buffer */ + entry->kernel = value; + break; + case TOK_OPTIONS: + /* Multiple times - allocate and concatenate */ + if (bls_append_str(&entry->options, value)) + err = true; + break; + case TOK_INITRD: + /* Multiple times - add pointer to buffer */ + if (!alist_add(&entry->initrds, value)) + err = true; + break; + case TOK_DEVICETREE: + /* Point into buffer */ + entry->devicetree = value; + break; + case TOK_DEVICETREE_OVERLAY: + /* Point into buffer */ + entry->dt_overlays = value; + break; + case TOK_ARCHITECTURE: + /* Point into buffer */ + entry->architecture = value; + break; + case TOK_MACHINE_ID: + /* Point into buffer */ + entry->machine_id = value; + break; + case TOK_SORT_KEY: + /* Point into buffer */ + entry->sort_key = value; + break; + default: + /* Ignore unknown fields for forward compatibility */ + log_debug("Ignoring unknown BLS field: %s\n", key); + break; + } + } + + /* Check for errors during parsing */ + if (err) + return -ENOMEM; + + /* + * Validate required fields: BLS spec requires at least one of + * 'linux' or 'efi'. We only support 'linux' for Type #1 entries. + */ + if (!entry->kernel) { + log_err("BLS entry missing required 'linux' field\n"); + return -EINVAL; + } + + return 0; +} + +void bls_entry_uninit(struct bls_entry *entry) +{ + if (!entry) + return; + + /* + * Most fields point into the parsed buffer and don't need freeing. + * Only options is allocated (for concatenation of multiple lines). + */ + free(entry->options); + + /* + * Uninit initrds list. The strings in the list point into the buffer, + * so we don't free them, just the list structure. + */ + alist_uninit(&entry->initrds); +} + diff --git a/include/bls.h b/include/bls.h new file mode 100644 index 00000000000..eb32b323f1a --- /dev/null +++ b/include/bls.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Boot Loader Specification (BLS) Type #1 support + * + * Copyright 2026 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#ifndef __BLS_H +#define __BLS_H + +#include <alist.h> +#include <linux/types.h> + +/** + * struct bls_entry - represents a single BLS boot entry + * + * This structure holds the parsed fields from a BLS Type #1 entry file. + * BLS entries use a simple key-value format with one field per line. + * + * Most fields point directly into the parsed buffer and are only valid while + * the buffer remains valid. The exception is @options which is allocated + * because multiple option lines must be concatenated. + * + * @title: Human-readable name (points into buffer) + * @version: OS version string (points into buffer) + * @kernel: Kernel path or FIT image - required (points into buffer) + * Can include FIT config syntax: path#config + * @options: Kernel command line - ALLOCATED, must be freed + * Multiple options lines are concatenated with spaces + * @initrds: List of initrd paths (alist of char * pointing into buffer) + * Multiple initrd lines are supported and accumulated + * @devicetree: Device tree blob path (points into buffer) + * @dt_overlays: Device tree overlays (points into buffer) + * @architecture: Target architecture (points into buffer) + * @machine_id: OS identifier (points into buffer) + * @sort_key: Sorting identifier (points into buffer) + * @filename: Path to .conf file (points into buffer) + */ +struct bls_entry { + char *title; + char *version; + char *kernel; + char *options; /* Allocated */ + struct alist initrds; /* list of char * into buffer */ + char *devicetree; + char *dt_overlays; + char *architecture; + char *machine_id; + char *sort_key; + char *filename; +}; + +/** + * bls_parse_entry() - Parse a BLS entry file + * + * Parses the contents of a BLS Type #1 entry file into a pre-allocated + * entry structure. The format is simple key-value pairs with one field per + * line. Lines starting with '#' are comments and blank lines are ignored. + * + * The entry is initialized to zero before parsing. Most entry fields will + * point directly into the buffer (which is modified to add null terminators). + * The buffer must remain valid for the lifetime of the entry. Only the + * 'options' field is allocated separately because multiple option lines must + * be concatenated. + * + * The caller must call bls_entry_uninit() on the entry when done, regardless + * of whether this function succeeds or fails, to free any allocated memory. + * + * Supported fields: + * title - Human-readable name + * version - OS version string + * linux - Kernel path (required) + * options - Kernel command line (allocated, can appear multiple times) + * initrd - Initramfs path (can appear multiple times) + * devicetree - Device tree blob path + * + * Unknown fields are ignored for forward compatibility. + * + * @buf: Buffer containing the BLS entry file contents (will be modified) + * @size: Size of the buffer in bytes + * @entry: BLS entry structure to fill in (will be initialized) + * Return: 0 on success, -ENOMEM if out of memory, -EINVAL if required fields + * are missing + */ +int bls_parse_entry(const char *buf, size_t size, struct bls_entry *entry); + + +/** + * bls_entry_uninit() - Clean up a BLS entry's fields + * + * Frees all allocated fields within the entry but does not free the entry + * structure itself. Use this for stack-allocated entries. + * + * @entry: Entry to clean up + */ +void bls_entry_uninit(struct bls_entry *entry); + + +#endif /* __BLS_H */ diff --git a/test/boot/Makefile b/test/boot/Makefile index 329f4acbd52..a57e8553b2e 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -4,6 +4,7 @@ ifdef CONFIG_UT_BOOTSTD obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o +obj-$(CONFIG_BOOTMETH_BLS) += bls_parse.o obj-$(CONFIG_CMDLINE) += pxe.o obj-$(CONFIG_FIT) += image.o obj-$(CONFIG_$(PHASE_)FIT_PRINT) += fit_print.o diff --git a/test/boot/bls_parse.c b/test/boot/bls_parse.c new file mode 100644 index 00000000000..01da816aee8 --- /dev/null +++ b/test/boot/bls_parse.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for Boot Loader Specification (BLS) parser + * + * Copyright 2026 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#include <bls.h> +#include <test/ut.h> + +/* Test basic BLS entry parsing */ +static int bls_test_parse_basic(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title Test Entry\n" + "version 1.0\n" + "linux /vmlinuz\n" + "options root=/dev/sda\n" + "initrd /initrd.img\n" + "devicetree /test.dtb\n"; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq_str("Test Entry", entry.title); + ut_asserteq_str("1.0", entry.version); + ut_asserteq_str("/vmlinuz", entry.kernel); + ut_asserteq_str("root=/dev/sda", entry.options); + ut_asserteq(1, entry.initrds.count); + ut_asserteq_str("/test.dtb", entry.devicetree); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_basic, 0, bootstd); + +/* Test multiple options lines are concatenated */ +static int bls_test_parse_multi_options(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title Test\n" + "linux /vmlinuz\n" + "options root=/dev/sda\n" + "options quiet splash\n"; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq_str("root=/dev/sda quiet splash", entry.options); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_multi_options, 0, bootstd); + +/* Test multiple initrd lines */ +static int bls_test_parse_multi_initrd(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title Test\n" + "linux /vmlinuz\n" + "initrd /initrd1.img\n" + "initrd /initrd2.img\n"; + const char **initrd; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq(2, entry.initrds.count); + + initrd = alist_get(&entry.initrds, 0, char *); + ut_asserteq_str("/initrd1.img", *initrd); + + initrd = alist_get(&entry.initrds, 1, char *); + ut_asserteq_str("/initrd2.img", *initrd); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_multi_initrd, 0, bootstd); + +/* Test comments and blank lines are ignored */ +static int bls_test_parse_comments(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "# This is a comment\n" + "title Test\n" + "\n" + "# Another comment\n" + "linux /vmlinuz\n" + "\n"; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq_str("Test", entry.title); + ut_asserteq_str("/vmlinuz", entry.kernel); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_comments, 0, bootstd); + +/* Test missing required field returns error */ +static int bls_test_parse_missing_kernel(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title Test\n" + "options root=/dev/sda\n"; + + ut_asserteq(-EINVAL, bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_missing_kernel, 0, bootstd); + +/* Test unknown fields are ignored */ +static int bls_test_parse_unknown_field(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title Test\n" + "linux /vmlinuz\n" + "unknown_field some_value\n" + "another_unknown 123\n"; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq_str("Test", entry.title); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_unknown_field, 0, bootstd); + +/* Test all supported fields */ +static int bls_test_parse_all_fields(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title Full Test\n" + "version 2.0.1\n" + "linux /boot/vmlinuz-2.0.1\n" + "options root=/dev/sda1 ro\n" + "options quiet splash\n" + "initrd /boot/initrd-2.0.1.img\n" + "devicetree /boot/dtb-2.0.1.dtb\n" + "devicetree-overlay /boot/overlay.dtbo\n" + "architecture x86_64\n" + "machine-id abc123\n" + "sort-key 001\n"; + const char **initrd; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq_str("Full Test", entry.title); + ut_asserteq_str("2.0.1", entry.version); + ut_asserteq_str("/boot/vmlinuz-2.0.1", entry.kernel); + ut_asserteq_str("root=/dev/sda1 ro quiet splash", entry.options); + ut_asserteq(1, entry.initrds.count); + initrd = alist_get(&entry.initrds, 0, char *); + ut_asserteq_str("/boot/initrd-2.0.1.img", *initrd); + ut_asserteq_str("/boot/dtb-2.0.1.dtb", entry.devicetree); + ut_asserteq_str("/boot/overlay.dtbo", entry.dt_overlays); + ut_asserteq_str("x86_64", entry.architecture); + ut_asserteq_str("abc123", entry.machine_id); + ut_asserteq_str("001", entry.sort_key); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_all_fields, 0, bootstd); -- 2.43.0
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 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, -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that verifies the BLS bootmethod can discover and boot from a Boot Loader Specification entry file. The test creates a disk image (mmc15) with a BLS entry at loader/entry.conf and verifies that bootflow scan finds it and reports it correctly. Test coverage: - Disk image creation with BLS entry format - Bootflow discovery via 'bootflow scan' - Bootflow listing and info display - Integration with standard bootdev framework The disk image setup creates a minimal BLS Type #1 entry with kernel, initrd, devicetree, and boot options. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/sandbox/dts/test.dts | 7 ++++ test/boot/bootflow.c | 54 ++++++++++++++++++++++++++++++ test/py/img/bls.py | 70 +++++++++++++++++++++++++++++++++++++++ test/py/tests/test_ut.py | 2 ++ 4 files changed, 133 insertions(+) create mode 100644 test/py/img/bls.py diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 33c25014e94..90167567757 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1296,6 +1296,13 @@ filename = "mmc14.img"; }; + /* This is used for BLS tests */ + mmc15 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc15.img"; + }; + pch { compatible = "sandbox,pch"; }; diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index 9a9cc68fa11..e1e50319740 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -36,6 +36,7 @@ DECLARE_GLOBAL_DATA_PTR; extern U_BOOT_DRIVER(bootmeth_android); extern U_BOOT_DRIVER(bootmeth_cros); extern U_BOOT_DRIVER(bootmeth_2script); +extern U_BOOT_DRIVER(bootmeth_2bls); /* Use this as the vendor for EFI to tell the app to exit boot services */ static u16 __efi_runtime_data test_vendor[] = u"U-Boot testing"; @@ -684,6 +685,13 @@ static int prep_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev, "android", 0, ofnode_null(), &dev)); } + /* Enable the BLS bootmeth if needed */ + if (IS_ENABLED(CONFIG_BOOTMETH_BLS) && bind_cros_android) { + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + ut_assertok(device_bind(bootstd, DM_DRIVER_REF(bootmeth_2bls), + "bls", 0, ofnode_null(), &dev)); + } + /* Change the order to include the device */ std = dev_get_priv(bootstd); old_order = std->bootdev_order; @@ -1809,3 +1817,49 @@ static int bootflow_cmd_info_encrypted(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootflow_cmd_info_encrypted, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Check 'bootflow scan' finds a BLS bootflow */ +static int bootflow_cmd_bls(struct unit_test_state *uts) +{ + struct bootstd_priv *std; + const char **old_order; + + ut_assertok(prep_mmc_bootdev(uts, "mmc15", true, &old_order)); + ut_assertok(run_command("bootflow scan", 0)); + ut_assert_console_end(); + + /* Restore the order used by the device tree */ + ut_assertok(bootstd_get_priv(&std)); + free(std->bootdev_order); + std->bootdev_order = old_order; + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing all bootflows"); + 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_nextlinen("---"); + ut_assert_nextline("(2 bootflows, 2 valid)"); + ut_assert_console_end(); + + /* Select the BLS bootflow and check info */ + ut_assertok(run_command("bootflow select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow info", 0)); + ut_assert_nextline("Name: mmc15.bootdev.part_1"); + ut_assert_nextline("Device: mmc15.bootdev"); + ut_assert_nextline("Block dev: mmc15.blk"); + ut_assert_nextline("Method: bls"); + ut_assert_nextline("State: ready"); + ut_assert_nextline("Partition: 1"); + 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_skip_to_line("Error: 0"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_bls, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); diff --git a/test/py/img/bls.py b/test/py/img/bls.py new file mode 100644 index 00000000000..0f55aad8ba5 --- /dev/null +++ b/test/py/img/bls.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2026 Canonical Ltd + +"""Create BLS test disk images""" + +import gzip +import os + +import utils +from fs_helper import DiskHelper, FsHelper +from img.common import mkdir_cond + + +def setup_bls_image(config, log, devnum, basename): + """Create a 20MB BLS disk image with a single FAT partition + + Args: + config (ArbitraryAttributeContainer): Configuration + log (multiplexed_log.Logfile): Log to write to + devnum (int): Device number to use, e.g. 5 + basename (str): Base name to use in the filename, e.g. 'mmc' + """ + vmlinux = 'vmlinuz-6.8.0' + initrd = 'initrd.img-6.8.0' + dtb = 'sandbox.dtb' + + # BLS Type #1 entry format + script = f'''title Test Boot +version 6.8.0 +linux /{vmlinux} +options root=/dev/mmcblk0p2 ro quiet +initrd /{initrd} +devicetree /{dtb}''' + + fsh = FsHelper(config, 'vfat', 18, prefix=basename) + fsh.setup() + + # Create loader directory for BLS entry + loader = os.path.join(fsh.srcdir, 'loader') + mkdir_cond(loader) + + # Create BLS entry file + conf = os.path.join(loader, 'entry.conf') + with open(conf, 'w', encoding='ascii') as fd: + print(script, file=fd) + + # Create compressed kernel image + inf = os.path.join(config.persistent_data_dir, 'inf') + with open(inf, 'wb') as fd: + fd.write(gzip.compress(b'vmlinux')) + mkimage = config.build_dir + '/tools/mkimage' + utils.run_and_log_no_ubman( + log, f'{mkimage} -f auto -d {inf} {os.path.join(fsh.srcdir, vmlinux)}') + + # Create initrd file + with open(os.path.join(fsh.srcdir, initrd), 'w', encoding='ascii') as fd: + print('initrd', file=fd) + + # Create device tree blob + dtb_file = os.path.join(fsh.srcdir, dtb) + utils.run_and_log_no_ubman( + log, f'dtc -o {dtb_file}', stdin=b'/dts-v1/; / {};') + + fsh.mk_fs() + + # Create disk image with single bootable partition + img = DiskHelper(config, devnum, basename) + img.add_fs(fsh, DiskHelper.VFAT, bootable=True) + img.create() + fsh.cleanup() diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index da36898d803..e64ccd407a0 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -23,6 +23,7 @@ from img.common import mkdir_cond, copy_partition, setup_extlinux_image from img.fedora import setup_fedora_image from img.ubuntu import setup_ubuntu_image from img.armbian import setup_bootmenu_image +from img.bls import setup_bls_image from img.chromeos import setup_cros_image from img.android import setup_android_image from img.efi import setup_efi_image @@ -79,6 +80,7 @@ def test_ut_dm_init_bootstd(u_boot_config, u_boot_log): u_boot_log (multiplexed_log.Logfile): Log to write to """ setup_fedora_image(u_boot_config, u_boot_log, 1, 'mmc') + setup_bls_image(u_boot_config, u_boot_log, 15, 'mmc') setup_bootmenu_image(u_boot_config, u_boot_log) setup_cedit_file(u_boot_config, u_boot_log) setup_cros_image(u_boot_config, u_boot_log) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Replace the single char *initrd field in struct pxe_label with an alist of initrd paths (struct alist initrds) to support multiple initrd files. This enhancement enables BLS (Boot Loader Specification) support for multiple initrd lines, which is a key feature of the BLS format. Changes made: - Replace char *initrd with struct alist initrds in pxe_label - Initialize initrds alist in label_create() - Free initrds alist entries in label_destroy() - Update PXE parser to append multiple INITRD directives to the alist - Update pxe_load_files() to load all initrds consecutively in memory - Update BLS bootmeth to add all ramdisk images to initrds alist - Update tests to check initrds.count instead of checking initrd pointer The PXE file loading code now: - Loads each initrd file consecutively starting at ramdisk_addr_r - Tracks the total size of all initrds for bootm - Handles FIT images where initrd path matches kernel (shares address) This maintains backwards compatibility with existing single-initrd PXE files while enabling multi-initrd support for BLS and future use cases. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/Kconfig | 17 +++++++ boot/bootmeth_bls.c | 44 +++++++++++------ boot/pxe_parse.c | 64 ++++++++++++++++++++----- boot/pxe_utils.c | 114 ++++++++++++++++++++++++++++++++++++++------ include/pxe_utils.h | 5 +- test/boot/pxe.c | 12 +++-- 6 files changed, 209 insertions(+), 47 deletions(-) diff --git a/boot/Kconfig b/boot/Kconfig index 63a0cf0bd7a..cff58795bc1 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -469,6 +469,22 @@ config PXE_UTILS help Utilities for parsing PXE file formats. +config PXE_INITRD_LIST + bool "Support multiple initrd files in PXE/extlinux" + depends on PXE_UTILS + help + Enable support for multiple initrd files in PXE/extlinux + configurations. This allows loading multiple initrd images + consecutively in memory, as required by Boot Loader Specification + (BLS) Type #1 entries. + + When enabled, the PXE parser can handle multiple 'initrd' lines + and load all specified initrd files. When disabled, only a single + initrd is supported, reducing code size. + + Say Y if you need BLS support or multiple initrds. Say N to save + code space if you only use single initrd configurations. + config BOOT_DEFAULTS_FEATURES bool select SUPPORT_RAW_INITRD @@ -693,6 +709,7 @@ config BOOTMETH_EXTLINUX_LOCALBOOT config BOOTMETH_BLS bool "Bootdev support for Boot Loader Specification (BLS) Type #1" select PXE_UTILS + select PXE_INITRD_LIST default y if SANDBOX help Enables support for Boot Loader Specification (BLS) Type #1 entries. diff --git a/boot/bootmeth_bls.c b/boot/bootmeth_bls.c index c9a2afbb6f3..2c66e14c641 100644 --- a/boot/bootmeth_bls.c +++ b/boot/bootmeth_bls.c @@ -125,25 +125,39 @@ static int bls_to_pxe_label(struct bootflow *bflow, goto err; } - /* Extract kernel, initrd and FDT from the bootflow images */ + /* Extract kernel, initrds 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) { + char *fname; + + fname = strdup(img->fname); + if (!fname) { + ret = -ENOMEM; + goto err; + } + + switch ((int)img->type) { + case IH_TYPE_KERNEL: + if (!label->kernel) + label->kernel = fname; + else + free(fname); + break; + case IH_TYPE_RAMDISK: + if (!alist_add(&label->initrds, fname)) { + free(fname); ret = -ENOMEM; goto err; } + break; + case IH_TYPE_FLATDT: + if (!label->fdt) + label->fdt = fname; + else + free(fname); + break; + default: + free(fname); + break; } } diff --git a/boot/pxe_parse.c b/boot/pxe_parse.c index 42f9125cb1d..28f957cfa70 100644 --- a/boot/pxe_parse.c +++ b/boot/pxe_parse.c @@ -107,6 +107,8 @@ static struct pxe_label *label_create(void) return NULL; memset(label, 0, sizeof(struct pxe_label)); alist_init_struct(&label->files, struct pxe_file); + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) + alist_init(&label->initrds, sizeof(char *), 4); return label; } @@ -121,7 +123,15 @@ void label_destroy(struct pxe_label *label) free(label->kernel); free(label->config); free(label->append); - free(label->initrd); + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + const char **initrd; + + alist_for_each(initrd, &label->initrds) + free((void *)*initrd); + alist_uninit(&label->initrds); + } else { + free(label->initrd); + } free(label->fdt); free(label->fdtdir); alist_for_each(file, &label->files) @@ -579,6 +589,7 @@ static int parse_label(char **c, struct pxe_menu *cfg, const char *limit) struct token t; int len; char *s = *c; + char *initrd_path; struct pxe_label *label; int err; @@ -612,26 +623,57 @@ static int parse_label(char **c, struct pxe_menu *cfg, const char *limit) break; case T_APPEND: err = parse_sliteral(c, &label->append, limit); - if (label->initrd) - break; + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + if (label->initrds.count) + break; + } else { + if (label->initrd) + break; + } s = strstr(label->append, "initrd="); if (!s) break; s += 7; len = (int)(strchr(s, ' ') - s); - label->initrd = malloc(len + 1); - strlcpy(label->initrd, s, len); - label->initrd[len] = '\0'; + initrd_path = malloc(len + 1); + if (!initrd_path) { + err = -ENOMEM; + break; + } + strlcpy(initrd_path, s, len + 1); + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + if (!alist_add(&label->initrds, initrd_path)) { + free(initrd_path); + err = -ENOMEM; + break; + } + } else { + label->initrd = initrd_path; + } + err = label_add_file(label, initrd_path, PFT_INITRD); break; case T_INITRD: - if (!label->initrd) { - err = parse_sliteral(c, &label->initrd, limit); - if (err < 0) + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + if (label->initrds.count) + break; + } else { + if (label->initrd) break; - err = label_add_file(label, label->initrd, - PFT_INITRD); } + err = parse_sliteral(c, &initrd_path, limit); + if (err < 0) + break; + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + if (!alist_add(&label->initrds, initrd_path)) { + free(initrd_path); + err = -ENOMEM; + break; + } + } else { + label->initrd = initrd_path; + } + err = label_add_file(label, initrd_path, PFT_INITRD); break; case T_FDT: if (!label->fdt) { diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 5c1d08feebf..e5f1f3e46c2 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -622,11 +622,71 @@ static int label_run_boot(struct pxe_context *ctx, struct pxe_label *label, */ static int generate_localboot(struct pxe_label *label) { + char *initrd_path; + label->kernel = strdup("/vmlinuz"); label->kernel_label = strdup(label->kernel); - label->initrd = strdup("/initrd.img"); - if (!label->kernel || !label->kernel_label || !label->initrd) + initrd_path = strdup("/initrd.img"); + if (!label->kernel || !label->kernel_label || !initrd_path) return -ENOMEM; + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + if (!alist_add(&label->initrds, initrd_path)) { + free(initrd_path); + return -ENOMEM; + } + } else { + label->initrd = initrd_path; + } + + return 0; +} + +/** + * pxe_load_initrds() - Load all initrd files consecutively + * + * @ctx: PXE context + * @label: Label containing initrd file paths + * @initrd_addr: Starting address for first initrd + * @total_sizep: Returns total size of all initrds + * Return: 0 on success, -EIO on error + */ +static int pxe_load_initrds(struct pxe_context *ctx, struct pxe_label *label, + ulong initrd_addr, ulong *total_sizep) +{ + const char **initrd_path; + ulong total_size = 0; + int ret; + int i; + + /* Load each initrd consecutively */ + for (i = 0; i < label->initrds.count; i++) { + ulong size; + + initrd_path = alist_get(&label->initrds, i, char *); + /* Use ramdisk_addr_r for first, then append */ + if (i == 0) { + ret = get_relfile_envaddr(ctx, *initrd_path, + "ramdisk_addr_r", SZ_2M, + (enum bootflow_img_t)IH_TYPE_RAMDISK, + &initrd_addr, &size); + ctx->initrd_addr = initrd_addr; + } else { + /* Load subsequent initrds after the previous one */ + ulong addr = initrd_addr + total_size; + + ret = get_relfile_envaddr(ctx, *initrd_path, + NULL, SZ_2M, + (enum bootflow_img_t)IH_TYPE_RAMDISK, + &addr, &size); + } + if (ret < 0) { + printf("Skipping %s for failure retrieving initrd %s\n", + label->name, *initrd_path); + return -EIO; + } + total_size += size; + } + *total_sizep = total_size; return 0; } @@ -634,6 +694,9 @@ static int generate_localboot(struct pxe_label *label) int pxe_load_files(struct pxe_context *ctx, struct pxe_label *label, char *fdtfile) { + const char **initrd_path; + ulong initrd_addr = 0; + ulong total_size = 0; int ret; if (!label->kernel) { @@ -649,18 +712,38 @@ int pxe_load_files(struct pxe_context *ctx, struct pxe_label *label, return -EIO; } - /* For FIT, the label can be identical to kernel one */ - if (label->initrd && !strcmp(label->kernel_label, label->initrd)) { - ctx->initrd_addr = ctx->kern_addr; - } else if (label->initrd) { - ret = get_relfile_envaddr(ctx, label->initrd, "ramdisk_addr_r", - SZ_2M, - (enum bootflow_img_t)IH_TYPE_RAMDISK, - &ctx->initrd_addr, &ctx->initrd_size); - if (ret < 0) { - printf("Skipping %s for failure retrieving initrd\n", - label->name); - return -EIO; + /* Load initrds if present */ + if (IS_ENABLED(CONFIG_PXE_INITRD_LIST)) { + if (label->initrds.count) { + /* For FIT, check if first initrd is identical to kernel */ + initrd_path = alist_get(&label->initrds, 0, char *); + if (!strcmp(label->kernel_label, *initrd_path)) { + ctx->initrd_addr = ctx->kern_addr; + ctx->initrd_size = ctx->kern_size; + } else { + ret = pxe_load_initrds(ctx, label, initrd_addr, + &total_size); + if (ret) + return ret; + ctx->initrd_size = total_size; + } + } + } else { + if (label->initrd) { + if (!strcmp(label->kernel_label, label->initrd)) { + ctx->initrd_addr = ctx->kern_addr; + ctx->initrd_size = ctx->kern_size; + } else { + if (get_relfile_envaddr(ctx, label->initrd, + "ramdisk_addr_r", SZ_2M, + (enum bootflow_img_t)IH_TYPE_RAMDISK, + &ctx->initrd_addr, + &ctx->initrd_size) < 0) { + printf("Skipping %s for failure retrieving initrd\n", + label->name); + return -EIO; + } + } } } @@ -697,7 +780,8 @@ int pxe_load_label(struct pxe_context *ctx, struct pxe_label *label) if (label->localboot) { if (label->localboot_val >= 0) { - if (IS_ENABLED(CONFIG_BOOTMETH_EXTLINUX_LOCALBOOT)) { + if (IS_ENABLED(CONFIG_BOOTMETH_EXTLINUX_LOCALBOOT) && + !label->kernel) { ret = generate_localboot(label); if (ret) return ret; diff --git a/include/pxe_utils.h b/include/pxe_utils.h index 48d36bdd14c..3367230eb1c 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -39,7 +39,9 @@ * @kernel: the path to the kernel file to use for this label * @config: FIT configuration to use (after '#'), or NULL if none * @append: kernel command line to use when booting this label - * @initrd: path to the initrd to use for this label. + * @initrd: path to single initrd (used if !CONFIG_PXE_INITRD_LIST) + * @initrds: list of initrd paths (alist of char *) (used if + * CONFIG_PXE_INITRD_LIST) * @fdt: path to FDT to use * @fdtdir: path to FDT directory to use * @files: list of files to load (alist of struct pxe_file) @@ -60,6 +62,7 @@ struct pxe_label { char *config; char *append; char *initrd; + struct alist initrds; char *fdt; char *fdtdir; struct alist files; diff --git a/test/boot/pxe.c b/test/boot/pxe.c index f4a124eafda..cd831807b94 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -203,7 +203,9 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq_str("/vmlinuz", label->kernel); ut_assertnull(label->config); ut_asserteq_str("root=/dev/sda1 quiet", label->append); - ut_asserteq_str("/initrd.img", label->initrd); + ut_asserteq(1, label->initrds.count); + ut_asserteq_str("/initrd.img", + *alist_get(&label->initrds, 0, char *)); ut_asserteq_str("/dtb/board.dtb", label->fdt); ut_assertnull(label->fdtdir); ut_asserteq(5, label->files.count); @@ -233,7 +235,7 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq_str("/vmlinuz-rescue", label->kernel); ut_assertnull(label->config); ut_asserteq_str("single", label->append); - ut_assertnull(label->initrd); + ut_asserteq(0, label->initrds.count); ut_assertnull(label->fdt); ut_asserteq_str("/dtb/", label->fdtdir); ut_asserteq(1, label->files.count); @@ -255,7 +257,7 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_assertnull(label->kernel); ut_assertnull(label->config); ut_assertnull(label->append); - ut_assertnull(label->initrd); + ut_asserteq(0, label->initrds.count); ut_assertnull(label->fdt); ut_assertnull(label->fdtdir); ut_asserteq(0, label->files.count); @@ -275,7 +277,7 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq_str("/boot/image.fit", label->kernel); ut_asserteq_str("#config-1", label->config); ut_asserteq_str("console=ttyS0", label->append); - ut_assertnull(label->initrd); + ut_asserteq(0, label->initrds.count); ut_assertnull(label->fdt); ut_assertnull(label->fdtdir); ut_asserteq(1, label->files.count); @@ -297,7 +299,7 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq_str("/boot/included-kernel", label->kernel); ut_assertnull(label->config); ut_asserteq_str("root=/dev/sdb1", label->append); - ut_assertnull(label->initrd); + ut_asserteq(0, label->initrds.count); ut_assertnull(label->fdt); ut_assertnull(label->fdtdir); ut_asserteq(1, label->files.count); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The BLS parser only accepts 'linux' as the kernel field. This does not distinguish between a plain kernel image and a FIT image, making it harder for the bootmethod to handle them differently. Add a 'fit' field as an alternative to 'linux' for specifying a FIT image path. The parser now accepts either 'linux' or 'fit' as the required kernel field. When both are present, 'fit' takes priority in the bootmethod. Add a parser unit test for a fit-only entry. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bls_parse.c | 12 +++++++++--- boot/bootmeth_bls.c | 7 ++++++- include/bls.h | 7 +++++-- test/boot/bls_parse.c | 24 ++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/boot/bls_parse.c b/boot/bls_parse.c index 9a067736655..1e4f6330430 100644 --- a/boot/bls_parse.c +++ b/boot/bls_parse.c @@ -26,6 +26,7 @@ enum bls_token_t { TOK_TITLE = 0, TOK_VERSION, TOK_LINUX, + TOK_FIT, TOK_OPTIONS, TOK_INITRD, TOK_DEVICETREE, @@ -42,6 +43,7 @@ static const char *const bls_token_names[] = { [TOK_TITLE] = "title", [TOK_VERSION] = "version", [TOK_LINUX] = "linux", + [TOK_FIT] = "fit", [TOK_OPTIONS] = "options", [TOK_INITRD] = "initrd", [TOK_DEVICETREE] = "devicetree", @@ -224,6 +226,10 @@ int bls_parse_entry(const char *buf, size_t size, struct bls_entry *entry) /* Point into buffer */ entry->kernel = value; break; + case TOK_FIT: + /* Point into buffer */ + entry->fit = value; + break; case TOK_OPTIONS: /* Multiple times - allocate and concatenate */ if (bls_append_str(&entry->options, value)) @@ -267,10 +273,10 @@ int bls_parse_entry(const char *buf, size_t size, struct bls_entry *entry) /* * Validate required fields: BLS spec requires at least one of - * 'linux' or 'efi'. We only support 'linux' for Type #1 entries. + * 'linux' or 'efi'. We also accept 'fit' for FIT images. */ - if (!entry->kernel) { - log_err("BLS entry missing required 'linux' field\n"); + if (!entry->kernel && !entry->fit) { + log_err("BLS entry missing required 'linux' or 'fit' field\n"); return -EINVAL; } diff --git a/boot/bootmeth_bls.c b/boot/bootmeth_bls.c index 2c66e14c641..13353906fff 100644 --- a/boot/bootmeth_bls.c +++ b/boot/bootmeth_bls.c @@ -202,7 +202,12 @@ static int bls_entry_init(struct bls_entry *entry, struct bootflow *bflow, } /* Register discovered images (not yet loaded, addr=0) */ - if (entry->kernel) { + if (entry->fit) { + if (!bootflow_img_add(bflow, entry->fit, + (enum bootflow_img_t)IH_TYPE_KERNEL, + 0, 0)) + return log_msg_ret("imf", -ENOMEM); + } else if (entry->kernel) { if (!bootflow_img_add(bflow, entry->kernel, (enum bootflow_img_t)IH_TYPE_KERNEL, 0, 0)) diff --git a/include/bls.h b/include/bls.h index eb32b323f1a..e00b5998999 100644 --- a/include/bls.h +++ b/include/bls.h @@ -24,8 +24,9 @@ * * @title: Human-readable name (points into buffer) * @version: OS version string (points into buffer) - * @kernel: Kernel path or FIT image - required (points into buffer) + * @kernel: Kernel path - required unless @fit is set (points into buffer) * Can include FIT config syntax: path#config + * @fit: FIT image path - required unless @kernel is set (points into buffer) * @options: Kernel command line - ALLOCATED, must be freed * Multiple options lines are concatenated with spaces * @initrds: List of initrd paths (alist of char * pointing into buffer) @@ -41,6 +42,7 @@ struct bls_entry { char *title; char *version; char *kernel; + char *fit; char *options; /* Allocated */ struct alist initrds; /* list of char * into buffer */ char *devicetree; @@ -70,7 +72,8 @@ struct bls_entry { * Supported fields: * title - Human-readable name * version - OS version string - * linux - Kernel path (required) + * linux - Kernel path (required unless 'fit' is present) + * fit - FIT image path (required unless 'linux' is present) * options - Kernel command line (allocated, can appear multiple times) * initrd - Initramfs path (can appear multiple times) * devicetree - Device tree blob path diff --git a/test/boot/bls_parse.c b/test/boot/bls_parse.c index 01da816aee8..63ae60d6091 100644 --- a/test/boot/bls_parse.c +++ b/test/boot/bls_parse.c @@ -137,6 +137,30 @@ static int bls_test_parse_unknown_field(struct unit_test_state *uts) } UNIT_TEST(bls_test_parse_unknown_field, 0, bootstd); +/* Test FIT-only entry (no linux field) */ +static int bls_test_parse_fit(struct unit_test_state *uts) +{ + struct bls_entry entry; + char buf[] = + "title FIT Test\n" + "version 1.0\n" + "fit /boot/image.fit\n" + "options root=/dev/sda\n" + "initrd /initrd.img\n"; + + ut_assertok(bls_parse_entry(buf, sizeof(buf) - 1, &entry)); + ut_asserteq_str("FIT Test", entry.title); + ut_assertnull(entry.kernel); + ut_asserteq_str("/boot/image.fit", entry.fit); + ut_asserteq_str("root=/dev/sda", entry.options); + ut_asserteq(1, entry.initrds.count); + + bls_entry_uninit(&entry); + + return 0; +} +UNIT_TEST(bls_test_parse_fit, 0, bootstd); + /* Test all supported fields */ static int bls_test_parse_all_fields(struct unit_test_state *uts) { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update the BLS documentation to reflect recent changes: - Document the 'fit' field as a U-Boot extension for specifying FIT images explicitly - Update initrd to note that all paths are now loaded - Add references to the Unified Kernel Image spec for the unsupported EFI/UKI fields - Remove outdated "only first initrd" limitation Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- doc/usage/bls.rst | 54 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/doc/usage/bls.rst b/doc/usage/bls.rst index 5f5f0efb8a4..f96652bd242 100644 --- a/doc/usage/bls.rst +++ b/doc/usage/bls.rst @@ -46,8 +46,9 @@ Supported Fields **Required (at least one):** -* ``linux`` - Path to Linux kernel image (Type #1); supports FITs with +* ``linux`` - Path to Linux kernel image; supports FITs with ``path#config`` syntax +* ``fit`` - Path to FIT (U-Boot extension, not in the BLS spec) **Optional:** @@ -55,30 +56,59 @@ Supported Fields * ``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) +* ``initrd`` - Initial ramdisk path (may appear multiple times; all initrds + are loaded) * ``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):** +**Not supported:** -* ``efi`` - EFI program path (Type #2/UKI) +These fields relate to `Unified Kernel Images`_ (UKIs), which combine a UEFI +boot stub, kernel, initrd and other resources into a single UEFI PE file. They +are not currently supported by U-Boot: + +* ``efi`` - EFI program path * ``uki`` - Unified Kernel Image path * ``uki-url`` - Remote UKI reference * ``profile`` - Multi-profile UKI selector +.. _Unified Kernel Images: https://uapi-group.org/specifications/specs/unified_kernel_image/ + +U-Boot Extensions +----------------- + +The following fields are U-Boot extensions not defined in the BLS spec: + +* ``fit`` - Specifies a FIT path, as an alternative to ``linux``. When + ``fit`` is present it takes priority over ``linux``. This allows the entry to + explicitly indicate that the image is a FIT, rather than relying on the + ``path#config`` syntax in the ``linux`` field. + +Example:: + + title Ubuntu 24.04 + version 6.8.0 + fit /boot/ubuntu-6.8.0.fit + options root=/dev/sda3 ro quiet + initrd /boot/initrd-6.8.0.img + FIT Support ----------- -U-Boot's BLS implementation works seamlessly with FITs using the standard -``path#config`` syntax in the ``linux`` field:: +FITs can be specified in two ways: + +1. Using the ``linux`` field with ``path#config`` syntax:: linux /boot/image.fit#config-1 -The PXE boot infrastructure handles FIT parsing automatically. +2. Using the ``fit`` field (U-Boot extension):: + + fit /boot/image.fit + +The PXE boot infrastructure handles FIT parsing automatically in both cases. Multiple Values --------------- @@ -86,8 +116,7 @@ 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) +* ``initrd`` - All paths are loaded consecutively in memory Usage ----- @@ -115,11 +144,12 @@ 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 +* No `Unified Kernel Image`_ (UKI) support + +.. _Unified Kernel Image: https://uapi-group.org/specifications/specs/unified_kernel_image/ See Also -------- -- 2.43.0
participants (1)
-
Simon Glass