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