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