Measuring expo performance

Expo is U-Boot’s menu- and GUI-layout system. It provides a set of objects like text, images and menu items. Expo allows these objects to be positioned on the display. Most importantly it can render the objects onto the display.

Input delays

The typical loop polls the expo for input (keyboard or mouse), does any required updates and then renders the expo on the display. This works fine, but is it efficient?

Recent work to add mouse support to expo exposed some shortcomings in the polling process. For example, keyboard input was waiting for up to 100ms for a key to be pressed in the EFI app. The original goal of this delay was to allow for escape sequences (such as are used by arrow keys, for example) to be fully captured in a single poll. But it slows the process down considerably.

This problem was fixed by commit a5c5b3b2fb6 (“expo: Speed up polling the keyboard”).

When to sync?

Another problem that came up was that the video system has its own idea of when to ‘sync’ the display. On most hardware this is a manual process. For sandbox, it involves drawing the contents on the U-Boot framebuffer on the SDL surface / display. On x86 devices it involves updating the framebuffer copy, used to improve performance with write-combining memory. On ARM devices it involves flushing the cache. Once these operations are completed, the changes become visible for the user.

In most cases syncing happens based on a a timer, e.g. a few times a second. In normal use this is fine since we don’t want to waste time updating the display when nothing has changed. A sync is also performed when U-Boot is idle, but in a tight polling loop it may never actually become idle.

In reality, expo should know whether anything has changed and some recent work has started the process of implementing that, making use of the video-damage work. In any case, when a mouse is present, expo wants to be as responsive as possible, so the mouse moves smoothly, rather than jerking from place to place 5 times a second.

Expo mode and manual sync

To resolve this a recent series in Concept adds support for an ‘expo mode’, where expo is in charge of syncing. It allows expo to initiate a video sync when it decides it wants to. The video sync is then done completely under expo control and the timer is not used.

Checking the frame rate

As it turns out, these changes exposed various problems with expo. To help with this a new ‘expo test’ mode was added in another series. This shows the frame rate and other useful information on the top right of the display, like this:

To enable it set the expotest environment variable to 1. From then on, your expo will show this information on the top right:

  • Frame number
  • Frames per second (rolling average over 5 seconds)
  • Render time in milliseconds
  • Sync time (i.e. display update)
  • Poll time (i.e. capturing keyboard and mouse input)

The information should help you track down any slow-downs in your drivers and expo itself!




New video command and unified embedded image handling

U-Boot has long supported embedding a graphical image directly into the binary – like the boot logo and the recently added BGRT (Boot Graphics Resource Table) image for EFI systems. But the way these images were handled was a bit of a mixed bag, with different patterns for different images and custom boilerplate for each one.

A new 6-patch series cleans this up, introducing a unified infrastructure for embedded images along with new commands to work with them.

What’s new

Unified image infrastructure

Previously, images were handled through ad-hoc Makefile rules that looked for specific patterns like _logo.bmp or _image.bmp. Each image required custom accessor macros and boilerplate code.

The new approach moves all embedded images into a single drivers/video/images/ directory and automatically generates linker list entries for them. This makes it trivial to add new images – just add obj-y += myimage.o to the Makefile and reference it using video_image_get(myimage, &size) or video_image_getptr(myimage).

The linker list infrastructure ensures that all images are discoverable at runtime, which enables the new video images command described below.

New video command

A new video command has been added with four subcommands:

  • video setcursor <col> <row> – Set cursor position (equivalent to existing setcurs command)
  • video puts <string> – Write string at current position (equivalent to existing lcdputs command)
  • video write -p [<col>:<row> <string>...] – Write string at a given position, either a character or a pixel position
  • video images – List all images compiled into U-Boot

The existing standalone setcurs and lcdputs commands remain available for backward compatibility.

Example usage

=> video images
Name                       Size
-------------------- ----------
bgrt                      43926
u_boot                     6932

Total images: 2

=> video setcursor 10 5
=> video puts "Hello U-Boot!"
=> video write -p a3:34 "Pixels"

The payoff

This series removes 46 lines of duplicate accessor code while adding about 500 lines total (mostly documentation and tests). But the real win is in maintainability:

  • Simpler to extend: Adding a new embedded image now requires just a single line in a Makefile
  • Discoverable: The video images command shows what’s available at runtime
  • Better organized: All images live in drivers/video/images/ rather than scattered across the tree
  • Consistent API: One pair of macros works for all images

The series also brings comprehensive documentation for the video commands (which previously had none) and adds tests to ensure everything works correctly.

If you’ve ever wanted to add a custom boot logo or wondered what images are built into your U-Boot binary, this series makes both much easier!




Enhancing EFI Boot and Developer Experience

We’ve just rolled out a series of updates aimed at improving the U-Boot EFI application, with a special focus on streamlining the testing and debugging process, particularly for ARM platforms. This batch of 24 patches introduces several quality-of-life improvements, from better debugging tools to more robust boot procedures. Let’s dive into the key changes.


