[PATCH 00/13] ulib: Support building examples for x86
From: Simon Glass <simon.glass@canonical.com> This series extends ulib so that examples can be built and run on x86, not just sandbox. It refactors the demo to support both environments, extracts common binman ROM entries into a reusable template, adds build infrastructure for x86 examples, and includes a QEMU-based pytest for the resulting demo.rom image. Simon Glass (13): ulib: Add MAINTAINERS entry ulib: Use cross-toolchain objcopy for symbol redefinition examples: ulib: Support both sandbox and linked-in demo ulib: Make shared-lib deps conditional on ULIB_SHARED_LIB examples: ulib: Build shared-linked binaries only when .so is available efi: Allow runtime relocation in library mode ulib: Replace CONFIG_ULIB_JUMP_TO_MAIN with runtime ulib_has_main() Makefile: Extract u-boot-link helper for reuse x86: binman: Extract common ROM entries into a template x86: ulib: Add build infrastructure for example/ x86: ulib: Add demo.rom and enable examples for qemu-x86 Makefile: Only run standalone ulib examples for sandbox x86: ulib: Add pytest for demo.rom under QEMU Kconfig | 9 ---- MAINTAINERS | 11 +++++ Makefile | 28 ++++++++---- arch/x86/Makefile | 32 +++++++++++++ arch/x86/dts/u-boot.dtsi | 82 +++++++++++++++++++++------------ common/board_f.c | 2 +- common/board_r.c | 18 +++++++- configs/qemu-x86_defconfig | 1 + examples/Makefile | 4 ++ examples/ulib/Kbuild | 6 +++ examples/ulib/demo.c | 87 +++++++++++++++++++++--------------- examples/ulib/demo_helper.c | 5 +++ examples/ulib/rules.mk | 9 ++-- include/init.h | 11 +++++ lib/efi_loader/efi_runtime.c | 4 -- scripts/build_api.py | 18 +++++--- test/py/tests/test_ulib.py | 40 +++++++++++++++-- 17 files changed, 266 insertions(+), 101 deletions(-) create mode 100644 examples/ulib/Kbuild -- 2.43.0 base-commit: 3e9503f56997e202451c5b1b4308f34ef2c5fc79 branch: uliba
From: Simon Glass <simon.glass@canonical.com> There is no MAINTAINERS entry for the ulib subsystem, so patches do not get automatically routed. Add an entry covering the library, examples, tests and documentation. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index bda924912c1..62c98772441 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1875,6 +1875,17 @@ M: Neha Malcom Francis <n-francis@ti.com> S: Maintained F: drivers/ufs/ +ULIB +M: Simon Glass <sjg@chromium.org> +S: Maintained +T: git https://source.denx.de/u-boot/custodians/u-boot-dm.git +F: doc/develop/ulib.rst +F: examples/ulib/ +F: include/u-boot-lib.h +F: lib/ulib/ +F: test/py/tests/test_ulib.py +F: test/ulib/ + UPL M: Simon Glass <sjg@chromium.org> S: Maintained -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> build_api.py hardcodes the host 'objcopy' for symbol redefinition, which fails for cross-compiled targets (e.g. qemu_arm64) since the host objcopy cannot handle foreign ELF formats. Add an --objcopy argument to build_api.py and pass $(OBJCOPY) from the Makefile so the correct cross-toolchain binary is used. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- Makefile | 1 + scripts/build_api.py | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index e22c12da712..263914b3757 100644 --- a/Makefile +++ b/Makefile @@ -1891,6 +1891,7 @@ quiet_cmd_ulib-objs = OBJS $@ $(PYTHON3) $(srctree)/scripts/build_api.py \ $(srctree)/lib/ulib/rename.syms \ --redefine $$(cat $@.objlist) --output-dir $@.objdir \ + --objcopy $(OBJCOPY) \ $(if $(filter -j%,$(MAKEFLAGS)),--jobs $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS)))) \ > $@; \ rm -f $@.tmp $@.objlist diff --git a/scripts/build_api.py b/scripts/build_api.py index 7adc6c978a3..aa8b4f16a1a 100755 --- a/scripts/build_api.py +++ b/scripts/build_api.py @@ -310,7 +310,7 @@ class SymbolRedefiner: """ def __init__(self, syms: List[Symbol], outdir: str, max_workers, - verbose=False): + verbose=False, objcopy='objcopy'): """Initialize with symbols and output settings Args: @@ -318,11 +318,13 @@ class SymbolRedefiner: outdir (str): Directory to write modified object files max_workers (int): Number of parallel workers verbose (bool): Whether to show verbose output + objcopy (str): Path to objcopy binary """ self.syms = syms self.outdir = outdir self.verbose = verbose self.max_workers = max_workers + self.objcopy = objcopy self.redefine_args = [] self.symbol_names = set() @@ -339,7 +341,7 @@ class SymbolRedefiner: infile (str): Input object file path outfile (str): Output object file path """ - cmd = ['objcopy'] + self.redefine_args + [infile, outfile] + cmd = [self.objcopy] + self.redefine_args + [infile, outfile] subprocess.run(cmd, check=True, capture_output=True, text=True) if self.verbose: print(f'Copied and modified {infile} -> {outfile}') @@ -397,7 +399,8 @@ class SymbolRedefiner: return outfiles, modified @staticmethod - def apply_renames(obj_files, syms, outdir: str, max_workers, verbose=False): + def apply_renames(obj_files, syms, outdir: str, max_workers, + verbose=False, objcopy='objcopy'): """Apply symbol redefinitions to object files using objcopy Args: @@ -406,6 +409,7 @@ class SymbolRedefiner: outdir (str): Directory to write modified object files max_workers (int): Number of parallel workers verbose (bool): Whether to show verbose output + objcopy (str): Path to objcopy binary Returns: tuple[List[str], int]: List of output object file paths and @@ -414,7 +418,8 @@ class SymbolRedefiner: if not syms: return obj_files, 0 - redefiner = SymbolRedefiner(syms, outdir, max_workers, verbose) + redefiner = SymbolRedefiner(syms, outdir, max_workers, verbose, + objcopy) # Setup: create output directory and prepare work items os.makedirs(outdir, exist_ok=True) @@ -618,6 +623,8 @@ def parse_args(argv): help='Show verbose output') parser.add_argument('-j', '--jobs', type=int, metavar='N', help='Number of parallel jobs for symbol processing') + parser.add_argument('--objcopy', default='objcopy', + help='Path to objcopy binary (default: objcopy)') parser.add_argument('-P', '--processes', type=int, help='set number of processes to use for running tests') parser.add_argument('-t', '--test', action='store_true', dest='test', @@ -692,7 +699,8 @@ def main(argv=None): jobs = args.jobs if args.jobs else min(os.cpu_count() or 4, 8) start_time = time.time() outfiles, modified = SymbolRedefiner.apply_renames( - args.redefine, syms, args.output_dir, jobs, args.verbose) + args.redefine, syms, args.output_dir, jobs, args.verbose, + args.objcopy) # Print the list of output files for the build system to use if args.output_dir: print('\n'.join(outfiles)) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Refactor demo.c so it builds as a standalone sandbox binary (using ulib_init/ulib_uninit and os_* helpers) or as an example linked into U-Boot (providing a strong ulib_has_main() and main()). Extract demo_run() for the common path and use IS_ENABLED(CONFIG_SANDBOX) to gate the sandbox-specific initialisation and /proc/version reading. Update demo_helper.c to use printf() directly when built as part of U-Boot (CONFIG_ULIB) rather than the u-boot-api shim. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- examples/ulib/demo.c | 87 +++++++++++++++++++++---------------- examples/ulib/demo_helper.c | 5 +++ test/py/tests/test_ulib.py | 10 +++-- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/examples/ulib/demo.c b/examples/ulib/demo.c index 5077fcda0a6..0dbe5d233f5 100644 --- a/examples/ulib/demo.c +++ b/examples/ulib/demo.c @@ -2,66 +2,79 @@ /* * Demo program showing U-Boot library functionality * - * This demonstrates using U-Boot library functions in sandbox like os_* - * from external programs. + * This demonstrates using U-Boot library functions from external programs + * (sandbox) or as a standalone example linked into U-Boot. * * Copyright 2025 Canonical Ltd. * Written by Simon Glass <simon.glass@canonical.com> */ -#include <inttypes.h> -#include <stdbool.h> -#include <stdio.h> -#include <string.h> - #include <os.h> +#include <stdio.h> #include <u-boot-lib.h> #include <version_string.h> #include "demo_helper.h" +#ifndef CONFIG_SANDBOX +bool ulib_has_main(void) +{ + return true; +} +#endif + +static const char *get_version(void) +{ + if (IS_ENABLED(CONFIG_SANDBOX)) + return ulib_get_version(); + return version_string; +} + +static int demo_run(void) +{ + demo_show_banner(); + printf("U-Boot version: %s\n", get_version()); + printf("\n"); + + demo_add_numbers(42, 13); + demo_show_footer(); + + return 0; +} + +#ifdef CONFIG_SANDBOX int main(int argc, char *argv[]) { - int fd, result, lines = 0; + int fd, lines = 0; char line[256]; + int ret; - /* Init U-Boot library */ if (ulib_init(argv[0]) < 0) { fprintf(stderr, "Failed to initialize U-Boot library\n"); return 1; } - demo_show_banner(); - printf("U-Boot version: %s\n", ulib_get_version()); - printf("\n"); + ret = demo_run(); - /* Use U-Boot's os_open to open a file */ + /* Also demonstrate using U-Boot's os_* functions to read a file */ fd = os_open("/proc/version", 0); - if (fd < 0) { - fprintf(stderr, "Failed to open /proc/version\n"); - ulib_uninit(); - return 1; - } - - printf("System version:\n"); - - /* Use U-Boot's os_fgets to read lines */ - while (os_fgets(line, sizeof(line), fd)) { - printf(" %s", line); - lines++; + if (fd >= 0) { + printf("\nSystem version:\n"); + while (os_fgets(line, sizeof(line), fd)) { + printf(" %s", line); + lines++; + } + os_close(fd); + printf("\nRead %d line(s) using U-Boot library functions.\n", + lines); } - os_close(fd); - - printf("\nRead %d line(s) using U-Boot library functions.\n", lines); - - /* Test the helper function */ - result = demo_add_numbers(42, 13); - printf("Helper function result: %d\n", result); - - demo_show_footer(); - - /* Clean up */ ulib_uninit(); - return 0; + return ret; +} +#else +int main(void) +{ + return demo_run(); } +#endif diff --git a/examples/ulib/demo_helper.c b/examples/ulib/demo_helper.c index e3a2c6bdcfb..167bbd26f64 100644 --- a/examples/ulib/demo_helper.c +++ b/examples/ulib/demo_helper.c @@ -6,7 +6,12 @@ * Written by Simon Glass <simon.glass@canonical.com> */ +#ifndef CONFIG_ULIB #include <u-boot-api.h> +#else +#include <stdio.h> +#define ub_printf printf +#endif void demo_show_banner(void) { diff --git a/test/py/tests/test_ulib.py b/test/py/tests/test_ulib.py index 9b8b7097db4..9e46d529c87 100644 --- a/test/py/tests/test_ulib.py +++ b/test/py/tests/test_ulib.py @@ -53,18 +53,22 @@ def check_demo_output(ubman, out): with open('/proc/version', 'r', encoding='utf-8') as f: proc_version = f.read().strip() + # demo.c uses U-Boot's printf (compiled with U-Boot headers) while + # demo_helper.c uses glibc's printf, so their output streams are + # buffered separately. The helper output appears first, then the + # U-Boot printf output is flushed at exit. expected = [ 'U-Boot Library Demo Helper\r', '==========================\r', - 'System version:helper: Adding 42 + 13 = 55\r', + 'helper: Adding 42 + 13 = 55\r', '=================================\r', 'Demo complete\r', - f'U-Boot version: {ubman.u_boot_version_string}', + '\r', + f'System version:U-Boot version: {ubman.u_boot_version_string}', '', f' {proc_version}', '', 'Read 1 line(s) using U-Boot library functions.', - 'Helper function result: 55', '' ] -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The examples_ulib and examples_rust targets unconditionally depend on libu-boot.so, which is only buildable on sandbox (ULIB_SHARED_LIB depends on SANDBOX). Make the dependency conditional on CONFIG_ULIB_SHARED_LIB so the targets can also be built on other architectures such as x86. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 263914b3757..26ecf59a98a 100644 --- a/Makefile +++ b/Makefile @@ -1961,7 +1961,7 @@ test/ulib/ulib_test_static: test/ulib/ulib_test.o libu-boot.a \ # abspath is used since many paths are relative PHONY += examples_ulib -examples_ulib: libu-boot.a libu-boot.so FORCE +examples_ulib: libu-boot.a $(if $(CONFIG_ULIB_SHARED_LIB),libu-boot.so) FORCE $(Q)$(MAKE) -C $(srctree)/examples/ulib \ UBOOT_BUILD=$(abspath $(obj)) \ EXAMPLE_DIR=. \ @@ -1973,7 +1973,7 @@ examples_ulib: libu-boot.a libu-boot.so FORCE LIB_STATIC_LDS="$(abspath $(LIB_STATIC_LDS))" PHONY += examples_rust -examples_rust: libu-boot.a libu-boot.so FORCE +examples_rust: libu-boot.a $(if $(CONFIG_ULIB_SHARED_LIB),libu-boot.so) FORCE @if command -v cargo >/dev/null 2>&1; then \ $(MAKE) -C $(srctree)/examples/rust \ UBOOT_BUILD=$(abspath $(obj)) \ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The shared-linked example binaries (demo, boot) are only useful when libu-boot.so exists. Use $(wildcard) to conditionally include them, so standalone builds also adapt correctly. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- examples/ulib/rules.mk | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/ulib/rules.mk b/examples/ulib/rules.mk index c4b0bdb3668..bc677488649 100644 --- a/examples/ulib/rules.mk +++ b/examples/ulib/rules.mk @@ -5,9 +5,12 @@ # Copyright 2025 Canonical Ltd. # Written by Simon Glass <simon.glass@canonical.com> -# Generate normal and statically linked binary names from progs variable -all_bins := $(foreach prog,$(progs),$(OUTDIR)/$(prog) \ - $(OUTDIR)/$(prog)_static) +# Generate binary names from progs variable +# Shared-linked versions are only built when libu-boot.so is available +static_bins := $(foreach prog,$(progs),$(OUTDIR)/$(prog)_static) +shared_bins := $(if $(wildcard $(UBOOT_BUILD)/libu-boot.so),\ + $(foreach prog,$(progs),$(OUTDIR)/$(prog))) +all_bins := $(shared_bins) $(static_bins) # Default target builds both programs all: $(all_bins) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> EFI runtime relocation is needed even when U-Boot runs in library mode, so remove the early return that skips it when gd_ulib() is set. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- lib/efi_loader/efi_runtime.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c index bd09d78d047..8c0301e3918 100644 --- a/lib/efi_loader/efi_runtime.c +++ b/lib/efi_loader/efi_runtime.c @@ -718,10 +718,6 @@ static __efi_runtime void efi_relocate_runtime_table(ulong offset) /* Relocate EFI runtime to uboot_reloc_base = offset */ void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map) { - /* Skip EFI runtime relocation when running as a library */ - if (gd_ulib()) - return; - /* * Cache gd->relocaddr for use by the EFI runtime services after * the OS has taken over. On architectures where 'gd' is accessed -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The Kconfig option CONFIG_ULIB_JUMP_TO_MAIN is a build-time switch that cannot vary per-binary when multiple images share the same config. Replace it with a weak ulib_has_main() function that returns false by default. Example programs override it to return true, so the decision to set GD_FLG_ULIB and jump to main() is made at link time rather than build time. Add a weak main() fallback that enters the normal command loop. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- Kconfig | 9 --------- common/board_f.c | 2 +- common/board_r.c | 18 +++++++++++++++++- include/init.h | 11 +++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Kconfig b/Kconfig index 011f248ba03..ccd62220f0e 100644 --- a/Kconfig +++ b/Kconfig @@ -129,15 +129,6 @@ config ULIB_SHARED_LIB available for SANDBOX builds since shared libraries are not meaningful for bare-metal targets. -config ULIB_JUMP_TO_MAIN - bool "Set GD_FLG_ULIB flag in early boot" - depends on ULIB - help - Enable this to set the GD_FLG_ULIB flag in the global data structure - during early boot (in start.S before calling board_init_f). This allows - U-Boot code to detect that it's running as a library from the very - beginning of initialization. - config OPTIMIZE_INLINING bool "Allow compiler to uninline functions marked 'inline' in full U-Boot" help diff --git a/common/board_f.c b/common/board_f.c index 448cf2e319a..4558fdf297a 100644 --- a/common/board_f.c +++ b/common/board_f.c @@ -1031,7 +1031,7 @@ void board_init_f(ulong boot_flags) gd->flags = boot_flags; gd->flags &= ~GD_FLG_HAVE_CONSOLE; - if (IS_ENABLED(CONFIG_ULIB_JUMP_TO_MAIN)) + if (ulib_has_main()) gd->flags |= GD_FLG_ULIB; gd->boardf = &boardf; diff --git a/common/board_r.c b/common/board_r.c index 53851504c8f..ce5aa28b1e5 100644 --- a/common/board_r.c +++ b/common/board_r.c @@ -771,6 +771,22 @@ static void initcall_run_r(void) INITCALL(run_main_loop); } +__weak bool ulib_has_main(void) +{ + return false; +} + +#ifdef CONFIG_ULIB +__weak int main(void) +{ + /* No example linked -- fall through to normal command loop */ + for (;;) + main_loop(); + + return 0; +} +#endif + void board_init_r(gd_t *new_gd, ulong dest_addr) { /* @@ -803,7 +819,7 @@ void board_init_r(gd_t *new_gd, ulong dest_addr) if (gd_ulib()) { #ifdef CONFIG_ULIB /* handle __noreturn attribute */ - if (!IS_ENABLED(CONFIG_ULIB_JUMP_TO_MAIN)) + if (!ulib_has_main()) return; #endif main(); diff --git a/include/init.h b/include/init.h index 4e2155abb22..8a4e23403cc 100644 --- a/include/init.h +++ b/include/init.h @@ -392,6 +392,17 @@ int do_bdinfo(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); */ int ulib_init_with_data(char *progname, struct global_data *data); +/** + * ulib_has_main() - check whether an example main() is linked + * + * This weak function returns false by default. Example programs override it + * to return true so that U-Boot can set GD_FLG_ULIB and jump to main() after + * initialisation. + * + * Return: true if an example main() is present, false otherwise + */ +bool ulib_has_main(void); + /** * main() - main program called from ulib * -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Factor the u-boot link command into a parameterised u-boot-link macro that accepts extra objects and a map-file path. The existing cmd_u-boot__ calls it with empty extra objects and u-boot.map, so behaviour is unchanged. This allows arch/x86 example builds to reuse the same link logic to produce binaries that override weak symbols in the main u-boot archive. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- Makefile | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 26ecf59a98a..e61e1c7fe94 100644 --- a/Makefile +++ b/Makefile @@ -1831,33 +1831,40 @@ endif # Rule to link u-boot # May be overridden by arch/$(ARCH)/config.mk +# +# u-boot-link is a parameterised helper shared with arch/x86 example builds: +# $(1) - extra objects to link alongside $(u-boot-init) (empty for u-boot) +# $(2) - map-file path ifeq ($(LTO_ENABLE),y) quiet_cmd_u-boot__ ?= LTO $@ - cmd_u-boot__ ?= \ +define u-boot-link touch $(u-boot-main) ; \ $(CC) -nostdlib -nostartfiles \ $(LTO_FINAL_LDFLAGS) $(c_flags) \ $(KBUILD_LDFLAGS:%=-Wl,%) $(LDFLAGS_u-boot:%=-Wl,%) -o $@ \ - -T u-boot.lds $(u-boot-init) \ + -T u-boot.lds $(u-boot-init) $(1) \ -Wl,--whole-archive \ $(u-boot-main) \ $(u-boot-keep-syms-lto) \ $(PLATFORM_LIBS) \ -Wl,--no-whole-archive \ - -Wl,-Map,u-boot.map; \ + -Wl,-Map,$(2); \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) +endef else quiet_cmd_u-boot__ ?= LD $@ - cmd_u-boot__ ?= \ +define u-boot-link touch $(u-boot-main) ; \ - $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_u-boot) -o $@ \ - -T u-boot.lds $(u-boot-init) \ + $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_u-boot) -o $@ \ + -T u-boot.lds $(u-boot-init) $(1) \ --whole-archive \ $(u-boot-main) \ --no-whole-archive \ - $(PLATFORM_LIBS) -Map u-boot.map; \ + $(PLATFORM_LIBS) -Map $(2); \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) +endef endif + cmd_u-boot__ ?= $(call u-boot-link,,u-boot.map) quiet_cmd_smap = GEN common/system_map.o cmd_smap = \ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The x86 ROM image (u-boot.rom) contains a set of entries that are common to all x86 boards: the start16/reset16 vectors and image-header. These are currently inlined in the &rom node, making them hard to reuse. Add a 'binman' label to the binman node and use it to add a rom_common template containing these shared entries. The &rom node then pulls them in with insert-template, so board-specific dtsi files can also reference the template. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/x86/dts/u-boot.dtsi | 66 ++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/arch/x86/dts/u-boot.dtsi b/arch/x86/dts/u-boot.dtsi index c26b855edc9..630f85db31a 100644 --- a/arch/x86/dts/u-boot.dtsi +++ b/arch/x86/dts/u-boot.dtsi @@ -7,7 +7,7 @@ #include <config.h> / { - binman { + binman: binman { multiple-images; rom: rom { }; @@ -15,12 +15,43 @@ }; #ifdef CONFIG_ROM_SIZE +&binman { + rom_common: template { + end-at-4gb; + sort-by-offset; + pad-byte = <0xff>; + size = <CONFIG_ROM_SIZE>; +#ifdef CONFIG_TPL + x86-start16-tpl { + offset = <CONFIG_SYS_X86_START16>; + }; + x86-reset16-tpl { + offset = <CONFIG_RESET_VEC_LOC>; + }; +#elif defined(CONFIG_SPL) + x86-start16-spl { + offset = <CONFIG_SYS_X86_START16>; + }; + x86-reset16-spl { + offset = <CONFIG_RESET_VEC_LOC>; + }; +#else + x86-start16 { + offset = <CONFIG_SYS_X86_START16>; + }; + x86-reset16 { + offset = <CONFIG_RESET_VEC_LOC>; + }; +#endif + image-header { + location = "end"; + }; + }; +}; + &rom { filename = "u-boot.rom"; - end-at-4gb; - sort-by-offset; - pad-byte = <0xff>; - size = <CONFIG_ROM_SIZE>; + insert-template = <&rom_common>; #ifdef CONFIG_HAVE_INTEL_ME intel-descriptor { filename = CONFIG_FLASH_DESCRIPTOR_FILE; @@ -163,30 +194,5 @@ offset = <CFG_X86_REFCODE_ADDR>; }; #endif -#ifdef CONFIG_TPL - x86-start16-tpl { - offset = <CONFIG_SYS_X86_START16>; - }; - x86-reset16-tpl { - offset = <CONFIG_RESET_VEC_LOC>; - }; -#elif defined(CONFIG_SPL) - x86-start16-spl { - offset = <CONFIG_SYS_X86_START16>; - }; - x86-reset16-spl { - offset = <CONFIG_RESET_VEC_LOC>; - }; -#else - x86-start16 { - offset = <CONFIG_SYS_X86_START16>; - }; - x86-reset16 { - offset = <CONFIG_RESET_VEC_LOC>; - }; -#endif - image-header { - location = "end"; - }; }; #endif -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add the kbuild and Makefile plumbing to compile and link ulib example programs for x86: - examples/ulib/Kbuild compiles demo objects via kbuild (not linked into u-boot itself) - examples/Makefile hooks the ulib subdirectory for non-sandbox builds - arch/x86/Makefile re-links u-boot with the example objects using the u-boot-link helper, so the example's strong main() overrides the weak default, then objcopy produces a flat binary Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/x86/Makefile | 32 ++++++++++++++++++++++++++++++++ examples/Makefile | 4 ++++ examples/ulib/Kbuild | 6 ++++++ 3 files changed, 42 insertions(+) create mode 100644 examples/ulib/Kbuild diff --git a/arch/x86/Makefile b/arch/x86/Makefile index e8813aa7e28..7df20c43de1 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -64,3 +64,35 @@ u-boot-x86-start16.bin: u-boot-x86-16bit.elf FORCE u-boot-x86-reset16.bin: u-boot-x86-16bit.elf FORCE $(call if_changed,objcopy) endif + +# x86 example targets: re-link U-Boot with example objects providing main() +# +# The example .o files are compiled via kbuild (examples/ulib/Kbuild). +# This re-links u-boot with those objects so the example's strong main() +# overrides the weak one in board_r.c, using the shared u-boot-link helper. +ifdef CONFIG_EXAMPLES +INPUTS-$(CONFIG_ULIB) += examples_x86 + +PHONY += examples_x86 + +X86_EXAMPLES := demo + +quiet_cmd_u-boot-example = LD $@ + cmd_u-boot-example = $(call u-boot-link,$(example-objs),$@.map) + +# Per-example object lists (matches examples/ulib/Makefile:demo_objs) +example-demo-objs := examples/ulib/demo.o examples/ulib/demo_helper.o + +# Link each example ELF (depends on u-boot to ensure archives exist) +examples/ulib/demo: $(example-demo-objs) u-boot FORCE + $(eval example-objs := $(example-demo-objs)) + $(call if_changed,u-boot-example) + +# Binary targets (same objcopy flags as u-boot-nodtb.bin) +OBJCOPYFLAGS_demo-nodtb.bin = $(OBJCOPYFLAGS_u-boot-nodtb.bin) +examples/ulib/demo-nodtb.bin: examples/ulib/demo FORCE + $(call if_changed,objcopy) + +examples_x86: $(foreach e,$(X86_EXAMPLES),examples/ulib/$(e)-nodtb.bin) FORCE + @: +endif diff --git a/examples/Makefile b/examples/Makefile index 50bef4e6157..21c703d144b 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -8,4 +8,8 @@ endif subdir-$(EXAMPLES_STANDALONE) += standalone subdir-$(CONFIG_LEGACY_API) += api +# Sandbox examples use the standalone Makefile; other archs use kbuild +ifndef CONFIG_SANDBOX +subdir-$(CONFIG_ULIB) += ulib +endif endif diff --git a/examples/ulib/Kbuild b/examples/ulib/Kbuild new file mode 100644 index 00000000000..88d6a805b91 --- /dev/null +++ b/examples/ulib/Kbuild @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Example objects for x86 platforms (compiled via kbuild, not linked into +# u-boot). The standalone Makefile is used for sandbox builds instead. + +extra-y += demo.o demo_helper.o -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a demo-rom binman entry that reuses the rom_common template and packages the demo example binary alongside the device tree and fdtmap, producing a demo.rom that can be run on qemu-x86. Enable CONFIG_EXAMPLES in qemu-x86_defconfig so the demo is built by default. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/x86/dts/u-boot.dtsi | 18 ++++++++++++++++++ configs/qemu-x86_defconfig | 1 + 2 files changed, 19 insertions(+) diff --git a/arch/x86/dts/u-boot.dtsi b/arch/x86/dts/u-boot.dtsi index 630f85db31a..69925c49c49 100644 --- a/arch/x86/dts/u-boot.dtsi +++ b/arch/x86/dts/u-boot.dtsi @@ -195,4 +195,22 @@ }; #endif }; + +#ifdef CONFIG_EXAMPLES +/ { binman { + demo-rom { + filename = "demo.rom"; + insert-template = <&rom_common>; + + blob { + filename = "examples/ulib/demo-nodtb.bin"; + offset = <CONFIG_X86_OFFSET_U_BOOT>; + }; + u-boot-dtb { + }; + fdtmap { + }; + }; +}; }; +#endif #endif diff --git a/configs/qemu-x86_defconfig b/configs/qemu-x86_defconfig index 30b69908988..2275ff9de1a 100644 --- a/configs/qemu-x86_defconfig +++ b/configs/qemu-x86_defconfig @@ -14,6 +14,7 @@ CONFIG_SMP=y CONFIG_GENERATE_PIRQ_TABLE=y CONFIG_GENERATE_MP_TABLE=y CONFIG_ULIB=y +CONFIG_EXAMPLES=y CONFIG_FIT=y CONFIG_BOOTSTD_FULL=y CONFIG_BOOTSTAGE=y -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The examples_ulib target invokes the standalone Makefile in examples/ulib/ which uses sandbox-specific headers and linking flags (e.g. -lpthread, -ldl, arch/sandbox/include). Running this for non-sandbox builds like qemu-x86 causes compilation failures due to missing sandbox-specific CONFIG symbols and GD_SIZE mismatches. Non-sandbox architectures already build ulib examples via the kbuild path (examples/ulib/Kbuild + arch-specific re-linking), so guard the standalone Makefile target with CONFIG_SANDBOX. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index e61e1c7fe94..f1fdace661d 100644 --- a/Makefile +++ b/Makefile @@ -1061,11 +1061,13 @@ ifeq ($(NO_LIBS),) INPUTS-$(CONFIG_ULIB_SHARED_LIB) += libu-boot.so test/ulib/ulib_test INPUTS-$(CONFIG_ULIB) += libu-boot.a ifdef CONFIG_EXAMPLES +ifdef CONFIG_SANDBOX INPUTS-$(CONFIG_ULIB) += examples_ulib examples_rust endif endif endif endif +endif LDFLAGS_u-boot += $(LDFLAGS_FINAL) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> There is no test coverage for the demo.rom x86 image that packages the ulib example program. Add test_ulib_demo_rom() which boots demo.rom under qemu-system-i386 and verifies the expected demo output strings appear on the serial console. The test skips gracefully if demo.rom or QEMU is not available. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/py/tests/test_ulib.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/py/tests/test_ulib.py b/test/py/tests/test_ulib.py index 9e46d529c87..aff586c4d79 100644 --- a/test/py/tests/test_ulib.py +++ b/test/py/tests/test_ulib.py @@ -4,6 +4,7 @@ """Test U-Boot library functionality""" import os +import shutil import subprocess import pytest import utils @@ -208,3 +209,32 @@ def test_ulib_api_header(ubman): assert 'ub_printf(const char *fmt, ...)' in out assert 'ub_snprintf(char *buf, size_t size, const char *fmt, ...)' in out assert 'ub_vprintf(const char *fmt, va_list args)' in out + +@pytest.mark.boardspec('qemu-x86') +@pytest.mark.buildconfigspec("examples") +def test_ulib_demo_rom(ubman): + """Test the ulib demo ROM image under QEMU x86.""" + build = ubman.config.build_dir + demo_rom = os.path.join(build, 'demo.rom') + + assert os.path.exists(demo_rom), 'demo.rom not found in build directory' + assert shutil.which('qemu-system-i386'), 'qemu-system-i386 not found' + + cmd = ['qemu-system-i386', '-bios', demo_rom, '-nographic', + '-no-reboot'] + with subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as proc: + try: + stdout, _ = proc.communicate(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + stdout, _ = proc.communicate() + + out = stdout.decode('utf-8', errors='replace') + + assert 'U-Boot Library Demo Helper' in out + assert '==========================' in out + assert 'U-Boot version:' in out + assert 'helper: Adding 42 + 13 = 55' in out + assert '=================================' in out + assert 'Demo complete' in out -- 2.43.0
participants (1)
-
Simon Glass