One of the more ambitious initiatives in U-Boot recently is the U-Boot Library (ulib) — the ability to build U-Boot as a reusable library that external programs can link against. Until now, ulib has only worked on sandbox, U-Boot’s native host execution environment. This series takes the first step toward real hardware by bringing ulib examples to x86 and running them under QEMU.
What is ulib?
U-Boot contains a vast array of functionality: drivers, filesystems, networking, boot protocols, a CLI, devicetree support, and more. Ulib makes all of this available as a library — either a shared library (libu-boot.so) or a static archive (libu-boot.a) — so that external programs can reuse U-Boot’s capabilities without being built into a U-Boot image.
On sandbox, this means you can write a C or Rust program that calls ulib_init(), uses U-Boot’s printf, its OS abstraction layer, or any of its subsystems, and then calls ulib_uninit() when done. The build system handles the tricky problem of namespace collisions (U-Boot’s printf() vs libc’s printf()) through automatic symbol renaming — U-Boot’s version becomes ub_printf() in the library API.
But what about real hardware? On a real board there’s no host OS and no libc to collide with. The approach is different: instead of linking against a separate library, we link the example program into U-Boot itself. The example provides a strong main() function that overrides U-Boot’s weak default, so when the board boots, it runs the example instead of the normal command loop.
What this series does
This 13-patch series extends ulib from sandbox-only to x86, using qemu-x86 as the first target.
1. Runtime detection replaces build-time config
The old CONFIG_ULIB_JUMP_TO_MAIN Kconfig option was a build-time switch that couldn’t vary per-binary. It’s replaced with a weak ulib_has_main() function that returns false by default. Example programs override it to return true, so the decision happens at link time, not build time. This is essential for producing multiple images (normal u-boot.rom and demo.rom) from the same configuration.
2. Refactored demo for dual environments
The demo example (examples/ulib/demo.c) is refactored to work both ways:
// On sandbox: external program linking against libu-boot
#ifdef CONFIG_SANDBOX
int main(int argc, char *argv[])
{
ulib_init(argv[0]);
demo_run();
// ... read /proc/version using U-Boot's os_* functions ...
ulib_uninit();
}
#else
// On real hardware: linked into U-Boot, provides main()
int main(void)
{
return demo_run();
}
#endif
The common path, i.e. demo_run(), displays a banner, shows the U-Boot version, does some arithmetic, and prints a footer. Simple, but it exercises the full U-Boot initialisation path.
3. Reusable ROM template with binman
X86 ROM images share common duplication: start16/reset16 vectors, image headers, pad bytes. This series extracts those into a rom_common template that both the standard u-boot.rom and the new demo.rom can reference via insert-template. This avoids duplication in the devicetree and makes it easy to add more ROM variants in the future.
4. Build infrastructure
The x86 build uses a different approach to sandbox. Rather than a standalone Makefile that links against a pre-built library, it works within kbuild:
examples/ulib/Kbuildcompiles the demo objectsarch/x86/Makefilere-links U-Boot with the demo objects using a newu-boot-linkhelper macro, so the example’s strongmain()overrides the weak defaultobjcopyproduces a flat binary, and binman packages it intodemo.rom
The u-boot-link helper is extracted from the main Makefile so the same link logic is used for both the standard u-boot and the example builds.
5. QEMU pytest
The series includes a pytest that boots demo.rom under qemu-system-i386 and verifies the expected output:
@pytest.mark.boardspec('qemu-x86')
@pytest.mark.buildconfigspec("examples")
def test_ulib_demo_rom(ubman):
cmd = ['qemu-system-i386', '-bios', demo_rom, '-nographic',
'-no-reboot']
# ... run QEMU with 5-second timeout ...
assert 'U-Boot Library Demo Helper' in out
assert 'helper: Adding 42 + 13 = 55' in out
assert 'Demo complete' in out
Why this matters
Ulib on sandbox is useful for development and testing, but the real value comes from running on actual hardware. This x86 support is the first proof that the approach generalises beyond sandbox. The pattern — weak main(), link-time override, binman packaging — should transfer to ARM, RISC-V, and other architectures with relatively little arch-specific code.
The longer-term vision for ulib is to serve as a platform for innovation in boot firmware: writing boot flows in Rust, experimenting with new protocols, or building specialised firmware that reuses U-Boot’s driver model and hardware support without carrying the full U-Boot shell. Getting it working on real (or at least emulated) hardware is a necessary step on that path.
Try it yourself
# Build qemu-x86 with examples enabled
make qemu-x86_defconfig O=/tmp/b/qemu-x86
make -j$(nproc) O=/tmp/b/qemu-x86
# Boot the demo ROM
qemu-system-i386 -bios /tmp/b/qemu-x86/demo.rom -nographic -no-reboot
You should see the demo banner, version string, arithmetic result, and footer — all running on a bare-metal x86 QEMU instance, powered by U-Boot’s initialisation and driver model.
You can find out more about ulib from this post and the documentation.


