U-Boot https://u-boot.org Universal Bootloader Sun, 22 Feb 2026 15:52:32 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://u-boot.org/wp-content/uploads/2025/06/u-boot-logo-text-192-150x150.png U-Boot https://u-boot.org 32 32 Expanding U-Boot’s CI: Rust Demos, EFI App Boards and More https://u-boot.org/blog/expanding-u-boots-ci-rust-demos-efi-app-boards-and-more/ https://u-boot.org/blog/expanding-u-boots-ci-rust-demos-efi-app-boards-and-more/#respond Fri, 20 Mar 2026 21:52:13 +0000 https://u-boot.org/?p=1382 U-Boot’s CI pipeline has seen two rounds of improvements recently, adding test coverage for new platforms, the Rust ulib demo and EFI application boards.

Rust Toolchain and Ulib Demo Testing (ci/ulibd)

The first round added the Rust toolchain to the Docker CI image and enabled testing of the ulib Rust demo across multiple architectures. The demo now builds and runs under QEMU on x86 (32- and 64-bit), ARM64 and RISC-V 64, in both BIOS and EFI modes. A new qemu-x86_64_nospl board was also added to CI, testing x86_64 without SPL.

Other additions in this round include:

  • Arm FVP support — the CI container now includes TF-A builds and the Arm Fixed Virtual Platform, enabling vexpress_fvp and vexpress_fvp_bloblist test.py runs
  • qemu_arm_spl / qemu_arm64_spl boards added to the test matrix
  • localqemu marker — tests that launch their own QEMU instance are now marked so they can be skipped when running against a real lab board

EFI Application Board Testing (ulibe)

The second round adds full test.py coverage for U-Boot’s EFI application boards. These boards build U-Boot as a UEFI application (u-boot-app.efi) that is launched by platform firmware rather than running bare-metal.

To support this, a shared QEMU helper script (qemu.efi_app) was created. It stages the EFI binary and a startup.nsh into a FAT drive, sets up UEFI pflash firmware and launches QEMU — handling the differences between architectures (e.g. RISC-V needs blockdev-style pflash and virtio-blk).

Four new boards are thus now tested in CI:

  • efi-arm_app64 — AArch64 via AAVMF firmware
  • efi-riscv_app64 — RISC-V 64 via RISC-V UEFI firmware
  • efi-x86_app32 — x86 32-bit via OVMF
  • efi-x86_app64 — x86 64-bit via OVMF

To make this work, the UEFI firmware packages (AAVMF, OVMF, qemu-efi-riscv64) are now installed in the Docker image, and tests that are incompatible with EFI app boards are skipped using notbuildconfigspec(‘efi_app’).

Summary

Together these changes significantly broaden U-Boot’s automated test coverage for the EFI-app builds, catching regressions across more architectures and boot modes before they reach users.

]]>
https://u-boot.org/blog/expanding-u-boots-ci-rust-demos-efi-app-boards-and-more/feed/ 0
Devicetree in firmware or packaged with the OS? https://u-boot.org/blog/devicetree-in-firmware-or-packaged-with-the-os/ https://u-boot.org/blog/devicetree-in-firmware-or-packaged-with-the-os/#respond Tue, 17 Mar 2026 21:40:05 +0000 https://u-boot.org/?p=839 Linux needs deep understanding of the hardware it is running on. Without this it either cannot function, or cannot take full advantage of hardware features. While most peripherals can be probed to find out what they are (SCSI, USB, UFS, etc.), we need a way to know about the core hardware, such as the USB controller, GPIO controller and clocks .

For x86 devices, the intent was that “all internal devices are PCI and PCI is plug-and-play”, so Linux can just scan the hardware and figure everything out from there. This isn’t always realistic. PCI may tell Linux that there is a GPIO available. Linux can happily turn it on and off. But what does it do? Does it control the LCD backlight, power to the WiFi module or a halt-and-catch-fire circuit?

ACPI tables bridge this gap, providing some context on top of the devices, as well as a way to describe things which are not plug-and-play. All x86 devices include ACPI tables in their firmware, typically 100-500KB.

The devicetree approach

Since 2011 ARM devices have used devicetree. Devicetree is unlike ACPI in almost every respect, except that it also describes hardware. Since plug-and-play was considered too expensive for embedded systems (ROMs everywhere!) and PCI was too power-hungry, devicetree often describes a system in considerable detail.

