[PATCH 00/16] test: pxe: Add some decent tests for the PXE/extlinux parser
From: Simon Glass <simon.glass@canonical.com> This series adds unit tests and Python test wrappers for the PXE/extlinux configuration parser and associated logic. - Basic parsing of extlinux.conf files and label properties - The 'say' keyword for printing messages during parsing - Include directives and nested includes (up to MAX_NEST_LEVEL) - The sysboot command for booting via extlinux.conf - fdtdir path resolution using environment variables - FDT overlay loading and error handling - Helper functions (pxe_get_file_size, format_mac_pxe, get_pxelinux_path) - The pxe_timeout environment variable for menu timeout override - ipappend functionality for IP and MAC address appending - FDT fallback logic extraction - The pxe_label_override environment variable - LMB address allocation when no address env vars are set The tests use the usual hybrid approach: a Python fixtures to create filesystem images with extlinux.conf files and a C unit-test to perform the actual parsing and verification. This allows testing the parser APIs directly without needing to boot a real kernel. Simon Glass (16): buildman: Fix f-string in the delta column header test: pxe: Add unit tests for PXE/extlinux parser test: pxe: Add a test for the 'say' keyword boot: pxe: Stop after first boot attempt on sandbox test: pxe: Test include directive test: pxe: Add a test for nested include boot: pxe: Add a test for the sysboot command boot: pxe: Add a test for fdtdir path-resolution boot: pxe: Add a test for FDT error-handling test: pxe: Add a few tests for pxe functions test: pxe: Add a note about ontimeout keyword testing boot: pxe: Add a pxe_timeout env variable for menu timeout test: pxe: Add a test for the ipappend functionality boot: pxe: Extract FDT fallback logic and add test test: pxe: Add test for tpxe_label_override test: pxe: Add test for the address-allocation path boot/pxe_utils.c | 70 ++- include/pxe_utils.h | 14 + test/boot/Makefile | 1 + test/boot/pxe.c | 924 +++++++++++++++++++++++++++++++ test/cmd_ut.c | 2 + test/py/tests/test_pxe_parser.py | 450 +++++++++++++++ tools/buildman/builder.py | 2 +- 7 files changed, 1437 insertions(+), 26 deletions(-) create mode 100644 test/boot/pxe.c create mode 100644 test/py/tests/test_pxe_parser.py -- 2.43.0 base-commit: 99b8ca833994961f1b9f9473419a000f12599ba5 branch: fitc
From: Simon Glass <simon.glass@canonical.com> The '+' sign format specifier is only valid for numeric types to force showing the sign. Using it with a string literal causes a ValueError. Remove the invalid '+' from the "delta" column header format specifier. Fixes: 34567e06b6a7 ("buildman: Convert to f-strings in builder.py") Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/buildman/builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py index d92748b01e9..b7343b57c0d 100644 --- a/tools/buildman/builder.py +++ b/tools/buildman/builder.py @@ -1219,7 +1219,7 @@ class Builder: f'{args[0]}/{args[1]}, grow: {args[2]}/{args[3]} bytes: ' f'{args[4]}/{args[5]} ({args[6]})') tprint(f'{indent} {"function":<38s} {"old":>7s} {"new":>7s} ' - f'{"delta":>+7s}') + f'{"delta":>7s}') for diff, name in delta: if diff: color = self.col.RED if diff > 0 else self.col.GREEN -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add comprehensive tests for the PXE/extlinux.conf parser APIs. The tests verify that config files can be parsed and label properties inspected without loading kernel/initrd/FDT files. The C test (test/boot/pxe.c) validates all struct pxe_label fields: - String fields: name, menu, kernel, kernel_label, config, append, initrd, fdt, fdtdir, fdtoverlays - Integer fields: ipappend, attempted, localboot, localboot_val, kaslrseed, num The test also verifies struct pxe_menu fields: title, default_label, fallback_label, bmp, timeout, prompt. Parser keywords exercised include: kernel, linux, fit (with #config syntax), append, initrd, fdt, fdtdir, fdtoverlays, localboot, ipappend, kaslrseed, menu title, timeout, prompt, default, fallback, background. The Python wrapper (test/py/tests/test_pxe_parser.py) creates a FAT filesystem image with an extlinux.conf file and passes it to the C test. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/Makefile | 1 + test/boot/pxe.c | 197 +++++++++++++++++++++++++++++++ test/cmd_ut.c | 2 + test/py/tests/test_pxe_parser.py | 182 ++++++++++++++++++++++++++++ 4 files changed, 382 insertions(+) create mode 100644 test/boot/pxe.c create mode 100644 test/py/tests/test_pxe_parser.py diff --git a/test/boot/Makefile b/test/boot/Makefile index 21f533cdc4c..ceb863969dd 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_CMDLINE) += pxe.o obj-$(CONFIG_FIT) += image.o obj-$(CONFIG_$(PHASE_)FIT_PRINT) += fit_print.o obj-$(CONFIG_BLK_LUKS) += luks.o diff --git a/test/boot/pxe.c b/test/boot/pxe.c new file mode 100644 index 00000000000..49d8d160389 --- /dev/null +++ b/test/boot/pxe.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PXE parser tests - C implementation for Python wrapper + * + * Copyright 2026 Canonical Ltd + * + * These tests verify the extlinux.conf parser APIs. + */ + +#include <dm.h> +#include <env.h> +#include <fs_legacy.h> +#include <mapmem.h> +#include <pxe_utils.h> +#include <test/test.h> +#include <test/ut.h> + +/* Define test macro for pxe suite - no init function needed */ +#define PXE_TEST_ARGS(_name, _flags, ...) \ + UNIT_TEST_ARGS(_name, _flags, pxe, __VA_ARGS__) + +/* Argument indices */ +#define PXE_ARG_FS_IMAGE 0 /* Path to filesystem image */ +#define PXE_ARG_CFG_PATH 1 /* Path to config file within image */ + +/* Memory address for loading files */ +#define PXE_LOAD_ADDR 0x01000000 + +/** + * struct pxe_test_info - context for the test getfile callback + * + * @uts: Unit test state for assertions + */ +struct pxe_test_info { + struct unit_test_state *uts; +}; + +/** + * pxe_test_getfile() - Read a file from the host filesystem + * + * This callback is used by the PXE parser to read included files. + */ +static int pxe_test_getfile(struct pxe_context *ctx, const char *file_path, + ulong *addrp, ulong align, + enum bootflow_img_t type, ulong *sizep) +{ + loff_t len_read; + int ret; + + if (!*addrp) + return -ENOTSUPP; + + ret = fs_set_blk_dev("host", "0:0", FS_TYPE_ANY); + if (ret) + return ret; + ret = fs_legacy_read(file_path, *addrp, 0, 0, &len_read); + if (ret) + return ret; + *sizep = len_read; + + return 0; +} + +/** + * Test parsing an extlinux.conf file + * + * This test: + * 1. Binds a filesystem image containing extlinux.conf + * 2. Parses the config using parse_pxefile() + * 3. Verifies the parsed labels can be inspected + * 4. Verifies label properties are accessible + */ +static int pxe_test_parse_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + ulong addr = PXE_LOAD_ADDR; + struct pxe_test_info info; + struct pxe_context ctx; + struct pxe_label *label; + struct pxe_menu *cfg; + int ret; + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + info.uts = uts; + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Set up the PXE context */ + ut_assertok(pxe_setup_ctx(&ctx, pxe_test_getfile, &info, true, cfg_path, + false, false, NULL)); + + /* Read the config file into memory */ + ret = get_pxe_file(&ctx, cfg_path, addr); + ut_asserteq(1, ret); /* get_pxe_file returns 1 on success */ + + /* Parse the config file */ + cfg = parse_pxefile(&ctx, addr); + ut_assertnonnull(cfg); + + /* Verify menu properties */ + ut_asserteq_str("Test Boot Menu", cfg->title); + ut_asserteq_str("linux", cfg->default_label); + ut_asserteq_str("rescue", cfg->fallback_label); + ut_asserteq_str("/boot/background.bmp", cfg->bmp); + ut_asserteq(50, cfg->timeout); + ut_asserteq(1, cfg->prompt); + + /* Verify first label: linux (with fdt, fdtoverlays) */ + label = list_first_entry(&cfg->labels, struct pxe_label, list); + ut_asserteq_str("", label->num); /* only set when menu is built */ + ut_asserteq_str("linux", label->name); + ut_asserteq_str("Boot Linux", label->menu); + ut_asserteq_str("/vmlinuz", label->kernel_label); + 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_str("/dtb/board.dtb", label->fdt); + ut_assertnull(label->fdtdir); + ut_asserteq_str("/dtb/overlay1.dtbo /dtb/overlay2.dtbo", + label->fdtoverlays); + ut_asserteq(0, label->ipappend); + ut_asserteq(0, label->attempted); + ut_asserteq(0, label->localboot); + ut_asserteq(0, label->localboot_val); + ut_asserteq(1, label->kaslrseed); + + /* Verify second label: rescue (linux keyword, fdtdir, ipappend) */ + label = list_entry(label->list.next, struct pxe_label, list); + ut_asserteq_str("", label->num); + ut_asserteq_str("rescue", label->name); + ut_asserteq_str("Rescue Mode", label->menu); + ut_asserteq_str("/vmlinuz-rescue", label->kernel_label); + ut_asserteq_str("/vmlinuz-rescue", label->kernel); + ut_assertnull(label->config); + ut_asserteq_str("single", label->append); + ut_assertnull(label->initrd); + ut_assertnull(label->fdt); + ut_asserteq_str("/dtb/", label->fdtdir); + ut_assertnull(label->fdtoverlays); + ut_asserteq(3, label->ipappend); + ut_asserteq(0, label->attempted); + ut_asserteq(0, label->localboot); + ut_asserteq(0, label->localboot_val); + ut_asserteq(0, label->kaslrseed); + + /* Verify third label: local (localboot only) */ + label = list_entry(label->list.next, struct pxe_label, list); + ut_asserteq_str("", label->num); + ut_asserteq_str("local", label->name); + ut_asserteq_str("Local Boot", label->menu); + ut_assertnull(label->kernel_label); + ut_assertnull(label->kernel); + ut_assertnull(label->config); + ut_assertnull(label->append); + ut_assertnull(label->initrd); + ut_assertnull(label->fdt); + ut_assertnull(label->fdtdir); + ut_assertnull(label->fdtoverlays); + ut_asserteq(0, label->ipappend); + ut_asserteq(0, label->attempted); + ut_asserteq(1, label->localboot); + ut_asserteq(1, label->localboot_val); + ut_asserteq(0, label->kaslrseed); + + /* Verify fourth label: fitboot (fit keyword sets kernel and config) */ + label = list_entry(label->list.next, struct pxe_label, list); + ut_asserteq_str("", label->num); + ut_asserteq_str("fitboot", label->name); + ut_asserteq_str("FIT Boot", label->menu); + ut_asserteq_str("/boot/image.fit#config-1", label->kernel_label); + 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_assertnull(label->fdt); + ut_assertnull(label->fdtdir); + ut_assertnull(label->fdtoverlays); + ut_asserteq(0, label->ipappend); + ut_asserteq(0, label->attempted); + ut_asserteq(0, label->localboot); + ut_asserteq(0, label->localboot_val); + ut_asserteq(0, label->kaslrseed); + + /* Clean up */ + destroy_pxe_menu(cfg); + pxe_destroy_ctx(&ctx); + + return 0; +} +PXE_TEST_ARGS(pxe_test_parse_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/cmd_ut.c b/test/cmd_ut.c index a35ac69434d..f8ff02bdbc7 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -71,6 +71,7 @@ SUITE_DECL(measurement); SUITE_DECL(mem); SUITE_DECL(optee); SUITE_DECL(pci_mps); +SUITE_DECL(pxe); SUITE_DECL(seama); SUITE_DECL(setexpr); SUITE_DECL(upl); @@ -100,6 +101,7 @@ static struct suite suites[] = { SUITE(mem, "memory-related commands"), SUITE(optee, "OP-TEE"), SUITE(pci_mps, "PCI Express Maximum Payload Size"), + SUITE(pxe, "PXE/extlinux parser tests"), SUITE(seama, "seama command parameters loading and decoding"), SUITE(setexpr, "setexpr command"), SUITE(upl, "Universal payload support"), diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py new file mode 100644 index 00000000000..c9af135b17d --- /dev/null +++ b/test/py/tests/test_pxe_parser.py @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2026 Canonical Ltd + +""" +Test the PXE/extlinux parser APIs + +These tests verify that the extlinux.conf parser can be used independently +to inspect boot labels without loading kernel/initrd/FDT files. + +Tests are implemented in C (test/boot/pxe.c) and called from here. +Python handles filesystem image setup and configuration. +""" + +import os +import pytest + +from fs_helper import FsHelper + + +def create_extlinux_conf(srcdir, labels, menu_opts=None): + """Create an extlinux.conf file with the given labels + + Args: + srcdir (str): Directory to create the extlinux directory in + labels (list): List of dicts with label properties: + - name: Label name (required) + - menu: Menu label text (optional) + - kernel: Kernel path (optional) + - linux: Linux kernel path (alternative to kernel) + - initrd: Initrd path (optional) + - append: Kernel arguments (optional) + - fdt: Device tree path (optional) + - fdtdir: Device tree directory (optional) + - fdtoverlays: Device tree overlays (optional) + - localboot: Local boot flag (optional) + - ipappend: IP append flags (optional) + - fit: FIT config path (optional) + - kaslrseed: Enable KASLR seed (optional) + - default: If True, this is the default label (optional) + menu_opts (dict): Menu-level options: + - title: Menu title + - timeout: Timeout in tenths of a second + - prompt: Prompt flag + - fallback: Fallback label name + - ontimeout: Label to boot on timeout + - background: Background image path + - say: Message to print + - include: File to include + + Returns: + str: Path to the config file relative to srcdir + """ + if menu_opts is None: + menu_opts = {} + + extdir = os.path.join(srcdir, 'extlinux') + os.makedirs(extdir, exist_ok=True) + + conf_path = os.path.join(extdir, 'extlinux.conf') + with open(conf_path, 'w', encoding='ascii') as fd: + # Menu-level options + title = menu_opts.get('title', 'Test Boot Menu') + fd.write(f'menu title {title}\n') + fd.write(f"timeout {menu_opts.get('timeout', 1)}\n") + if 'prompt' in menu_opts: + fd.write(f"prompt {menu_opts['prompt']}\n") + if 'fallback' in menu_opts: + fd.write(f"fallback {menu_opts['fallback']}\n") + if 'ontimeout' in menu_opts: + fd.write(f"ontimeout {menu_opts['ontimeout']}\n") + if 'background' in menu_opts: + fd.write(f"menu background {menu_opts['background']}\n") + if 'say' in menu_opts: + fd.write(f"say {menu_opts['say']}\n") + if 'include' in menu_opts: + fd.write(f"include {menu_opts['include']}\n") + + for label in labels: + if label.get('default'): + fd.write(f"default {label['name']}\n") + + for label in labels: + fd.write(f"\nlabel {label['name']}\n") + if 'menu' in label: + fd.write(f" menu label {label['menu']}\n") + if 'kernel' in label: + fd.write(f" kernel {label['kernel']}\n") + if 'linux' in label: + fd.write(f" linux {label['linux']}\n") + if 'initrd' in label: + fd.write(f" initrd {label['initrd']}\n") + if 'append' in label: + fd.write(f" append {label['append']}\n") + if 'fdt' in label: + fd.write(f" fdt {label['fdt']}\n") + if 'fdtdir' in label: + fd.write(f" fdtdir {label['fdtdir']}\n") + if 'fdtoverlays' in label: + fd.write(f" fdtoverlays {label['fdtoverlays']}\n") + if 'localboot' in label: + fd.write(f" localboot {label['localboot']}\n") + if 'ipappend' in label: + fd.write(f" ipappend {label['ipappend']}\n") + if 'fit' in label: + fd.write(f" fit {label['fit']}\n") + if label.get('kaslrseed'): + fd.write(" kaslrseed\n") + + return '/extlinux/extlinux.conf' + + +@pytest.fixture +def pxe_image(u_boot_config): + """Create a filesystem image with an extlinux.conf file""" + fsh = FsHelper(u_boot_config, 'vfat', 4, prefix='pxe_test') + fsh.setup() + + # Create a simple extlinux.conf with multiple labels + labels = [ + { + 'name': 'linux', + 'menu': 'Boot Linux', + 'kernel': '/vmlinuz', + 'initrd': '/initrd.img', + 'append': 'root=/dev/sda1 quiet', + 'fdt': '/dtb/board.dtb', + 'fdtoverlays': '/dtb/overlay1.dtbo /dtb/overlay2.dtbo', + 'kaslrseed': True, + 'default': True, + }, + { + 'name': 'rescue', + 'menu': 'Rescue Mode', + 'linux': '/vmlinuz-rescue', # test 'linux' keyword + 'append': 'single', + 'fdtdir': '/dtb/', + 'ipappend': '3', + }, + { + 'name': 'local', + 'menu': 'Local Boot', + 'localboot': '1', + }, + { + 'name': 'fitboot', + 'menu': 'FIT Boot', + 'fit': '/boot/image.fit#config-1', + 'append': 'console=ttyS0', + }, + ] + + menu_opts = { + 'title': 'Test Boot Menu', + 'timeout': 50, + 'prompt': 1, + 'fallback': 'rescue', + 'ontimeout': 'linux', + 'background': '/boot/background.bmp', + } + + cfg_path = create_extlinux_conf(fsh.srcdir, labels, menu_opts) + + # Create the filesystem + fsh.mk_fs() + + yield fsh.fs_img, cfg_path + + # Cleanup + if not u_boot_config.persist: + fsh.cleanup() + + +@pytest.mark.boardspec('sandbox') +class TestPxeParser: + """Test PXE/extlinux parser APIs via C unit tests""" + + def test_pxe_parse(self, ubman, pxe_image): + """Test parsing an extlinux.conf and verifying label properties""" + fs_img, cfg_path = pxe_image + with ubman.log.section('Test PXE parse'): + ubman.run_ut('pxe', 'pxe_test_parse', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add test coverage for the 'say' keyword which prints a message during label-parsing. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 7 +++++++ test/py/tests/test_pxe_parser.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 49d8d160389..ef42026e748 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -101,6 +101,10 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) cfg = parse_pxefile(&ctx, addr); ut_assertnonnull(cfg); + /* Verify 'say' keyword printed its message during parsing */ + ut_assert_nextline("Retrieving file: %s", cfg_path); + ut_assert_nextline("Booting default Linux kernel"); + /* Verify menu properties */ ut_asserteq_str("Test Boot Menu", cfg->title); ut_asserteq_str("linux", cfg->default_label); @@ -186,6 +190,9 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq(0, label->localboot_val); ut_asserteq(0, label->kaslrseed); + /* Verify no more console output */ + ut_assert_console_end(); + /* Clean up */ destroy_pxe_menu(cfg); pxe_destroy_ctx(&ctx); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index c9af135b17d..1d51116a97a 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -105,6 +105,8 @@ def create_extlinux_conf(srcdir, labels, menu_opts=None): fd.write(f" fit {label['fit']}\n") if label.get('kaslrseed'): fd.write(" kaslrseed\n") + if 'say' in label: + fd.write(f" say {label['say']}\n") return '/extlinux/extlinux.conf' @@ -126,6 +128,7 @@ def pxe_image(u_boot_config): 'fdt': '/dtb/board.dtb', 'fdtoverlays': '/dtb/overlay1.dtbo /dtb/overlay2.dtbo', 'kaslrseed': True, + 'say': 'Booting default Linux kernel', 'default': True, }, { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Sandbox cannot actually boot a kernel, so there is no point in trying subsequent labels after the first boot attempt fails. Return success after the boot attempt so that sysboot stops, allowing tests to verify that files were loaded correctly. This change only affects sandbox builds. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/pxe_utils.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 981023a3012..ba1f7c41512 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -849,9 +849,15 @@ static int label_boot(struct pxe_context *ctx, struct pxe_label *label) label_run_boot(ctx, label, kern_addr_str, kern_addr, kern_size, initrd_addr, initrd_size, initrd_str, conf_fdt_str, conf_fdt); - /* ignore the error value since we are going to fail anyway */ - return 1; /* returning is always failure */ + /* + * Sandbox cannot boot a real kernel, so stop after the first attempt. + * On real hardware, returning is always failure, so try next label. + */ + if (IS_ENABLED(CONFIG_SANDBOX)) + return 0; + + return 1; } /* -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Test the 'include' directive which allows one extlinux.conf file to include another. Create an extra.conf file with an additional label and verify it is parsed correctly when included from the main config. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 20 ++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 16 ++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index ef42026e748..45948ab4ef4 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -104,6 +104,7 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) /* Verify 'say' keyword printed its message during parsing */ ut_assert_nextline("Retrieving file: %s", cfg_path); ut_assert_nextline("Booting default Linux kernel"); + ut_assert_nextline("Retrieving file: /extlinux/extra.conf"); /* Verify menu properties */ ut_asserteq_str("Test Boot Menu", cfg->title); @@ -190,6 +191,25 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq(0, label->localboot_val); ut_asserteq(0, label->kaslrseed); + /* Verify fifth label: included (from include directive) */ + label = list_entry(label->list.next, struct pxe_label, list); + ut_asserteq_str("", label->num); + ut_asserteq_str("included", label->name); + ut_asserteq_str("Included Label", label->menu); + ut_asserteq_str("/boot/included-kernel", label->kernel_label); + 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_assertnull(label->fdt); + ut_assertnull(label->fdtdir); + ut_assertnull(label->fdtoverlays); + ut_asserteq(0, label->ipappend); + ut_asserteq(0, label->attempted); + ut_asserteq(0, label->localboot); + ut_asserteq(0, label->localboot_val); + ut_asserteq(0, label->kaslrseed); + /* Verify no more console output */ ut_assert_console_end(); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index 1d51116a97a..b289bf58b11 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -72,8 +72,6 @@ def create_extlinux_conf(srcdir, labels, menu_opts=None): fd.write(f"menu background {menu_opts['background']}\n") if 'say' in menu_opts: fd.write(f"say {menu_opts['say']}\n") - if 'include' in menu_opts: - fd.write(f"include {menu_opts['include']}\n") for label in labels: if label.get('default'): @@ -108,6 +106,10 @@ def create_extlinux_conf(srcdir, labels, menu_opts=None): if 'say' in label: fd.write(f" say {label['say']}\n") + # Write include at the end so included labels come after main labels + if 'include' in menu_opts: + fd.write(f"\ninclude {menu_opts['include']}\n") + return '/extlinux/extlinux.conf' @@ -159,10 +161,20 @@ def pxe_image(u_boot_config): 'fallback': 'rescue', 'ontimeout': 'linux', 'background': '/boot/background.bmp', + 'include': '/extlinux/extra.conf', } cfg_path = create_extlinux_conf(fsh.srcdir, labels, menu_opts) + # Create an included config file with an additional label + extra_path = os.path.join(fsh.srcdir, 'extlinux', 'extra.conf') + with open(extra_path, 'w', encoding='ascii') as fd: + fd.write("# Included configuration\n") + fd.write("label included\n") + fd.write(" menu label Included Label\n") + fd.write(" kernel /boot/included-kernel\n") + fd.write(" append root=/dev/sdb1\n") + # Create the filesystem fsh.mk_fs() -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test for the include directive with maximum nesting depth: a chain of 16 include files (the MAX_NEST_LEVEL limit), each adding a label. This checks that the nest_level tracking in struct pxe_include works correctly through all levels. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 11 +++++++++++ test/py/tests/test_pxe_parser.py | 31 +++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 45948ab4ef4..778c7d62816 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -79,6 +79,8 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) struct pxe_context ctx; struct pxe_label *label; struct pxe_menu *cfg; + char name[16]; + uint i; int ret; ut_assertnonnull(fs_image); @@ -105,6 +107,8 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_assert_nextline("Retrieving file: %s", cfg_path); ut_assert_nextline("Booting default Linux kernel"); ut_assert_nextline("Retrieving file: /extlinux/extra.conf"); + for (i = 3; i <= 16; i++) + ut_assert_nextline("Retrieving file: /extlinux/nest%d.conf", i); /* Verify menu properties */ ut_asserteq_str("Test Boot Menu", cfg->title); @@ -210,6 +214,13 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) ut_asserteq(0, label->localboot_val); ut_asserteq(0, label->kaslrseed); + /* Verify labels from nested includes (levels 3-16) - just check names */ + for (i = 3; i <= 16; i++) { + label = list_entry(label->list.next, struct pxe_label, list); + snprintf(name, sizeof(name), "level%d", i); + ut_asserteq_str(name, label->name); + } + /* Verify no more console output */ ut_assert_console_end(); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index b289bf58b11..614252a444f 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -166,14 +166,29 @@ def pxe_image(u_boot_config): cfg_path = create_extlinux_conf(fsh.srcdir, labels, menu_opts) - # Create an included config file with an additional label - extra_path = os.path.join(fsh.srcdir, 'extlinux', 'extra.conf') - with open(extra_path, 'w', encoding='ascii') as fd: - fd.write("# Included configuration\n") - fd.write("label included\n") - fd.write(" menu label Included Label\n") - fd.write(" kernel /boot/included-kernel\n") - fd.write(" append root=/dev/sdb1\n") + # Create a chain of 16 nested include files to test MAX_NEST_LEVEL + # Level 1 is extlinux.conf, levels 2-16 are extra.conf, nest3.conf, etc. + for level in range(2, 17): + if level == 2: + fname = 'extra.conf' + label_name = 'included' + label_menu = 'Included Label' + else: + fname = f'nest{level}.conf' + label_name = f'level{level}' + label_menu = f'Level {level} Label' + + fpath = os.path.join(fsh.srcdir, 'extlinux', fname) + with open(fpath, 'w', encoding='ascii') as fd: + fd.write(f"# Level {level} configuration\n") + fd.write(f"label {label_name}\n") + fd.write(f" menu label {label_menu}\n") + fd.write(f" kernel /boot/{label_name}-kernel\n") + fd.write(f" append root=/dev/sd{chr(ord('a') + level - 1)}1\n") + # Include next level unless we're at level 16 + if level < 16: + next_fname = f'nest{level + 1}.conf' + fd.write(f"\ninclude /extlinux/{next_fname}\n") # Create the filesystem fsh.mk_fs() -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that uses the sysboot command to boot a label and verifies that files are loaded to the correct memory addresses. - Run sysboot with an extlinux.conf containing multiple labels - Verify console output shows files being retrieved - Check kernel, initrd, and FDT are at expected addresses - Verifiy file contents match what was written to the filesystem Use simple strings for the dummy kernel and initrd files so we can check that the correct files are loaded. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 85 ++++++++++++++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 77 +++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 778c7d62816..3d560fd9ade 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -9,7 +9,9 @@ #include <dm.h> #include <env.h> +#include <fdt_support.h> #include <fs_legacy.h> +#include <linux/libfdt.h> #include <mapmem.h> #include <pxe_utils.h> #include <test/test.h> @@ -25,6 +27,10 @@ /* Memory address for loading files */ #define PXE_LOAD_ADDR 0x01000000 +#define PXE_KERNEL_ADDR 0x02000000 +#define PXE_INITRD_ADDR 0x02800000 +#define PXE_FDT_ADDR 0x03000000 +#define PXE_OVERLAY_ADDR 0x03100000 /** * struct pxe_test_info - context for the test getfile callback @@ -233,3 +239,82 @@ static int pxe_test_parse_norun(struct unit_test_state *uts) PXE_TEST_ARGS(pxe_test_parse_norun, UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }, { "cfg_path", UT_ARG_STR }); + +/** + * Test booting via sysboot command + * + * This test: + * 1. Binds a filesystem image containing extlinux.conf + * 2. Sets up environment variables for file loading + * 3. Runs sysboot to boot the default label + * 4. Verifies files were loaded by checking console output + */ +static int pxe_test_sysboot_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + void *kernel, *initrd, *fdt; + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Set environment variables for file loading */ + ut_assertok(env_set_hex("pxefile_addr_r", PXE_LOAD_ADDR)); + ut_assertok(env_set_hex("kernel_addr_r", PXE_KERNEL_ADDR)); + ut_assertok(env_set_hex("ramdisk_addr_r", PXE_INITRD_ADDR)); + ut_assertok(env_set_hex("fdt_addr_r", PXE_FDT_ADDR)); + ut_assertok(env_set_hex("fdtoverlay_addr_r", PXE_OVERLAY_ADDR)); + ut_assertok(env_set("bootfile", cfg_path)); + + /* + * Run sysboot - it will try all labels and return 0 after failing + * to boot them all (since sandbox can't actually boot Linux) + */ + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + /* Skip menu output and find the first label boot attempt */ + ut_assert_skip_to_line("Enter choice: 1:\tBoot Linux"); + + /* Verify files were loaded in order */ + ut_assert_nextline("Retrieving file: /vmlinuz"); + ut_assert_nextline("Retrieving file: /initrd.img"); + ut_assert_nextline("append: root=/dev/sda1 quiet"); + ut_assert_nextline("Retrieving file: /dtb/board.dtb"); + ut_assert_nextline("Retrieving file: /dtb/overlay1.dtbo"); + ut_assert_nextline("Retrieving file: /dtb/overlay2.dtbo"); + + /* Boot fails on sandbox */ + ut_assert_nextline("Unrecognized zImage"); + ut_assert_nextlinen(" unmap_physmem"); + + /* Verify files were loaded at the correct addresses */ + kernel = map_sysmem(PXE_KERNEL_ADDR, 0); + initrd = map_sysmem(PXE_INITRD_ADDR, 0); + fdt = map_sysmem(PXE_FDT_ADDR, 0); + + /* Kernel should contain "kernel" at start */ + ut_asserteq_mem("kernel", kernel, 6); + + /* Initrd should contain "ramdisk" at start */ + ut_asserteq_mem("ramdisk", initrd, 7); + + /* FDT should have valid magic number */ + ut_assertok(fdt_check_header(fdt)); + + /* Verify overlays were applied - check for properties added by overlays */ + ut_asserteq_str("from-overlay1", + fdt_getprop(fdt, fdt_path_offset(fdt, "/test-node"), + "overlay1-property", NULL)); + ut_asserteq_str("from-overlay2", + fdt_getprop(fdt, fdt_path_offset(fdt, "/test-node"), + "overlay2-property", NULL)); + + return 0; +} +PXE_TEST_ARGS(pxe_test_sysboot_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index 614252a444f..c2467f70f5d 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -13,10 +13,64 @@ Python handles filesystem image setup and configuration. import os import pytest +import subprocess from fs_helper import FsHelper +# Simple base DTS with symbols enabled (for overlay support) +BASE_DTS = """\ +/dts-v1/; + +/ { + model = "Test Board"; + compatible = "test,board"; + + test: test-node { + test-property = <42>; + status = "okay"; + }; +}; +""" + +# Simple overlay that modifies the test node +OVERLAY1_DTS = """\ +/dts-v1/; +/plugin/; + +&test { + overlay1-property = "from-overlay1"; +}; +""" + +# Another overlay that adds a different property +OVERLAY2_DTS = """\ +/dts-v1/; +/plugin/; + +&test { + overlay2-property = "from-overlay2"; +}; +""" + + +def compile_dts(dts_content, output_path, is_overlay=False): + """Compile DTS content to DTB/DTBO file + + Args: + dts_content (str): DTS source content + output_path (str): Path to output DTB/DTBO file + is_overlay (bool): True if this is an overlay (needs -@) + + Raises: + subprocess.CalledProcessError: If dtc fails + """ + # Use -@ for both base (to generate __symbols__) and overlays + cmd = ['dtc', '-@', '-I', 'dts', '-O', 'dtb', '-o', output_path] + subprocess.run(cmd, input=dts_content.encode(), check=True, + capture_output=True) + + def create_extlinux_conf(srcdir, labels, menu_opts=None): """Create an extlinux.conf file with the given labels @@ -190,6 +244,21 @@ def pxe_image(u_boot_config): next_fname = f'nest{level + 1}.conf' fd.write(f"\ninclude /extlinux/{next_fname}\n") + # Create DTB and overlay files for testing + dtbdir = os.path.join(fsh.srcdir, 'dtb') + os.makedirs(dtbdir, exist_ok=True) + compile_dts(BASE_DTS, os.path.join(dtbdir, 'board.dtb')) + compile_dts(OVERLAY1_DTS, os.path.join(dtbdir, 'overlay1.dtbo'), + is_overlay=True) + compile_dts(OVERLAY2_DTS, os.path.join(dtbdir, 'overlay2.dtbo'), + is_overlay=True) + + # Create dummy kernel and initrd files with identifiable content + with open(os.path.join(fsh.srcdir, 'vmlinuz'), 'wb') as fd: + fd.write(b'kernel') + with open(os.path.join(fsh.srcdir, 'initrd.img'), 'wb') as fd: + fd.write(b'ramdisk') + # Create the filesystem fsh.mk_fs() @@ -201,6 +270,7 @@ def pxe_image(u_boot_config): @pytest.mark.boardspec('sandbox') +@pytest.mark.requiredtool('dtc') class TestPxeParser: """Test PXE/extlinux parser APIs via C unit tests""" @@ -210,3 +280,10 @@ class TestPxeParser: with ubman.log.section('Test PXE parse'): ubman.run_ut('pxe', 'pxe_test_parse', fs_image=fs_img, cfg_path=cfg_path) + + def test_pxe_sysboot(self, ubman, pxe_image): + """Test booting via sysboot command""" + fs_img, cfg_path = pxe_image + with ubman.log.section('Test PXE sysboot'): + ubman.run_ut('pxe', 'pxe_test_sysboot', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that verifies fdtdir path-construction via sysboot: - fdtdir with fdtfile env var uses the variable directly - fdtdir with soc/board env vars constructs {soc}-{board}.dtb The test runs sysboot twice with different environment settings and checks console output to verify the correct FDT paths are constructed. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 93 ++++++++++++++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 65 ++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 3d560fd9ade..a924247bf98 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -318,3 +318,96 @@ static int pxe_test_sysboot_norun(struct unit_test_state *uts) PXE_TEST_ARGS(pxe_test_sysboot_norun, UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }, { "cfg_path", UT_ARG_STR }); + +/** + * Test fdtdir path resolution via sysboot + * + * This test verifies fdtdir path construction by running sysboot and + * checking console output: + * 1. fdtdir with fdtfile env var - uses fdtfile value directly + * 2. fdtdir with soc/board env vars - constructs {soc}-{board}.dtb + * 3. fdtdir without trailing slash - slash is inserted + */ +static int pxe_test_fdtdir_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + void *fdt; + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* + * Test 1: fdtdir with fdtfile env var + * The first label uses fdtdir=/dtb/ and we set fdtfile=test-board.dtb + * so it should retrieve /dtb/test-board.dtb + */ + ut_assertok(env_set_hex("pxefile_addr_r", PXE_LOAD_ADDR)); + ut_assertok(env_set_hex("kernel_addr_r", PXE_KERNEL_ADDR)); + ut_assertok(env_set_hex("fdt_addr_r", PXE_FDT_ADDR)); + ut_assertok(env_set_hex("fdtoverlay_addr_r", PXE_OVERLAY_ADDR)); + ut_assertok(env_set("fdtfile", "test-board.dtb")); + ut_assertok(env_set("bootfile", cfg_path)); + + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + /* Skip to the boot attempt - first label is fdtfile-test */ + ut_assert_skip_to_line("Enter choice: 1:\tTest fdtfile env var"); + + /* Verify fdtdir used fdtfile env var to construct path */ + ut_assert_nextline("Retrieving file: /vmlinuz"); + ut_assert_nextline("append: console=ttyS0"); + ut_assert_nextline("Retrieving file: /dtb/test-board.dtb"); + ut_assert_nextline("Retrieving file: /dtb/overlay1.dtbo"); + + /* Boot fails but we verified the path construction */ + ut_assert_nextline("Unrecognized zImage"); + ut_assert_nextlinen(" unmap_physmem"); + + /* Verify FDT was loaded correctly */ + fdt = map_sysmem(PXE_FDT_ADDR, 0); + ut_assertok(fdt_check_header(fdt)); + + /* + * Test 2: fdtdir with soc/board env vars (no fdtfile) + * Clear fdtfile and set soc/board - the default label (fdtfile-test) + * will now construct the path from soc-board: /dtb/tegra-jetson.dtb + */ + ut_assertok(env_set("fdtfile", NULL)); /* Clear fdtfile */ + ut_assertok(env_set("soc", "tegra")); + ut_assertok(env_set("board", "jetson")); + + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + /* Still boots default label, but now uses soc-board path construction */ + ut_assert_skip_to_line("Enter choice: 1:\tTest fdtfile env var"); + + /* Verify fdtdir constructed path from soc-board */ + ut_assert_nextline("Retrieving file: /vmlinuz"); + ut_assert_nextline("append: console=ttyS0"); + ut_assert_nextline("Retrieving file: /dtb/tegra-jetson.dtb"); + ut_assert_nextline("Retrieving file: /dtb/overlay1.dtbo"); + + /* Boot fails but we verified the path construction */ + ut_assert_nextline("Unrecognized zImage"); + ut_assert_nextlinen(" unmap_physmem"); + + /* Verify FDT was loaded */ + fdt = map_sysmem(PXE_FDT_ADDR, 0); + ut_asserteq(FDT_MAGIC, fdt_magic(fdt)); + + /* Clean up env vars */ + env_set("fdtfile", NULL); + env_set("soc", NULL); + env_set("board", NULL); + + return 0; +} +PXE_TEST_ARGS(pxe_test_fdtdir_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index c2467f70f5d..ee8acb8c674 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -269,6 +269,64 @@ def pxe_image(u_boot_config): fsh.cleanup() +@pytest.fixture +def pxe_fdtdir_image(u_boot_config): + """Create a filesystem image with fdtdir-based configuration + + This tests the fdtdir path-resolution logic where the FDT filename + is constructed from environment variables. + """ + fsh = FsHelper(u_boot_config, 'vfat', 4, prefix='pxe_fdtdir') + fsh.setup() + + # Create labels using fdtdir instead of explicit fdt path + labels = [ + { + 'name': 'fdtfile-test', + 'menu': 'Test fdtfile env var', + 'kernel': '/vmlinuz', + 'append': 'console=ttyS0', + 'fdtdir': '/dtb/', # Will use fdtfile env var + 'fdtoverlays': '/dtb/overlay1.dtbo', + 'default': True, + }, + { + 'name': 'socboard-test', + 'menu': 'Test soc/board construction', + 'kernel': '/vmlinuz', + 'append': 'console=ttyS0', + 'fdtdir': '/dtb', # No trailing slash - tests slash insertion + }, + ] + + cfg_path = create_extlinux_conf(fsh.srcdir, labels) + + # Create DTB directory with files for different naming conventions + dtbdir = os.path.join(fsh.srcdir, 'dtb') + os.makedirs(dtbdir, exist_ok=True) + + # DTB for fdtfile env var test (fdtfile=test-board.dtb) + compile_dts(BASE_DTS, os.path.join(dtbdir, 'test-board.dtb')) + + # DTB for soc-board construction (soc=tegra, board=jetson) + compile_dts(BASE_DTS, os.path.join(dtbdir, 'tegra-jetson.dtb')) + + # Overlay for fdtdir test + compile_dts(OVERLAY1_DTS, os.path.join(dtbdir, 'overlay1.dtbo'), + is_overlay=True) + + # Create dummy kernel + with open(os.path.join(fsh.srcdir, 'vmlinuz'), 'wb') as fd: + fd.write(b'kernel') + fd.write(b'\x00' * (1024 - 6)) + + fsh.mk_fs() + + yield fsh.fs_img, cfg_path + + fsh.cleanup() + + @pytest.mark.boardspec('sandbox') @pytest.mark.requiredtool('dtc') class TestPxeParser: @@ -287,3 +345,10 @@ class TestPxeParser: with ubman.log.section('Test PXE sysboot'): ubman.run_ut('pxe', 'pxe_test_sysboot', fs_image=fs_img, cfg_path=cfg_path) + + def test_pxe_fdtdir(self, ubman, pxe_fdtdir_image): + """Test fdtdir path resolution with fdtfile and soc/board env vars""" + fs_img, cfg_path = pxe_fdtdir_image + with ubman.log.section('Test PXE fdtdir'): + ubman.run_ut('pxe', 'pxe_test_fdtdir', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that verifies error-handling when FDT files are missing: 1. Explicit FDT not found - when a label specifies an explicit fdt= path that doesn't exist, the label fails and sysboot moves to the next label 2. fdtdir FDT not found - when fdtdir is specified but the constructed FDT path doesn't exist, sysboot warns but continues to boot attempt The test uses sysboot and verifies behaviour through console output. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 72 ++++++++++++++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 68 ++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index a924247bf98..3ccb0c977e9 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -411,3 +411,75 @@ static int pxe_test_fdtdir_norun(struct unit_test_state *uts) PXE_TEST_ARGS(pxe_test_fdtdir_norun, UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }, { "cfg_path", UT_ARG_STR }); + +/** + * Test error handling for missing FDT files via sysboot + * + * This test verifies error handling by running sysboot and checking + * console output: + * 1. Explicit FDT not found - label fails with error, tries next label + * 2. fdtdir FDT not found - warns but continues to boot attempt + */ +static int pxe_test_errors_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Set up environment for loading */ + ut_assertok(env_set_hex("pxefile_addr_r", PXE_LOAD_ADDR)); + ut_assertok(env_set_hex("kernel_addr_r", PXE_KERNEL_ADDR)); + ut_assertok(env_set_hex("fdt_addr_r", PXE_FDT_ADDR)); + ut_assertok(env_set_hex("fdtoverlay_addr_r", PXE_OVERLAY_ADDR)); + ut_assertok(env_set("fdtfile", "missing.dtb")); /* For fdtdir test */ + ut_assertok(env_set("bootfile", cfg_path)); + + /* + * Run sysboot - it will try labels in sequence: + * 1. missing-fdt: fails because explicit FDT doesn't exist + * 2. missing-fdtdir: warns about missing FDT but attempts boot + * 3. missing-overlay: loads FDT, warns about missing overlay, boots + */ + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + /* + * Test 1: Explicit FDT file not found + * First label (missing-fdt) has fdt=/dtb/nonexistent.dtb + * Should fail and move to next label + */ + ut_assert_skip_to_line("Enter choice: 1:\tMissing explicit FDT"); + ut_assert_nextline("Retrieving file: /vmlinuz"); + ut_assert_nextline("Retrieving file: /dtb/nonexistent.dtb"); + ut_assert_nextline("Skipping missing-fdt for failure retrieving FDT"); + + /* + * Test 2: fdtdir with missing FDT file + * Second label (missing-fdtdir) has fdtdir=/dtb/ but fdtfile=missing.dtb + * Should warn but continue to boot attempt + */ + ut_assert_nextline("2:\tMissing fdtdir FDT"); + ut_assert_nextline("Retrieving file: /vmlinuz"); + ut_assert_nextline("Retrieving file: /dtb/missing.dtb"); + ut_assert_nextline("Skipping fdtdir /dtb/ for failure retrieving dts"); + + /* + * Boot attempt without FDT - sandbox can't boot, but this verifies + * that label loading continued despite missing fdtdir FDT + */ + ut_assert_nextline("Unrecognized zImage"); + ut_assert_nextlinen(" unmap_physmem"); + + /* Clean up env vars */ + env_set("fdtfile", NULL); + + return 0; +} +PXE_TEST_ARGS(pxe_test_errors_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index ee8acb8c674..2afe3a93b86 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -327,6 +327,67 @@ def pxe_fdtdir_image(u_boot_config): fsh.cleanup() +@pytest.fixture +def pxe_error_image(u_boot_config): + """Create a filesystem image for testing error handling + + This tests various error conditions: + - Explicit FDT file that doesn't exist (should fail label) + - fdtdir with missing FDT file (should continue) + - Missing overlay file (should continue) + """ + fsh = FsHelper(u_boot_config, 'vfat', 4, prefix='pxe_error') + fsh.setup() + + labels = [ + { + # Explicit FDT that doesn't exist - should fail this label + 'name': 'missing-fdt', + 'menu': 'Missing explicit FDT', + 'kernel': '/vmlinuz', + 'fdt': '/dtb/nonexistent.dtb', + 'default': True, + }, + { + # fdtdir with missing FDT - should warn but continue + 'name': 'missing-fdtdir', + 'menu': 'Missing fdtdir FDT', + 'kernel': '/vmlinuz', + 'fdtdir': '/dtb/', + }, + { + # Valid FDT but missing overlay - should continue + 'name': 'missing-overlay', + 'menu': 'Missing overlay', + 'kernel': '/vmlinuz', + 'fdt': '/dtb/board.dtb', + 'fdtoverlays': '/dtb/nonexistent.dtbo /dtb/overlay1.dtbo', + }, + ] + + cfg_path = create_extlinux_conf(fsh.srcdir, labels) + + # Create DTB directory with only some files + dtbdir = os.path.join(fsh.srcdir, 'dtb') + os.makedirs(dtbdir, exist_ok=True) + + # Only create board.dtb and overlay1.dtbo - others are missing + compile_dts(BASE_DTS, os.path.join(dtbdir, 'board.dtb')) + compile_dts(OVERLAY1_DTS, os.path.join(dtbdir, 'overlay1.dtbo'), + is_overlay=True) + + # Create dummy kernel + with open(os.path.join(fsh.srcdir, 'vmlinuz'), 'wb') as fd: + fd.write(b'kernel') + fd.write(b'\x00' * (1024 - 6)) + + fsh.mk_fs() + + yield fsh.fs_img, cfg_path + + fsh.cleanup() + + @pytest.mark.boardspec('sandbox') @pytest.mark.requiredtool('dtc') class TestPxeParser: @@ -352,3 +413,10 @@ class TestPxeParser: with ubman.log.section('Test PXE fdtdir'): ubman.run_ut('pxe', 'pxe_test_fdtdir', fs_image=fs_img, cfg_path=cfg_path) + + def test_pxe_errors(self, ubman, pxe_error_image): + """Test error handling for missing FDT and overlay files""" + fs_img, cfg_path = pxe_error_image + with ubman.log.section('Test PXE errors'): + ubman.run_ut('pxe', 'pxe_test_errors', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add some tests for: - pxe_get_file_size(): missing env var, valid hex values, invalid format - format_mac_pxe(): buffer too small, valid MAC format verification - get_pxelinux_path(): path too long error Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 107 ++++++++++++++++++++++++++++++- test/py/tests/test_pxe_parser.py | 7 ++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 3ccb0c977e9..e6d95d07d41 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -13,11 +13,14 @@ #include <fs_legacy.h> #include <linux/libfdt.h> #include <mapmem.h> +#include <net-common.h> #include <pxe_utils.h> #include <test/test.h> #include <test/ut.h> -/* Define test macro for pxe suite - no init function needed */ +/* Define test macros for pxe suite */ +#define PXE_TEST(_name, _flags) \ + UNIT_TEST(_name, _flags, pxe) #define PXE_TEST_ARGS(_name, _flags, ...) \ UNIT_TEST_ARGS(_name, _flags, pxe, __VA_ARGS__) @@ -483,3 +486,105 @@ static int pxe_test_errors_norun(struct unit_test_state *uts) PXE_TEST_ARGS(pxe_test_errors_norun, UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }, { "cfg_path", UT_ARG_STR }); + +/** + * Test pxe_get_file_size() function + * + * This tests reading the filesize from the environment variable. + */ +static int pxe_test_get_file_size(struct unit_test_state *uts) +{ + ulong size; + + /* Test with no filesize set - should return -ENOENT */ + env_set("filesize", NULL); + ut_asserteq(-ENOENT, pxe_get_file_size(&size)); + + /* Test with valid hex filesize */ + env_set("filesize", "1234"); + ut_assertok(pxe_get_file_size(&size)); + ut_asserteq(0x1234, size); + + /* Test with larger value */ + env_set("filesize", "abcdef"); + ut_assertok(pxe_get_file_size(&size)); + ut_asserteq(0xabcdef, size); + + /* Test with invalid (non-hex) value */ + env_set("filesize", "not_hex"); + ut_asserteq(-EINVAL, pxe_get_file_size(&size)); + + /* Clean up */ + env_set("filesize", NULL); + + return 0; +} +PXE_TEST(pxe_test_get_file_size, 0); + +/** + * Test format_mac_pxe() function + * + * This tests MAC address formatting for PXE boot paths. + */ +static int pxe_test_format_mac(struct unit_test_state *uts) +{ + char buf[21]; + + /* Test with buffer too small */ + ut_asserteq(-ENOSPC, format_mac_pxe(buf, 20)); + ut_asserteq(-ENOSPC, format_mac_pxe(buf, 1)); + + /* Test with valid buffer - sandbox has an ethernet device */ + ut_asserteq(1, format_mac_pxe(buf, sizeof(buf))); + + /* Verify format: 01-xx-xx-xx-xx-xx-xx */ + ut_asserteq(20, strlen(buf)); + ut_asserteq('0', buf[0]); + ut_asserteq('1', buf[1]); + ut_asserteq('-', buf[2]); + ut_asserteq('-', buf[5]); + ut_asserteq('-', buf[8]); + ut_asserteq('-', buf[11]); + ut_asserteq('-', buf[14]); + ut_asserteq('-', buf[17]); + + return 0; +} +PXE_TEST(pxe_test_format_mac, UTF_ETH_BOOTDEV); + +/** + * Test get_pxelinux_path() with path too long + * + * This tests the path length check in get_pxelinux_path(). + */ +static int pxe_test_pxelinux_path_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + struct pxe_test_info info; + struct pxe_context ctx; + char path[600]; + + ut_assertnonnull(fs_image); + info.uts = uts; + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Set up the PXE context */ + ut_assertok(pxe_setup_ctx(&ctx, pxe_test_getfile, &info, false, "/", + false, false, NULL)); + + /* Create a path that's too long (> 512 - 13 for "pxelinux.cfg/") */ + memset(path, 'a', sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + + /* Should fail with -ENAMETOOLONG */ + ut_asserteq(-ENAMETOOLONG, get_pxelinux_path(&ctx, path, + PXE_LOAD_ADDR)); + + pxe_destroy_ctx(&ctx); + + return 0; +} +PXE_TEST_ARGS(pxe_test_pxelinux_path_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index 2afe3a93b86..0d957c08219 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -420,3 +420,10 @@ class TestPxeParser: with ubman.log.section('Test PXE errors'): ubman.run_ut('pxe', 'pxe_test_errors', fs_image=fs_img, cfg_path=cfg_path) + + def test_pxe_pxelinux_path(self, ubman, pxe_image): + """Test get_pxelinux_path() path length checking""" + fs_img, cfg_path = pxe_image + with ubman.log.section('Test PXE pxelinux path'): + ubman.run_ut('pxe', 'pxe_test_pxelinux_path', + fs_image=fs_img) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The ontimeout keyword is exercised via the test fixtures but cannot be verified separately from the default keyword since both set the same cfg->default_label field. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index e6d95d07d41..8c526e1ba59 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -5,6 +5,11 @@ * Copyright 2026 Canonical Ltd * * These tests verify the extlinux.conf parser APIs. + * + * Note: The 'ontimeout' keyword is tested via the test fixtures which include + * it. Since ontimeout is handled identically to 'default' (both set + * cfg->default_label), it cannot be distinguished after parsing. + * */ #include <dm.h> -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The PXE menu timeout is configured in extlinux.conf, but tests need a way to override it to avoid long delays. Add a pxe_timeout environment variable that, when set, overrides the configured timeout value. The test uses pxe_timeout=1 to reduce the menu wait from 5 seconds to 1 second. Ideally the test would be instant, but this is good enough for now. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/pxe_utils.c | 5 ++++- test/boot/pxe.c | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index ba1f7c41512..fbe4a111453 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -929,11 +929,14 @@ static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg) int i = 1; char *default_num = NULL; char *override_num = NULL; + int timeout; + + timeout = env_get_ulong("pxe_timeout", 10, DIV_ROUND_UP(cfg->timeout, 10)); /* * Create a menu and add items for all the labels. */ - m = menu_create(cfg->title, DIV_ROUND_UP(cfg->timeout, 10), + m = menu_create(cfg->title, timeout, cfg->prompt, NULL, label_print, NULL, NULL, NULL); if (!m) return NULL; diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 8c526e1ba59..36e1f5519f4 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -276,6 +276,7 @@ static int pxe_test_sysboot_norun(struct unit_test_state *uts) ut_assertok(env_set_hex("fdt_addr_r", PXE_FDT_ADDR)); ut_assertok(env_set_hex("fdtoverlay_addr_r", PXE_OVERLAY_ADDR)); ut_assertok(env_set("bootfile", cfg_path)); + ut_assertok(env_set("pxe_timeout", "1")); /* * Run sysboot - it will try all labels and return 0 after failing -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that verifies ipappend correctly appends IP and MAC info to the bootargs environment variable. The test boots the rescue label which has ipappend=3, enabling both: - bit 0: ip=<ipaddr>:<serverip>:<gatewayip>:<netmask> - bit 1: BOOTIF=01-xx-xx-xx-xx-xx-xx Also add the vmlinuz-rescue kernel file to the test fixture. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 66 ++++++++++++++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 9 +++++ 2 files changed, 75 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 36e1f5519f4..fec2361c27f 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -594,3 +594,69 @@ static int pxe_test_pxelinux_path_norun(struct unit_test_state *uts) } PXE_TEST_ARGS(pxe_test_pxelinux_path_norun, UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }); + +/** + * Test ipappend functionality + * + * This tests that ipappend correctly appends IP and MAC information to + * bootargs. The rescue label has ipappend=3 which enables both: + * - bit 0x1: ip=<ipaddr>:<serverip>:<gatewayip>:<netmask> + * - bit 0x2: BOOTIF=01-xx-xx-xx-xx-xx-xx + */ +static int pxe_test_ipappend_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Set environment variables for file loading */ + ut_assertok(env_set_hex("pxefile_addr_r", PXE_LOAD_ADDR)); + ut_assertok(env_set_hex("kernel_addr_r", PXE_KERNEL_ADDR)); + ut_assertok(env_set_hex("ramdisk_addr_r", PXE_INITRD_ADDR)); + ut_assertok(env_set_hex("fdt_addr_r", PXE_FDT_ADDR)); + ut_assertok(env_set("bootfile", cfg_path)); + + /* Set network environment variables for ipappend */ + ut_assertok(env_set("ipaddr", "192.168.1.10")); + ut_assertok(env_set("serverip", "192.168.1.1")); + ut_assertok(env_set("gatewayip", "192.168.1.254")); + ut_assertok(env_set("netmask", "255.255.255.0")); + + /* Override to boot the rescue label which has ipappend=3 */ + ut_assertok(env_set("pxe_label_override", "rescue")); + ut_assertok(env_set("pxe_timeout", "1")); + + /* Run sysboot */ + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + /* Skip to the rescue label boot */ + ut_assert_skip_to_line("Retrieving file: /vmlinuz-rescue"); + + /* + * Verify ipappend output - should have: + * - original append: "single" + * - ip= string from ipappend bit 0x1 + * - BOOTIF= string from ipappend bit 0x2 + */ + ut_assert_nextlinen("append: single ip=192.168.1.10:192.168.1.1:" + "192.168.1.254:255.255.255.0 BOOTIF=01-"); + + /* Clean up */ + env_set("ipaddr", NULL); + env_set("serverip", NULL); + env_set("gatewayip", NULL); + env_set("netmask", NULL); + env_set("pxe_label_override", NULL); + env_set("pxe_timeout", NULL); + + return 0; +} +PXE_TEST_ARGS(pxe_test_ipappend_norun, UTF_CONSOLE | UTF_MANUAL | UTF_ETH_BOOTDEV, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index 0d957c08219..f3ae3ff9f78 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -256,6 +256,8 @@ def pxe_image(u_boot_config): # Create dummy kernel and initrd files with identifiable content with open(os.path.join(fsh.srcdir, 'vmlinuz'), 'wb') as fd: fd.write(b'kernel') + with open(os.path.join(fsh.srcdir, 'vmlinuz-rescue'), 'wb') as fd: + fd.write(b'rescue') with open(os.path.join(fsh.srcdir, 'initrd.img'), 'wb') as fd: fd.write(b'ramdisk') @@ -427,3 +429,10 @@ class TestPxeParser: with ubman.log.section('Test PXE pxelinux path'): ubman.run_ut('pxe', 'pxe_test_pxelinux_path', fs_image=fs_img) + + def test_pxe_ipappend(self, ubman, pxe_image): + """Test ipappend functionality for IP and MAC appending""" + fs_img, cfg_path = pxe_image + with ubman.log.section('Test PXE ipappend'): + ubman.run_ut('pxe', 'pxe_test_ipappend', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Extract the FDT address fallback logic from label_boot() into a new exported function pxe_get_fdt_fallback(). This determines the FDT address when a label doesn't specify an FDT file: 1. First tries fdt_addr environment variable 2. Falls back to fdtcontroladdr (unless kernel is FIT format) Add a test that verifies the fallback priority and behaviour. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/pxe_utils.c | 55 +++++++++++++++++++++++++++------------------ include/pxe_utils.h | 14 ++++++++++++ test/boot/pxe.c | 49 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index fbe4a111453..9034c3d86e7 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -396,6 +396,37 @@ skip_overlay: } #endif +const char *pxe_get_fdt_fallback(struct pxe_label *label, ulong kern_addr) +{ + const char *conf_fdt_str = NULL; + void *buf; + + /* + * Fallback to fdt_addr env var if label doesn't specify FDT + * and it's not ATAG mode (fdt="-") + */ + if (!IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS) || + !label->fdt || strcmp("-", label->fdt)) { + conf_fdt_str = env_get("fdt_addr"); + if (conf_fdt_str) + return conf_fdt_str; + } + + /* + * Fallback to fdtcontroladdr if not a FIT image and not ATAG mode + */ + buf = map_sysmem(kern_addr, 0); + if (genimg_get_format(buf) != IMAGE_FORMAT_FIT) { + if (!IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS) || + !label->fdt || strcmp("-", label->fdt)) { + conf_fdt_str = env_get("fdtcontroladdr"); + } + } + unmap_sysmem(buf); + + return conf_fdt_str; +} + /* * label_process_fdt() - Process FDT for the label * @@ -789,28 +820,8 @@ static int label_boot(struct pxe_context *ctx, struct pxe_label *label) if (ret) return ret; - if (!conf_fdt_str) { - if (!IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS) || - strcmp("-", label->fdt)) { - conf_fdt_str = env_get("fdt_addr"); - log_debug("using fdt_addr '%s'\n", conf_fdt_str); - } - } - - if (!conf_fdt_str) { - void *buf; - - buf = map_sysmem(kern_addr, 0); - if (genimg_get_format(buf) != IMAGE_FORMAT_FIT) { - if (!IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS) || - strcmp("-", label->fdt)) { - conf_fdt_str = env_get("fdtcontroladdr"); - log_debug("using fdtcontroladdr '%s'\n", - conf_fdt_str); - } - } - unmap_sysmem(buf); - } + if (!conf_fdt_str) + conf_fdt_str = pxe_get_fdt_fallback(label, kern_addr); if (conf_fdt_str) conf_fdt = hextoul(conf_fdt_str, NULL); log_debug("conf_fdt %lx\n", conf_fdt); diff --git a/include/pxe_utils.h b/include/pxe_utils.h index 7ecb5788d0b..9629f051a91 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -306,6 +306,20 @@ int pxe_process(struct pxe_context *ctx, ulong pxefile_addr_r, bool prompt); */ int pxe_get_file_size(ulong *sizep); +/** + * pxe_get_fdt_fallback() - Get the FDT address using fallback logic + * + * When a label doesn't specify an FDT file (via 'fdt' or 'fdtdir'), this + * function determines the FDT address using fallback environment variables: + * 1. fdt_addr - if set, use this address + * 2. fdtcontroladdr - if set and kernel is not FIT format + * + * @label: Label being processed + * @kern_addr: Address where kernel is loaded + * Return: FDT address string from environment, or NULL if no fallback available + */ +const char *pxe_get_fdt_fallback(struct pxe_label *label, ulong kern_addr); + /** * pxe_get() - Get the PXE file from the server * diff --git a/test/boot/pxe.c b/test/boot/pxe.c index fec2361c27f..505628ab92d 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -660,3 +660,52 @@ static int pxe_test_ipappend_norun(struct unit_test_state *uts) PXE_TEST_ARGS(pxe_test_ipappend_norun, UTF_CONSOLE | UTF_MANUAL | UTF_ETH_BOOTDEV, { "fs_image", UT_ARG_STR }, { "cfg_path", UT_ARG_STR }); + +/** + * Test pxe_get_fdt_fallback() function + * + * This tests the FDT address fallback logic when a label doesn't specify + * an FDT file via 'fdt' or 'fdtdir' keywords. + */ +static int pxe_test_fdt_fallback(struct unit_test_state *uts) +{ + const char *orig_fdt_addr, *orig_fdtcontroladdr; + ulong kern_addr = 0x1000000; + struct pxe_label label; + void *kern_buf; + + /* Create a dummy kernel buffer (not FIT format) */ + kern_buf = map_sysmem(kern_addr, 64); + memset(kern_buf, '\0', 64); + unmap_sysmem(kern_buf); + + memset(&label, '\0', sizeof(label)); + + /* Save and clear env vars (fdtcontroladdr is set by U-Boot) */ + orig_fdt_addr = env_get("fdt_addr"); + orig_fdtcontroladdr = env_get("fdtcontroladdr"); + ut_assertok(env_set("fdt_addr", NULL)); + ut_assertok(env_set("fdtcontroladdr", NULL)); + + /* Test 1: No fallback env vars set - should return NULL */ + ut_assertnull(pxe_get_fdt_fallback(&label, kern_addr)); + + /* Test 2: fdt_addr set - should return fdt_addr */ + ut_assertok(env_set_hex("fdt_addr", 0x2000000)); + ut_asserteq_str("2000000", pxe_get_fdt_fallback(&label, kern_addr)); + + /* Test 3: Both set - fdt_addr takes priority */ + ut_assertok(env_set_hex("fdtcontroladdr", 0x3000000)); + ut_asserteq_str("2000000", pxe_get_fdt_fallback(&label, kern_addr)); + + /* Test 4: Only fdtcontroladdr set - should return fdtcontroladdr */ + ut_assertok(env_set("fdt_addr", NULL)); + ut_asserteq_str("3000000", pxe_get_fdt_fallback(&label, kern_addr)); + + /* Restore env vars */ + ut_assertok(env_set("fdt_addr", orig_fdt_addr)); + ut_assertok(env_set("fdtcontroladdr", orig_fdtcontroladdr)); + + return 0; +} +PXE_TEST(pxe_test_fdt_fallback, 0); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that verifies that pxe_label_override can override the default boot label, and that an invalid override prints an error message. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 51 ++++++++++++++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 7 +++++ 2 files changed, 58 insertions(+) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index 505628ab92d..a6761ecc447 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -709,3 +709,54 @@ static int pxe_test_fdt_fallback(struct unit_test_state *uts) return 0; } PXE_TEST(pxe_test_fdt_fallback, 0); + +/** + * Test pxe_label_override environment variable + * + * This tests that pxe_label_override can override the default label, + * and that an invalid override prints an error message. + */ +static int pxe_test_label_override_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Set environment variables for file loading */ + ut_assertok(env_set_hex("pxefile_addr_r", PXE_LOAD_ADDR)); + ut_assertok(env_set_hex("kernel_addr_r", PXE_KERNEL_ADDR)); + ut_assertok(env_set_hex("ramdisk_addr_r", PXE_INITRD_ADDR)); + ut_assertok(env_set_hex("fdt_addr_r", PXE_FDT_ADDR)); + ut_assertok(env_set("bootfile", cfg_path)); + ut_assertok(env_set("pxe_timeout", "1")); + + /* Test 1: Override to 'local' label (localboot) */ + ut_assertok(env_set("pxe_label_override", "local")); + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + /* Should boot 'local' label instead of default 'linux' */ + ut_assert_skip_to_line("3:\tLocal Boot"); + ut_assert_skip_to_line("missing environment variable: localcmd"); + + /* Test 2: Invalid override - should print error */ + ut_assertok(env_set("pxe_label_override", "nonexistent")); + ut_assertok(run_commandf("sysboot host 0:0 any %x %s", + PXE_LOAD_ADDR, cfg_path)); + + ut_assert_skip_to_line("Missing override pxe label: nonexistent"); + + /* Clean up */ + ut_assertok(env_set("pxe_label_override", NULL)); + ut_assertok(env_set("pxe_timeout", NULL)); + + return 0; +} +PXE_TEST_ARGS(pxe_test_label_override_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index f3ae3ff9f78..8462b53c8d8 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -436,3 +436,10 @@ class TestPxeParser: with ubman.log.section('Test PXE ipappend'): ubman.run_ut('pxe', 'pxe_test_ipappend', fs_image=fs_img, cfg_path=cfg_path) + + def test_pxe_label_override(self, ubman, pxe_image): + """Test pxe_label_override environment variable""" + fs_img, cfg_path = pxe_image + with ubman.log.section('Test PXE label override'): + ubman.run_ut('pxe', 'pxe_test_label_override', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test that verifies the LMB allocation path works correctly when no address environment variables are set. The test uses a custom getfile callback that: - Checks if *addrp is 0 (no env var was provided) - Assigns incrementing addresses (0x100, 0x200, etc.) to simulate LMB allocation The test verifies all pxe_context fields are set correctly after pxe_probe(): - Context setup fields (getfile, userdata, allow_abs_path, etc.) - Label selection (correct label chosen) - Kernel address and size - Initrd address and size - FDT fields (NULL/0 since fdt_addr_r not set) - Boot flags (restart, fake_go) Also reduce test fixture file sizes to minimal (just the identifier strings, without padding). Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/pxe.c | 162 +++++++++++++++++++++++++++++++ test/py/tests/test_pxe_parser.py | 9 +- 2 files changed, 169 insertions(+), 2 deletions(-) diff --git a/test/boot/pxe.c b/test/boot/pxe.c index a6761ecc447..9398cf5757f 100644 --- a/test/boot/pxe.c +++ b/test/boot/pxe.c @@ -16,6 +16,7 @@ #include <env.h> #include <fdt_support.h> #include <fs_legacy.h> +#include <image.h> #include <linux/libfdt.h> #include <mapmem.h> #include <net-common.h> @@ -760,3 +761,164 @@ static int pxe_test_label_override_norun(struct unit_test_state *uts) PXE_TEST_ARGS(pxe_test_label_override_norun, UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }, { "cfg_path", UT_ARG_STR }); + +/** + * struct pxe_alloc_info - context for the alloc test getfile callback + * + * @uts: Unit test state for assertions + * @next_addr: Next address to allocate (increments by 0x100 each call) + */ +struct pxe_alloc_info { + struct unit_test_state *uts; + ulong next_addr; +}; + +/** + * pxe_alloc_getfile() - Read a file, allocating address if not provided + * + * For files loaded via env vars (kernel, initrd, fdt), this verifies that + * *addrp is 0 (no environment variable set), then assigns an incrementing + * address to simulate LMB allocation. For the config file (which is loaded + * with a direct address), it just uses the provided address. + */ +static int pxe_alloc_getfile(struct pxe_context *ctx, const char *file_path, + ulong *addrp, ulong align, + enum bootflow_img_t type, ulong *sizep) +{ + struct pxe_alloc_info *info = ctx->userdata; + loff_t len_read; + int ret; + + /* + * Config file is loaded with direct address (non-zero). + * Kernel/initrd/fdt/overlays come through env vars - if not set, + * addrp will be 0 and we need to allocate. + */ + if (!*addrp) { + *addrp = info->next_addr; + info->next_addr += 0x100; + } + + ret = fs_set_blk_dev("host", "0:0", FS_TYPE_ANY); + if (ret) + return ret; + ret = fs_legacy_read(file_path, *addrp, 0, 0, &len_read); + if (ret) + return ret; + *sizep = len_read; + + return 0; +} + +/** + * Test file loading with no address environment variables + * + * This tests the LMB allocation path where if no address env var is set, + * the getfile callback receives *addrp == 0 and must allocate memory. + * Our test callback assigns incrementing addresses (0x100, 0x200, etc.) + * and verifies the addresses are then stored in ctx. + */ +static int pxe_test_alloc_norun(struct unit_test_state *uts) +{ + const char *orig_fdt_addr, *orig_fdtcontroladdr; + const char *fs_image = ut_str(PXE_ARG_FS_IMAGE); + const char *cfg_path = ut_str(PXE_ARG_CFG_PATH); + struct pxe_alloc_info info; + struct pxe_context ctx; + ulong addr; + int ret; + + ut_assertnonnull(fs_image); + ut_assertnonnull(cfg_path); + + info.uts = uts; + info.next_addr = 0x100; + + /* Bind the filesystem image */ + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + + /* Save and clear FDT fallback env vars (fdtcontroladdr is set at boot) */ + orig_fdt_addr = env_get("fdt_addr"); + orig_fdtcontroladdr = env_get("fdtcontroladdr"); + ut_assertok(env_set("fdt_addr", NULL)); + ut_assertok(env_set("fdtcontroladdr", NULL)); + + /* Ensure address env vars are NOT set */ + ut_assertok(env_set("kernel_addr_r", NULL)); + ut_assertok(env_set("ramdisk_addr_r", NULL)); + ut_assertok(env_set("fdt_addr_r", NULL)); + ut_assertok(env_set("fdtoverlay_addr_r", NULL)); + ut_assertok(env_set("pxe_timeout", "1")); + + /* Set up the PXE context with our allocating getfile */ + ut_assertok(pxe_setup_ctx(&ctx, pxe_alloc_getfile, &info, true, + cfg_path, false, false, NULL)); + + /* Read the config file - use a fixed address for parsing */ + addr = PXE_LOAD_ADDR; + ret = get_pxe_file(&ctx, cfg_path, addr); + ut_asserteq(1, ret); + + /* Parse and probe - this triggers file loading */ + ut_assertok(pxe_probe(&ctx, addr, false)); + + /* + * Verify all pxe_context fields are set correctly. + * + * The background BMP is loaded first (0x100), then the default + * label 'linux' loads: kernel (0x200), initrd (0x300). + * + * Note: FDT loading requires fdt_addr_r to be set (checked in + * label_process_fdt before attempting to load), so conf_fdt_str + * and conf_fdt are NULL/0. + */ + + /* Context setup fields */ + ut_asserteq_ptr(pxe_alloc_getfile, ctx.getfile); + ut_asserteq_ptr(&info, ctx.userdata); + ut_asserteq(true, ctx.allow_abs_path); + ut_assertnonnull(ctx.bootdir); + ut_asserteq(0, ctx.pxe_file_size); /* only set by cmd/pxe.c */ + ut_asserteq(false, ctx.use_ipv6); + ut_asserteq(false, ctx.use_fallback); + ut_asserteq(true, ctx.no_boot); + ut_assertnull(ctx.bflow); + ut_assertnonnull(ctx.cfg); + + /* BMP loaded first */ + ut_asserteq(0x100, image_load_addr); + + /* Label selection */ + ut_assertnonnull(ctx.label); + ut_asserteq_str("linux", ctx.label->name); + + /* Kernel */ + ut_asserteq_str("200", ctx.kern_addr_str); + ut_asserteq(0x200, ctx.kern_addr); + ut_asserteq(6, ctx.kern_size); + + /* Initrd */ + ut_asserteq(0x300, ctx.initrd_addr); + ut_asserteq(7, ctx.initrd_size); + ut_asserteq_str("300:7", ctx.initrd_str); + + /* FDT (not loaded - no fdt_addr_r env var) */ + ut_assertnull(ctx.conf_fdt_str); + ut_asserteq(0, ctx.conf_fdt); + + /* Boot flags */ + ut_asserteq(false, ctx.restart); + ut_asserteq(false, ctx.fake_go); + + /* Clean up */ + destroy_pxe_menu(ctx.cfg); + pxe_destroy_ctx(&ctx); + ut_assertok(env_set("pxe_timeout", NULL)); + ut_assertok(env_set("fdt_addr", orig_fdt_addr)); + ut_assertok(env_set("fdtcontroladdr", orig_fdtcontroladdr)); + + return 0; +} +PXE_TEST_ARGS(pxe_test_alloc_norun, UTF_CONSOLE | UTF_MANUAL, + { "fs_image", UT_ARG_STR }, + { "cfg_path", UT_ARG_STR }); diff --git a/test/py/tests/test_pxe_parser.py b/test/py/tests/test_pxe_parser.py index 8462b53c8d8..de01f219244 100644 --- a/test/py/tests/test_pxe_parser.py +++ b/test/py/tests/test_pxe_parser.py @@ -320,7 +320,6 @@ def pxe_fdtdir_image(u_boot_config): # Create dummy kernel with open(os.path.join(fsh.srcdir, 'vmlinuz'), 'wb') as fd: fd.write(b'kernel') - fd.write(b'\x00' * (1024 - 6)) fsh.mk_fs() @@ -381,7 +380,6 @@ def pxe_error_image(u_boot_config): # Create dummy kernel with open(os.path.join(fsh.srcdir, 'vmlinuz'), 'wb') as fd: fd.write(b'kernel') - fd.write(b'\x00' * (1024 - 6)) fsh.mk_fs() @@ -443,3 +441,10 @@ class TestPxeParser: with ubman.log.section('Test PXE label override'): ubman.run_ut('pxe', 'pxe_test_label_override', fs_image=fs_img, cfg_path=cfg_path) + + def test_pxe_alloc(self, ubman, pxe_image): + """Test file loading with no address env vars (LMB allocation path)""" + fs_img, cfg_path = pxe_image + with ubman.log.section('Test PXE alloc'): + ubman.run_ut('pxe', 'pxe_test_alloc', + fs_image=fs_img, cfg_path=cfg_path) -- 2.43.0
participants (1)
-
Simon Glass