Streamlining the Boot Process with ‘Fake Go’ 🚀

One of the standout features in this release is the introduction of a ‘fake go’ option for the boot process. Previously available only for tracing, this feature is now a standalone debugging tool enabled by CONFIG_BOOTM_FAKE_GO.

When you initiate a boot with the ‘fake go’ flag (e.g., bootflow boot -f or bootm fake), U-Boot performs all the necessary steps to prepare for booting an OS—loading the kernel, setting up the device tree, and preparing memory—but stops just short of jumping to the OS. This allows you to inspect the system’s state at the final moment before handoff, which is invaluable for debugging complex boot issues without needing a full OS boot cycle.


Pager Improvements for Better Interaction 📄

The console pager is a useful tool, but it can be cumbersome when you’re working without a serial console or need to quickly bypass lengthy output. We’ve introduced two new ways to control the pager on the fly:

  • Quit and Suppress (q): Pressing q at the pager prompt will now immediately stop all further output for the current command. This is perfect for when you’ve seen what you need and want to return to the prompt without sitting through pages of text.
  • Bypass Session (Q): Pressing Q will put the pager into bypass mode for the rest of your U-Boot session, allowing all subsequent commands to print their full output without interruption.

These small changes make console interaction much more fluid and give you greater control over command output.


Key Fixes and Enhancements 🛠️

Alongside these major features, this series includes a number of other smaller updates:

  • Safer Image Relocation on ARM: We’ve improved how kernel images are relocated on ARM. Instead of moving the image to a static offset, which could risk overwriting other critical data like the device tree, U-Boot now uses the LMB (Logical Memory Block) library to safely allocate a new, unused region of memory.
  • Improved Debugging Output: We’ve added more detailed debug messages throughout the boot process, especially in FIT image handling and device tree selection, making it easier to trace the boot flow and diagnose issues.
  • Cleaner ATAGs Messaging: The often-confusing “FDT and ATAGS support not compiled in” error has been clarified. U-Boot will now correctly report when a device tree is missing, preventing developers from going down the wrong path when debugging.
  • CI and Build Fixes: A few patches have been included to fix a bug in our automated release script that was causing CI failures, ensuring our development and release processes remain smooth.

These updates continue the development of the EFI app, while benefiting others boards as well.




Spring Cleaning: Refactoring the U-Boot Test Suite for a Brighter Future


A robust and efficient test suite is the backbone of a healthy open-source project. It gives developers the confidence to add new features and refactor code without causing regressions. Recently, we’ve merged a significant 19-patch series that begins a much-needed cleanup of our Python test infrastructure, paving the way for faster, more reliable, and more parallelizable tests.

The Old Way: A Monolithic Setup

For a long time, many of our tests, particularly those for the bootstd (boot standard) commands, have relied on a collection of disk images. These images simulate various boot scenarios, from Fedora and Ubuntu systems to ChromiumOS and Android partitions.

Our previous approach was to have a single, monolithic test called test_ut_dm_init_bootstd() that would run before all other unit tests. Its only job was to create every single disk image that any of the subsequent tests might need.

While this worked, it had several drawbacks:

  • Inefficiency: Every test run created all the images, even if you only wanted to run a single test that didn’t need any of them.
  • Hidden Dependencies: The relationship between a test and the image it required was not explicit. If an image failed to generate, a seemingly unrelated test would fail later, making debugging confusing.
  • No Parallelism: This setup made it impossible to run tests in parallel (make pcheck). Many tests implicitly depended on files created by other tests, a major barrier to parallel execution.
  • CI Gaps: Commands like make qcheck (quick check) and make pcheck were not being tested in our CI, which meant they could break and remain broken for long periods.

A New Direction: Embracing Test Fixtures

The long-term goal is to move away from this monolithic setup and towards using proper test fixtures. In frameworks like pytest (which we use), a fixture is a function that provides a well-defined baseline for tests. For us, this means a fixture would create a specific disk image and provide it directly to the tests that need it, and only those tests.

This 19-patch series is the first major step in that direction.


The Cleanup Process: A Three-Step Approach

The series can be broken down into three main phases of work.

1. Stabilization and Bug Squashing

Before making big changes, we had to fix the basics. The first few patches were dedicated to getting make qcheck to pass reliably. This involved:

  • Disabling Link-Time Optimization (LTO), which was interfering with our event tracing tools (Patch 1/19).
  • Fixing a memory leak in the VBE test code (Patch 2/19).
  • Standardizing how we compile device trees in tests to fix path-related issues (Patch 3/19).

2. Decoupling Dependent Tests

A key requirement for parallel testing is that each test must be self-contained. We found a great example of a dependency where test_fdt_add_pubkey() relied on cryptographic keys created by an entirely different test, test_vboot_base().

To fix this, we first moved the key-generation code into a shared helper function (Patch 6/19). Then, we updated test_fdt_add_pubkey() to call this helper itself, ensuring it creates all the files it needs to run (Patch 7/19). This makes the test independent and ready for parallel execution.