Devicetree files are specific to each device, although large chunks are common across SoC vendors, SoCs, etc. A devicetree file can easily be 80KB. If you have ever downloaded the datasheet for a modern SoC you might be surprised to learn it isn’t 800KB. Watch this space.

In theory, devicetree describes the hardware. Since the non-plug-and-play hardware on your device doesn’t change after manufacture, the devicetree should be set in stone at the start, right? But is it?

What if a new Linux version adds support for an audio DSP that was previously ignored? Perhaps the original devicetree had a bug?

In the ACPI world, these sorts of things are dealt with using a firmware update, traditionally a rare, inconvenient and scary thing. Even then, this isn’t universally true, since Operating Systems include code to work around ACPI-table bugs, replace or augment tables, etc. But when a new feature is added to Windows, the OEM provides a closed-source Windows driver and the hardware just works, ACPI table or no.

So back to devicetree. One option is to ask the vendor to create a firmware update with the new devicetree. That might take a while. Worse, you may want to boot two different versions of Linux, but each needs a slightly different devicetree to work properly. This shouldn’t happen, but it is common enough that Linux distros typically ship the devicetree as part of the OS.

But distros need to support a large array of hardware, so this does not scale very well. For example, if you build arm64 Linux with ‘make image.fit’ the compressed devicetrees take up more space than the kernel itself. That’s just for the hardware that vendors have got around to upstreaming!

What to do?

Really, we need to provide a devicetree in firmware and allow the OS to provide an updated version, or perhaps an overlay with a few changes. Even better, perhaps we can create a disk partition for the devicetrees, so they can be updated independently of the firmware, perhaps by a vendor-specific download?

How can this be done?

With U-Boot, if you have the OS in one FIT and the devicetrees in another, it is actuall possible to load both with something like:

bootm ${kernel_addr_r}#conf1 – ${fdt_addr_r}#conf1

assuming that both FITs have been loaded into memory.

In practice this is not very convenient, since it requires handling the loading separately for the two images. The solution is probably to add a new bootmeth which understands this approach. Some prototyping has been done so watch this space!

]]>
https://u-boot.org/blog/devicetree-in-firmware-or-packaged-with-the-os/feed/ 0
U-Boot Library (ulib): From Sandbox to Real Hardware https://u-boot.org/blog/u-boot-library-ulib-from-sandbox-to-real-hardware/ https://u-boot.org/blog/u-boot-library-ulib-from-sandbox-to-real-hardware/#respond Sat, 14 Mar 2026 03:02:19 +0000 https://u-boot.org/?p=1368 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/Kbuild compiles the demo objects
  • arch/x86/Makefile re-links U-Boot with the demo objects using a new u-boot-link helper macro, so the example’s strong main() overrides the weak default
  • objcopy produces a flat binary, and binman packages it into demo.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.

]]>
https://u-boot.org/blog/u-boot-library-ulib-from-sandbox-to-real-hardware/feed/ 0
BLS Comes to U-Boot: A New Bootmeth https://u-boot.org/blog/bls-comes-to-u-boot-a-new-bootmeth/ https://u-boot.org/blog/bls-comes-to-u-boot-a-new-bootmeth/#respond Tue, 10 Mar 2026 18:56:03 +0000 https://u-boot.org/?p=1361 U-Boot has long supported extlinux-style boot configurations, but there is another widely-used standard for describing boot entries: the Boot Loader Specification (BLS). Fedora, RHEL and other distributions use BLS Type #1 entries to describe available kernels and their parameters. With this series, U-Boot gains native support for discovering and booting from these entries.

Why BLS?

The extlinux format works well, but BLS offers a different approach. Rather than a single configuration file listing all boot options, BLS uses individual entry files—one per kernel version—in a standardised directory layout. This design maps naturally to how distributions manage kernel packages: installing a new kernel simply drops a new .conf file into loader/entries/, without editing a shared configuration.

The specification also defines fields for architecture filtering, version-based sorting, and machine identification, providing structure that extlinux leaves to convention.

What’s Implemented

The new bootmeth_bls bootmeth integrates with U-Boot’s standard boot framework. During bootflow scan, it looks for a BLS entry file at loader/entry.conf on each bootdev partition. When found, it parses the entry and registers a bootflow that can be displayed in menus or booted directly.

The parser handles all the core BLS fields:

  • titleversion — identification and display
  • linux — kernel path, including FIT path#config syntax
  • options — kernel command line (multiple lines concatenated)
  • initrd — initial ramdisk (multiple lines supported)
  • devicetreedevicetree-overlay — device tree configuration
  • architecturemachine-idsort-key — parsed for future use

The bootmeth reuses U-Boot’s existing PXE infrastructure for the actual file-loading and boot execution, converting the BLS entry into a PXE label behind the scenes. This avoids duplicating the kernel/initrd/FDT loading logic and keeps the new code focused on parsing and discovery.

A U-Boot Extension: the ‘fit’ Field

FITs are central to U-Boot’s boot flow, but the BLS spec has no concept of them. The linux field can point to a FIT using path#config syntax, but this overloads the field’s meaning and makes it harder for the bootmeth to distinguish a plain kernel from a FIT.

To address this, the series adds a fit field as a U-Boot-specific extension. When present, fit takes priority over linux and explicitly identifies the image as a FIT:

title Ubuntu 24.04
version 6.8.0
fit /boot/ubuntu-6.8.0.fit
options root=/dev/sda3 ro quiet
initrd /boot/initrd-6.8.0.img

This is not part of the BLS spec yet, but it fits naturally into the key-value format and gives U-Boot the information it needs to handle FITs properly.

Multiple Initrds

The BLS spec allows multiple initrd lines, and distributions use this for supplementary initrds (microcode updates, overlays, etc.). The series extends the PXE infrastructure to support this: all initrd paths are collected into an alist and loaded consecutively in memory.

The first initrd is placed at ramdisk_addr_r as before (if provided). Subsequent initrds use LMB allocation, letting the memory allocator find suitable placement rather than manually calculating offsets.

What’s Next