3. Preparing for Fixtures by Refactoring

The bulk of the work in this series was a large-scale refactoring of all our image-creation functions. Previously, functions like setup_fedora_image() took a ubman object as an argument. This ubman is a function-scoped fixture, meaning it’s set up and torn down for every single test. This is not suitable for creating images, which we’d prefer to do only once per test session.

The solution was to change the signature of all these setup functions. Instead of: def setup_fedora_image(ubman):

They now accept the specific dependencies they actually need: def setup_fedora_image(config, log, ...):

This was done for every image type: Fedora, Ubuntu, Android, ChromiumOS, EFI, and more. This change decouples the image creation logic from the lifecycle of an individual test run, making it possible for us to move this code into a session-scoped fixture in the future.

What’s Next?

This series has laid the groundwork. The immediate bugs are fixed, tests are more independent, and the code is structured correctly. The next step will be to complete the transition by creating a session-scoped pytest fixture that handles all this image setup work once at the start of a test run.

This investment in our test infrastructure will pay dividends in the form of faster CI runs, a more pleasant developer experience, and a more stable and reliable U-Boot. Happy testing! 🌱




Giving FIT-loading a Much-Needed Tune-Up

The U-Boot boot process relies heavily on the Flattened Image Tree (FIT) format to package kernels, ramdisks, device trees, and other components. At the heart of this lies the fit_image_load() function, which is responsible for parsing the FIT, selecting the right images, and loading them into memory.

Over the years, as more features like the “loadables” property were added, this important function grew in size and complexity. While it was a significant improvement over the scattered code it replaced, it had become a bit unwieldy—over 250 lines long! Maintaining and extending such a large function can be challenging.

Recognizing this, U-Boot developer Simon Glass recently undertook a refactoring effort to improve its structure and maintainability.


A Classic Refactor: Divide and Conquer

The core strategy of this patch series was to break down the monolithic fit_image_load() function into a collection of smaller, more focused helper functions. This makes the code easier to read, debug, and paves the way for future feature development.

The refactoring splits the loading process into logical steps, each now handled by its own function:

  • Image Selection: A new select_image() function now handles finding the correct configuration and image node within the FIT.
  • Verification and Checks: The print_and_verify() and check_allowed() functions centralize image verification and checks for things like image type, OS, and CPU architecture.
  • Loading and Decompression: The actual data loading and decompression logic were moved into handle_load_op() and decomp_image(), respectively.

Along with this restructuring, the series includes several smaller cleanups, such as removing unused variables and tidying up conditional compilation (#ifdef) directives for host builds.


Test Suite Improvements ⚙️

Good code changes are always backed by solid tests. This effort also included several improvements to the FIT test suite:

  • The test_fit() routine was renamed to test_fit_base() to prevent naming conflicts with other tests.
  • The test was updated to no longer require a full U-Boot restart, significantly speeding up test execution.
  • A new check was added to ensure U-Boot correctly reports an error when a required kernel image is missing from the FIT.

For a detailed look at all the changes, you can check out the merge commit or patches.




Streamlining the Final Leap: Unifying U-Boot’s Pre-OS Cleanup

What happens in the final moments before U-Boot hands control over to the operating system? Until recently, the answer was, “it’s complicated.” Each architecture like ARM, x86, and RISC-V had its own way of handling the final pre-boot cleanup, leading to a maze of slightly different functions and duplicated code. It was difficult to know what was really happening just before the kernel started.

Thanks to a recent series of commits in Concept, this critical part of the boot process has been significantly cleaned up and unified.

A Simpler, Centralized Approach

The core of this effort is the introduction of a new generic function: bootm_final(). This function’s purpose is to consolidate all the common steps that must happen right before booting an OS. By moving to this centralized model, we’ve replaced various architecture-specific functions, like bootm_announce_and_cleanup(), with a single, unified call.

This new approach has been adopted across the x86, RISC-V, and ARM architectures, as well as for the EFI loader.

Key Improvements in This Series

  • Unified Cleanup: Common tasks like disabling interrupts, quiescing board devices, and calling cleanup_before_linux() are now handled in one place, reducing code duplication and increasing consistency.
  • Better Bootstage Reporting: The EFI boot path now benefits from bootstage processing. If enabled, U-Boot will produce a bootstage report, offering better insights into boot-time performance when launching an EFI application. This report is emitted when exit-boot-services is called, thus allowing timing of GRUB and the kernel EFI stuff, if present.
  • Code Simplification: With the new generic function in place, redundant architecture-specific functions have been removed. We also took the opportunity to drop an outdated workaround for an old version of GRUB (EFI_GRUB_ARM32_WORKAROUND).

This cleanup makes the boot process more robust, easier to understand, and simpler to maintain. While there is still future work to be done in this area, this is a major step forward in standardizing the final hand-off from U-Boot to the OS.