The current implementation reads a single entry file at loader/entry.conf. Full BLS support would scan loader/entries/*.conf for multiple entries, sort them by version and sort-key, and filter by architecture. These are natural next steps.

Devicetree overlay application is another gap—the devicetree-overlay field is parsed but not yet acted upon during boot.

The series deliberately omits support for Unified Kernel Images (UKIs), which combine kernel, initrd, and boot stub into a single UEFI PE file. U-Boot’s FIT format already serves this role and more—it bundles kernel, initrd, device trees, and configurations into one signed image, and works in both EFI and non-EFI environments. FIT is the more powerful and flexible format than what is essentially a repurposed PE file.

Getting Started~

Enable BLS support with CONFIG_BOOTMETH_BLS=y – the bootmeth is ordered after extlinux by default, so existing setups are unaffected. Create a loader/entry.conf on a boot partition and U-Boot will discover it during bootflow scan.

Full documentation is available.

]]>
https://u-boot.org/blog/bls-comes-to-u-boot-a-new-bootmeth/feed/ 0
Pickman: Decomposing Mega-Merges https://u-boot.org/blog/pickman-decomposing-mega-merges/ https://u-boot.org/blog/pickman-decomposing-mega-merges/#comments Fri, 06 Mar 2026 21:33:41 +0000 https://u-boot.org/?p=1352 Introduction

Pickman is a tool for cherry-picking patches from upstream U-Boot into a downstream branch. It walks the first-parent chain of the source branch, finds merge commits, and cherry-picks their contents one merge at a time using an AI agent.

This works well for normal merges containing a handful of commits. But upstream U-Boot occasionally has mega-merges — large merges whose second parent itself contains sub-merges. A single Merge branch ‘next’ can pull in dozens of sub-merges, each with their own set of commits, totalling hundreds of patches.

Feeding all of these to the AI agent at once overwhelms it. The context window fills up, conflicts become harder to diagnose, and a single failure can require restarting a very large batch. A better approach is to break the mega-merge into smaller pieces and process them one at a time.

The problem

Consider a typical mega-merge on the first-parent chain:

    A---B---M---C---D      (first-parent / mainline)
            |
            S1--S2--S3     (second parent, containing sub-merges)

When pickman encounters merge M, it collects all commits in prev..M and hands them to the agent. For a normal merge this is fine, a few commits at most. But when S1, S2 and S3 are themselves merges, each containing many commits, the batch can be very large.

The solution

Two new functions handle this:

  • detect_sub_merges(merge_hash) – examines the second parent’s first-parent chain to find merge commits (sub-merges) within a larger merge. Returns a list of sub-merge hashes in chronological order, or an empty list if no sub-merges are present.
  • decompose_mega_merge(dbs, prev_commit, merge_hash, sub_merges) – returns the next unprocessed batch from a mega-merge.

The second function handles three phases:

  1. Mainline commits — commits between the previous position and the merge’s first parent (prev..M^1). These are on the mainline and are not part of any sub-merge.
  2. Sub-merge batches — one sub-merge at a time, skipping any whose commits are already tracked in the database. Each call returns the commits for the next unprocessed sub-merge.
  3. Remainder commits — any commits after the last sub-merge on the second parent’s chain.

The function tracks progress via the database: commits that have been applied (or are pending in an open MR) are skipped automatically. On the next invocation, pickman picks up where it left off.

The advance_to field in NextCommitsInfo tells the caller whether to update the source position. When processing sub-merge batches, it is None (stay put, more batches to come). When the mega-merge is fully processed, it contains the merge hash so the source advances past it.

The next-merges command also benefits: it now expands mega-merges in its output, showing each sub-merge with a numbered index:

    Next merges from us/next (15 from 3 first-parent):
      abc1234abcd Merge branch 'next' (12 sub-merges):
        1. def5678defg Merge branch 'dm-next'
        2. 1112222abcd Merge branch 'video-next'
        ...
      13. 333aaaa4444 Merge branch 'minor-fixes'
      14. 444bbbb5555 Merge branch 'doc-updates'

Rewind support

When something goes wrong partway through a mega-merge, it helps to be able to step backwards. The new rewind command walks back N merges on the first-parent chain from the current source position, deletes the corresponding commits from the database, and resets the source to the earlier position.

By default it performs a dry run, showing what would happen:

    $ pickman rewind us/next 2
    [dry run] Rewind 'us/next': abc123456789 -> def567890123
      Target: def5678901 Merge branch 'dm-next'
      Merges being rewound:
        abc1234abcd Merge branch 'video-next'
        bbb2345bcde Merge branch 'usb-next'
      Commits to delete from database: 47
    Use --force to execute this rewind

It also identifies any open MRs on GitLab whose cherry-pick branches correspond to commits in the rewound range, so that these can be cleaned up (normally deleted).

Summary

These changes allow pickman to handle the full range of upstream merge topologies with less manual intervention. Normal merges continue to work as before. Mega-merges are transparently decomposed into manageable batches, with progress tracked across runs and the ability to rewind when needed.

]]>
https://u-boot.org/blog/pickman-decomposing-mega-merges/feed/ 1
Running U-Boot x86_64 Directly from ROM Without SPL https://u-boot.org/blog/running-u-boot-x86_64-directly-from-rom-without-spl/ https://u-boot.org/blog/running-u-boot-x86_64-directly-from-rom-without-spl/#comments Tue, 03 Mar 2026 16:21:24 +0000 https://u-boot.org/?p=1323 Introduction

U-Boot on x86_64 has traditionally relied on a Secondary Program Loader (SPL) to bootstrap into 64-bit mode. SPL starts in 16-bit real mode (as required by the x86 reset vector), transitions through 32-bit protected mode, sets up page tables, and finally jumps into the 64-bit U-Boot proper.

A recent series adds support for running U-Boot directly from ROM on x86_64 without SPL, using QEMU as the development platform. While this is a simpler configuration, getting it to work required solving several interesting architectural challenges at the intersection of x86 hardware, compiler conventions, and virtual-machine emulation.

Why Skip SPL?

The SPL adds complexity and boot time. On platforms like QEMU where the firmware image runs directly from a flat ROM, the extra SPL stage is unnecessary. A single-binary U-Boot that transitions from 16-bit to 64-bit mode internally is simpler to build, debug, and deploy.

This also serves as a foundation for future work on other x86_64 platforms that may not need SPL.

The Challenges

Read-only .data in ROM

When running from ROM (SPI flash), the .data section is in read-only flash memory. The existing x86_64 code stored the global data (gd) pointer in a global variable, which lives in .data. Writing to it before relocation to RAM would fault.

The solution was to use MSR_FS_BASE to hold the gd pointer address, mirroring how 32-bit x86 uses the FS segment descriptor base. This is a CPU register, so it works regardless of whether .data is writable:

    static inline void set_gd(volatile gd_t *gd_ptr)
    {
        gd_t *p = (gd_t *)gd_ptr;

        p->arch.gd_addr = p;
        asm volatile("wrmsr" : :
            "c" (MSR_FS_BASE),
            "a" ((unsigned int)(unsigned long)&p->arch.gd_addr),
            "d" ((unsigned int)((unsigned long)&p->arch.gd_addr >> 32))
            : "memory");
    }

This seemingly simple change had a knock-on effect: EFI runtime services that accesses gd->relocaddr crashes when the Linux kernel called SetVirtualAddressMap(), because the kernel repurposes the FS register for its own per-CPU data. The fix was to cache gd->relocaddr in an __efi_runtime_data variable during U-Boot initialisation, before the kernel takes over.

Mixing 32-bit and 64-bit Code

The x86 reset vector runs in 16-bit real mode. U-Boot’s existing 16-bit startup code (start16.S, resetvec.S) is designed for 32-bit builds. On x86_64, the main binary is compiled as position-independent 64-bit code (PIE), which is fundamentally incompatible with 16-bit/32-bit startup code.

The solution was to compile the 16-bit startup objects as 32-bit code and link them into a separate 32-bit ELF, then extract just the raw binary from it. The build system includes this binary in the final ROM image at the correct reset vector address. This required several build-system changes:

  • Moving 16-bit binary rules from the top-level Makefile to arch/x86/Makefile
  • Compiling startup objects with explicit -m32 flags on x86_64
  • Linking them into a separate 32-bit ELF (u-boot-x86-start16.elf) distinct from the 64-bit main binary

32-bit to 64-bit Transition

A new assembly file (start_from_32.S) handles the transition from the 32-bit startup environment to 64-bit long mode:

  1. Build identity-mapping page tables in RAM (1 GiB pages for simplicity)
  2. Enable PAE (CR4.PAE) and load the page table base (CR3)
  3. Set the long-mode-enable bit in MSR_EFER
  4. Enable paging (CR0.PG), which activates long mode
  5. Load a 64-bit GDT and perform a far jump to the 64-bit entry point

Why not write this in C? Well, the page tables must be created before enabling 64-bit long mode, but the C code is compiled as 64-bit and cannot execute until long mode is active. Since the setup is just a few store-loops filling PML4 and PDPT entries, assembly is simpler than compiling and linking a separate 32-bit C function just for the page tables.

One subtle requirement emerged during testing with KVM: the GDT used for the mode transition must be in RAM, not ROM. The CPU performs an implicit data read from the GDT during the far jump to load the 64-bit code-segment descriptor. While normal instruction fetches from ROM work fine, KVM cannot service this implicit GDT read from the ROM region (an EPT mapping limitation?). The symptom is a silent hang at the far-jump instruction with no exception or output. The fix is to copy the GDT from ROM to RAM with rep movsl before loading it with lgdt.

SSE: A Hidden Requirement

x86_64 GCC assumes SSE2 is always available (it is part of the x86_64 baseline) and freely generates SSE instructions such as movq %xmm0. If the SSE control bits are not set in the CPU control registers, these instructions cause an invalid-opcode exception (#UD), manifesting as a triple fault and boot loop after relocation.

The startup code must set CR4.OSFXSR and clear CR0.EM before any compiler-generated code runs.

Regparm and Calling Conventions

The x86 32-bit builds use -mregparm=3 to pass function arguments in registers rather than on the stack, improving performance and code size. However, this is a 32-bit-only GCC option and is incompatible with x86_64 (which already uses registers by default per the System V AMD64 ABI). A new Kconfig option (X86_NO_REGPARM) allows disabling this for 32-bit builds. The provides better interoperability with Rust, for example.

The Result

The new qemu-x86_64_nospl board is a single U-Boot binary that boots directly from the QEMU ROM, transitions to 64-bit mode, and can launch an operating system directly or via EFI. It is tested in CI alongside the existing SPL-based configurations. CI tests confirm that it can boot Linux correctly.

To try it with the build-qemu script:

./scripts/build-qemu -a x86 -rsX       # TCG (software emulation)
./scripts/build-qemu -a x86 -rsXk      # KVM (hardware virtualisation) 

Series Overview

The series consists of 12 patches:

  1. Allow disabling regparm — adds X86_NO_REGPARM Kconfig option for x86_64 compatibility
  2. MSR_FS_BASE for gd pointer — eliminates the writable .data dependency for the global-data pointer on x86_64
  3. Cache gd->relocaddr for EFI — fixes the EFI runtime crash caused by the MSR_FS_BASE change
  4. Build-system changes — restructure 16-bit startup code compilation to support mixed 32/64-bit linking
  5. (continued)
  6. (continued)
  7. (continued)
  8. 32-to-64-bit startup code — the assembly that transitions from 32-bit protected mode to 64-bit long mode
  9. New defconfigqemu-x86_64_nospl board configuration
  10. MTRR setup — enable memory-type range register configuration for the no-SPL path
  11. build-qemu integration — add –no-spl (-X) option to the QEMU helper script
  12. CI coverage — add the new board to the continuous-integration test matrix

Debugging Tips

Debugging early x86 boot code in a virtual machine has its own set of tricks:

  • QEMU exception logging: qemu-system-x86_64 -d int -D /tmp/log.log logs all CPU exceptions. Search for v=06 (#UD), v=0e (#PF), v=0d (#GP).
  • Instruction tracing: add -d in_asm to trace executed instructions. Useful for finding where the CPU diverges from the expected path.
  • KVM limitation: -d int does not work with KVM. Use the QEMU monitor (-monitor telnet:localhost:4444,server,nowait) or serial-port output (outb to 0x3f8) instead.
  • Identifying SSE faults: look for v=06 (invalid opcode) in the exception log, then decode the instruction bytes (e.g. f3 0f 7e is the SSE movq instruction).

]]>
https://u-boot.org/blog/running-u-boot-x86_64-directly-from-rom-without-spl/feed/ 1
Streamlining U-Boot Workflows: Build and Summarize in One Shot https://u-boot.org/blog/streamlining-u-boot-workflows-build-and-summarize-in-one-shot/ https://u-boot.org/blog/streamlining-u-boot-workflows-build-and-summarize-in-one-shot/#comments Fri, 27 Feb 2026 12:56:02 +0000 https://u-boot.org/?p=1313 If you use U-Boot’s buildman tool frequently, you are likely familiar with the standard two-step dance. First, you run the build. Then, to really understand what happened—checking for code bloat, size changes, or new warnings—you run buildman -s to generate the summary.

While buildman effectively has two modes (building and summarising), treating them as mutually exclusive steps can be a hassle when you just want a quick health check on a branch.

A new patch aims to smooth out this workflow by adding a Build and Summary option.

The New -z Flag

The patch introduces a new command-line flag: -z (or --build-summary).

Previously, if you wanted to build a branch and immediately see the stats, you had to chain commands. Now, buildman can handle the compilation and immediately transition into generating the summary report within a single execution.

How it works

When you run buildman -z, the tool performs the build as usual. Once the build completes—regardless of whether it succeeded or failed—it automatically invokes the summary logic.

This is particularly useful for quickly iterating on a branch to reduce size growth.

The return code of the command still reflects the success or failure of the build.

Example

To build a branch for a specific board and see the size changes immediately:

buildman -b exph firefly-rk3399 -zSB

This simple addition removes friction from the development cycle, making it easier to keep an eye on binary sizes and warnings as you iterate.

]]>
https://u-boot.org/blog/streamlining-u-boot-workflows-build-and-summarize-in-one-shot/feed/ 1
U-Boot CLI Gets a Power-Up: Multi-level Undo/Redo and More https://u-boot.org/blog/u-boot-cli-gets-a-power-up-multi-level-undo-redo-and-more/ https://u-boot.org/blog/u-boot-cli-gets-a-power-up-multi-level-undo-redo-and-more/#respond Tue, 24 Feb 2026 16:40:31 +0000 https://u-boot.org/?p=1317 Have you ever found yourself wishing for a bit more “modernity” while editing environment variables or command strings at the U-Boot prompt? Our latest patch series brings a suite of enhanced editing features to U-Boot, designed to make the command-line experience much more forgiving and efficient.

While these features improve the standard CLI, they were primarily designed to empower the expo framework, specifically for the new multi-line textedit widget.


Key Features at a Glance

This series introduces several “quality-of-life” improvements that seasoned terminal users will recognize:

  • Multi-level Undo & Redo: Mistake? No problem. Use Ctrl+Z to undo and Ctrl+Shift+Z to redo.
  • Emacs-style Yank/Paste: Text deleted with “kill” commands (like Ctrl+K) is saved to a yank buffer and can be pasted back with Ctrl+Y.
  • Word Navigation: Move quickly through long strings using Ctrl+Left and Ctrl+Right arrows.
  • Enhanced Multi-line Support: For expo-based editors, Home and End now intelligently navigate within the current line, and Ctrl+K kills text to the end of the line rather than the entire buffer.
  • Sandbox SDL Support: We’ve added mapping for Home, End, and Ctrl+arrow keys in the sandbox console for easier testing and development.

Focus on Expo and Graphical Editing

The real star of this update is the integration with expo. We’ve added a new flag, SCENEOF_MULTILINE, which allows the textedit widget to handle multiple lines of text naturally—pressing Enter now inserts a newline rather than closing the widget.

To showcase this, we’ve updated the editenv command. You can now use the -e flag to trigger a graphical environment editor:

=> editenv -e my_long_variable

This opens a dedicated expo scene where you can use all the new undo/redo and navigation features in a clear, multi-line interface.


Configuration (Kconfig Options)

To enable these features in your build, you’ll need to look at these primary Kconfig options:

OptionDescription
CONFIG_CMDLINE_EDITORThe master switch for enhanced editing features. Enabled by default if EXPO is on.
CONFIG_CMDLINE_UNDOEnables the undo/redo and yank/paste buffers.
CONFIG_CMDLINE_UNDO_COUNTConfigures the depth of the undo ring buffer (Default: 64).
CONFIG_EXPO_EDITENVEnables the underlying graphical editor logic.
CONFIG_CMD_EDITENV_EXPOAdds the -e flag to the editenv command to use the expo editor.

Note on Memory: Each undo level stores a full copy of the edit buffer. While 64 levels is the default, you can tune CONFIG_CMDLINE_UNDO_COUNT to fit the memory constraints of your specific board.


Technical Details

The implementation involves a new cli_editor_state structure that manages the redirection of character output and navigation callbacks. This allows the CLI logic to remain clean while supporting the specific needs of a graphical UI like expo. We’ve also addressed several memory leaks in expo object destruction and improved the video test infrastructure to help debug these graphical features more easily.

]]>
https://u-boot.org/blog/u-boot-cli-gets-a-power-up-multi-level-undo-redo-and-more/feed/ 0
Cleaning Up ext4l: Organizing the Compatibility Layer https://u-boot.org/blog/cleaning-up-ext4l-organizing-the-compatibility-layer/ https://u-boot.org/blog/cleaning-up-ext4l-organizing-the-compatibility-layer/#respond Sat, 21 Feb 2026 04:27:43 +0000 https://u-boot.org/?p=1305 We’ve been working to improve the structure of the ext4l filesystem implementation in U-Boot, specifically targeting the large compatibility layer that allows us to reuse Linux kernel code.

We’ve just posted a new 33-patch series that reorganises the compatibility stubs, moving them out of the monolithic ext4_uboot.h and into their proper locations within include/linux/.

The Problem: A Monolithic Header

The ext4l filesystem uses a file named ext4_uboot.h to bridge the gap between U-Boot’s environment and the Linux kernel code it incorporates. To save time when porting ext4l, this header has become a dumping ground for every missing function, macro, and struct definition needed to make the code compile. It contains stubs for everything from inode operations to block device management, all mixed together.

This monolithic approach makes it difficult to see what was actually being stubbed and prevented other subsystems from using these compatibility definitions.

So why did we do it this way?

The header file has only been there a few months. Why didn’t we just put everything in the right place to begin with?

Well, it sounds easy, but actually it’s quite tricky. When porting ext4l there were thousands upon thousands of build errors and warnings. We could have brought in the Linux files one by one to try to get past the compilation step, i.e. to just be left with link errors. But the files continue a lot of stuff we don’t need in U-Boot, so that would have created lots of useless code to maintain.

Worse, most of that code would just add confusion, since there is no C implementation. Someone coming along later would see the prototype, call the function and wonder why it doesn’t work.

But the main issue was that the goal is to make the filesystem actually work. Until something is working, any stubbing or implementation work is speculative. The necessary pieces only fall into place when it is possible to mount a file system, list a directory, write to a file, etc.

So the approach chosen was to pull in, function by function, macro by macro, only what was needed to make the code build. This was still many weeks of work, not to mention around a week of time with Google Antimatter and Claude Code to build the initial prototype.

So ext4_uboot.h provided a feasible path to a functional implementation. The problem is, now we have an ext4_uboot.h hangover.

The Solution: Standard Linux Headers

This series takes a systematic approach to cleaning up this technical debt, building on half a dozen earlier series of a similar ilk. We move definitions from ext4_uboot.h to their standard Linux header locations in include/linux/

For example:

  • File and inode operations (struct file_operations, struct inode_operations) are now in linux/fs.h
  • Block device operations (blkdev_issue_discard, etc.) are now in linux/blkdev.h
  • Wait queue primitives (DEFINE_WAIT) are in linux/wait.h
  • DAX stubs are in linux/dax.h

We also created new headers where necessary, such as include/linux/fs/super_types.h for superblock operations, mirroring the kernel’s structure more closely.

Fixing list_sort

One notable fix in this series involves list_sort(). The function signature in U-Boot didn’t match the Linux kernel, lacking const qualifiers on the list heads in the comparison callback.

We updated list_sort() to match the kernel API:

typedef int (*list_cmp_func_t)(void *priv, const struct list_head *a,
                               const struct list_head *b);

This requires updating several callers across the codebase (UBIFS, EFI loader, STM32MP), but it ensures that we can use kernel code that relies on list_sort without modification.

Results

The result is a significant reduction in the size and complexity of ext4_uboot.h, shrinking it from over 550 lines to around 270 lines of essential, ext4-specific glue (at its peak the file held over 3000 lines!). The remaining code is much cleaner, with stubs residing where developers expect to find them.

This work completes the reorganisation of the ext4l compatibility layer, making future updates and maintenance significantly easier.

]]>
https://u-boot.org/blog/cleaning-up-ext4l-organizing-the-compatibility-layer/feed/ 0
Improving Text Editing in U-Boot’s Expo: Multiline Support and Cursor Independence https://u-boot.org/blog/improving-text-editing-in-u-boots-expo-multiline-support-and-cursor-independence/ https://u-boot.org/blog/improving-text-editing-in-u-boots-expo-multiline-support-and-cursor-independence/#respond Wed, 18 Feb 2026 04:16:31 +0000 https://u-boot.org/?p=1300 As the “Expo” menu system in U-Boot continues to mature, we are moving beyond simple menu selections and into more complex user interaction. One area needing significant attention is text input—specifically, how we handle multi-line text editing and the underlying video console cursor.

In a new 16-patch series, we overhaul the textedit object to support proper multiline editing and fundamentally change how we manage video console contexts.

The Cursor Conflict

Currently, the video console (vidconsole) operates with a single shared context for the cursor state. This works fine for a simple command line, but it becomes problematic when rendering complex UIs. If an Expo object needs to blink a cursor or accept input, it fights for control over the global cursor state. We typically have to rely on a clumsy save/restore mechanism (vidconsole_entry_save) to backup the console state before rendering a menu and restore it afterwards.

This series introduces independent vidconsole contexts.

Now, text-input objects (like textline and textedit) have their own dedicated vidconsole context. They maintain their own cursor position and state, completely independent of the main console or other objects. This allows us to delete the old entry_save and entry_restore mechanisms entirely, resulting in a nice cleanup of the video console drivers.

Multiline Navigation (Ctrl-P / Ctrl-N)

Editing text that spans multiple lines requires more than just moving forward and backward in a character buffer. Users expect to move visually up and down lines while maintaining their horizontal position—standard behaviour in any modern text editor.

We add support for visual line navigation using Ctrl-P (Previous line) and Ctrl-N (Next line).

Implementing this requires some interesting logic. Because the text wraps automatically based on the width of the container, “up” doesn’t mean “back 80 characters.” The implementation uses text measurement to calculate the pixel geometry of the wrapping. When a user moves up or down, we calculate the horizontal pixel offset of the current cursor, find the corresponding visual line, and place the cursor at the character index that matches that pixel alignment.

Key Changes Summary

  • Independent Contexts: We add vidconsole_ctx to decouple cursor state for different objects.
  • Cleanup: We remove vidconsole_entry_save/restore ops from console_normal and console_truetype.
  • Navigation: We add logic to cread_line_process_ch and Expo to handle visual line jumping.
  • Testing: We include tests in test/boot/expo.c covering keypress handling (Ctrl-B, Ctrl-F, Ctrl-W, etc.) and open/close cycles to ensure the display restores correctly.

This update paves the way for more sophisticated configuration screens within U-Boot, allowing users to edit longer configuration strings, scripts, or certificates directly from the bootloader UI.

]]>
https://u-boot.org/blog/improving-text-editing-in-u-boots-expo-multiline-support-and-cursor-independence/feed/ 0