Tidying up the FIT: Refactoring, Testing, and Shrinking U-Boot

Flattened Image Trees (FIT) are a cornerstone of modern U-Boot booting, offering a flexible way to package kernels, device trees, ramdisks, and firmware. However, the code responsible for printing information about these images—the output you see when running mkimage -l or iminfo—has been around for a long time.

As with any legacy code, it had become difficult to maintain. It lacked unit tests, relied on ad-hoc printing logic, and was cluttered within the massive boot/image-fit.c file.

This week, I submitted a 30-patch series titled “fit: Improve and test the code to print FIT info” to address these issues. Here is a look at what changed and why.

The Problem: Spaghetti Printing

Previously, the logic for printing FIT details was scattered. Functions manually handled indentation strings, and there were inconsistent printf calls for every property. If we wanted to change how a property was displayed or correct alignment, we had to touch multiple places in the code. Furthermore, there was no safety net; modifying the printing logic risked breaking the output parsing for users or scripts.

The Solution: Context and Helpers

The refactoring process followed a structured approach:

  1. Test First: Before touching the core logic, I added comprehensive tests. This includes a Python test (test_fit_print.py) that generates a FIT with various components (kernels, FDTs, signatures, loadables) and asserts the output is exactly as expected. This ensured that subsequent refactoring didn’t break existing functionality.
  2. Separation of Concerns: The printing code was moved out of boot/image-fit.c and into its own dedicated file, boot/fit_print.c.
  3. Context Structure: Instead of passing the FIT pointer and indentation strings recursively through every function, a new struct fit_print_ctx was introduced.
  4. Helper Functions: I introduced helpers like emit_label_val, emit_timestamp, and emit_addr. This replaced manual formatting with standardized calls, ensuring consistent alignment and handling of “unavailable” optional properties automatically.

AI-Assisted Development

An interesting aspect of this series is the use of AI assistance. You might notice the Co-developed-by: Claude tags in the commit log. The AI assisted in generating boilerplate, suggesting refactoring patterns, and ensuring coding standards were met, speeding up the iterative process of cleaning up the codebase.

The Results

The refactoring didn’t just make the code cleaner; it made it more efficient.

  • Binary Size Reduction: By removing duplicate printing logic and streamlining the flow, we see a binary-size reduction of approximately 320 bytes on aarch64 builds.
  • Better Output: The output columns are now strictly aligned, making it easier to read visually.
  • Maintainability: With the printing logic isolated and heavily tested, future changes to FIT reporting can be made with confidence.

The series is currently available on the mailing list for review.




An update on mouse support

Over the last few months (and since the last post) the mouse support in U-Boot Concept has matured quite a bit. The various performance improvements have had a big impact and the UI is now smooth and useable. Here’s a video:

So what’s next? Let’s look at a few topics.

Touchpads

So far touchpads are only supported in the EFI app, assuming that the underlying firmware enables this. On real hardware, such as the Qualcomm x1e laptops, this seems to work OK.

Support on other platforms requires a driver. The USB mouse driver (CONFIG_USB_MOUSE) might work, or it might not. If the touchpad is attached via I2C or SPI, then a different driver would be needed, perhaps with a special protocol for the device.

EFI app

So how do you the mouse or touchpad running on the EFI app? Just make sure that CONFIG_EFI_MOUSE is enabled when you build the U-Boot app, and all should be well.

Unfortunately the widely used ‘ovmf’ Debian package does not enable the mouse, or at least not properly. This is used for running EFI apps under QEMU. U-Boot’s build-efi script passes the required arguments, but this is not enough.

If you would like to build a version of EDK2 which supports the mouse / touchpad, it is quite simple. Just change these two files.

First, in OvmfPkg/Include/Dsc/UsbComponents.dsc.inc add this line, e.g. between UsbKbDxe and UsbMassStorageDxe so that it builds the mouse driver:

  MdeModulePkg/Bus/Usb/UsbMouseDxe/UsbMouseDxe.inf

Second, in OvmfPkg/OvmfPkgX64.fdf add this line, e.g. between UsbKbDxe and UsbMassStorageDxe so that the driver ends up in the firmware volume:

INF  MdeModulePkg/Bus/Usb/UsbMouseDxe/UsbMouseDxe.inf

Clicking in more places

So far, clicking in a lineedit object just places the cursor at the end of that object. Really it should set the cursor to the clicked position. This will become more important when the test editor is finished, since there may be a lot of text on the screen.

This is fairly simple to implement and should appear early in the new year.

Selecting text

A more ambitious feature would be text selection, where the mouse can be used to select part of a lineedit. This would need to be done as part of a copy/paste feature. It would make the text editor a little more functional, but it is not really core feature. If you are interested in implementing that, send an email to the U-Boot Concept mailing list!




Unlocking Modern Storage: U-Boot Adds LUKSv2 Support

We’re excited to announce that U-Boot concept has merged support for unlocking LUKSv2 encrypted partitions! This is a significant enhancement to U-Boot’s security capabilities, allowing it to handle the encryption standard used today by most current Linux distributions.

This 16-patch series (and a small follow-up) bring U-Boot up to speed with modern disk encryption, building on the existing luks unlock command.

Why LUKSv2?

While LUKSv1 was added initially, it was with the intention of taking this next step. LUKSv2 is the modern standard, offering superior security. The two key features U-Boot now supports from LUKSv2 are:

  1. Argon2id Key Derivation: LUKSv2 defaults to using Argon2id, the winner of the Password Hashing Competition. Unlike LUKSv1’s PBKDF2, Argon2id is a memory-hard function designed to be highly resistant to brute-force attacks using GPUs and ASICs. This series introduces the Argon2 library to U-Boot to handle this.
  2. XTS Cipher Mode: Support for the AES-XTS cipher mode (via mbedtls) has been added. XTS is the modern standard for disk encryption, providing stronger security guarantees than the older CBC mode.

The Implementation: A JSON-to-FDT Converter

One of the most interesting challenges in this series was handling the LUKSv2 metadata format. Unlike LUKSv1’s binary header, LUKSv2 stores its complex, hierarchical metadata as a JSON object.

As noted in the cover letter:

“One interesting part of this series is a converter from JSON to FDT, so that U-Boot’s existing ofnode interface can be used to access the hierarchical data in JSON text. This obviously results in quite a bit of new code, but it is more robust than trying to parse the text directly using strstr(), etc.”

This is the core of the new implementation. Instead of writing a new, complex JSON parser from scratch, a new function, json_to_fdt(), was created. This function parses the JSON text and converts it on-the-fly into a Flattened Device Tree (FDT) blob in memory.

From there, the LUKSv2 code can use U-Boot’s familiar and robust ofnode API (ofnode_find_subnode(), ofnode_read_string(), etc.) to navigate the metadata and retrieve keyslots, digests, and segment information. This approach is not only more reliable but also fits better within the existing U-Boot architecture.

How It Works

For the command interface, not much has changed. The existing luks unlock command just grows some new features:

  • It automatically detects whether the partition is LUKSv1 or LUKSv2.
  • If it’s LUKSv2, it will parse the JSON metadata.
  • If the keyslot uses Argon2id, it will use the new Argon2 library to derive the key.
  • If the partition uses XTS, it will use the newly enabled mbedtls functions to decrypt it.

Once unlocked, the encrypted partition is mapped as a blkmap device (e.g., blkmap 0), which you can then read from using standard commands like ext4load, fatload, or ls.

This work, along with the necessary documentation and test updates, makes U-Boot ready to boot from modern, secure, full-disk-encrypted systems.




A New Key in U-Boot: Introducing Support for the Tillitis TKey

A new patch series has landed in U-Boot concept, adding foundational support for the Tillitis TKey, an open-source USB security token. This series lays the groundwork for integrating hardware-backed security operations directly within the bootloader.

The TKey is an interesting piece of hardware. It’s a small, programmable USB device that contains a unique, internal secret key. When you plug it in, it starts in a firmware mode, ready to receive a small application. Once the application is loaded, the TKey transitions to an “app mode” where it can perform cryptographic tasks, like deriving keys, without ever exposing its core secret. When the key is removed, the application is erased, along with all data.

This initial 8-patch series introduces the necessary components to get started with the TKey in U-Boot.


What’s Included?

The support is built around a new tkey uclass, providing a standard driver model interface for communicating with the device. The series includes several key pieces:

  • A New tkey Command: A simple command-line tool to interact with the device. You can get device info, load applications, and derive keys.
  • Signer App Firmware: The series includes the binary for the standard “signer” application. This is the code that gets loaded onto the TKey to perform key derivation. You can build this yourself if you like, but for now U-Boot uses the vendor-provide version (see Tellitis TKey Signer).
  • User-Supplied Secret (USS) Derivation: To support the TKey’s key derivation mechanism, support for the blake2s hashing algorithm has been added. The TKey uses this to combine its internal secret with a user-provided password (the USS).
  • Sandbox Emulator and Driver: For development and testing, a full TKey emulator is included for the sandbox environment. Additionally, a sandbox driver that can talk to a real TKey over a serial port (/dev/ttyACM0) is provided, making it easy to test with actual hardware from a development machine.

How It Works: A Quick Example

The main goal of this feature is to derive a secret key that can be used for other purposes, like unlocking an encrypted disk. The workflow is straightforward. Using the new tkey command, you can provide a password (a “User-Supplied Secret” or USS):

Bash

=> tkey connect
Connected to TKey device
=> tkey getkey my-secret
Public Key: 505152535455565758595a5b5c5d5e5f505152535455565758595a5b5c5d5e5f
Disk Key: 228b2f6abf8be05649b2417586150bbf3e1b3f669afa1c6151ddc72957933c21
Verification Hash: a72a46b8f8c7ff0824416ada886f62b6c2808896d71201a32814ab432c7a81cf
=> 

The TKey loads the signer app using the USS, combines its internal secret with your password, and generates a unique public key. From this, a deterministic Disk Key is derived using a hash function. Because the TKey’s internal secret is unique and stable, the same password will always produce the same disk key on the same TKey, but a different key on any other TKey.

The command also outputs a Verification Hash. This can be stored and used later to check if a password is correct without having to perform a full decryption operation.


What’s Next?

This patch series provides the basic building blocks. It’s a first step toward more advanced and interesting security features. We’re looking forward to seeing how the community builds on this foundation, with potential integrations into secure boot flows, disk encryption unlocking with LUKS, and other security-sensitive operations.




Dumping expo contents

Expo is U-Boot’s forms and UI subsystem. It supports text, images and menus and a large expo can contain quite a bit of information. How do you debug and understand that? Tracing through large, linked data structures is not fun.

U-Boot Concept now has an expo_dump() function, designed to be called from your code when you are trying to figure out what is happening. It writes a dump of the expo to an abuf, something like the following:

Expo: name 'my menus'
  display lcd
  cons lcd.vidconsole_tt
  mouse (none)
  scene_id 0
  next_id 46
  req_width 0
  req_height 0
  text_mode 0
  popup 0
  show_highlight 0
  mouse_enabled 0
  mouse_ptr 0000000000000000
  mouse_size 0x0
  mouse_pos (0,0)
  damage (0,0)-(0,0)
  done 0
  save 0
  last_key_ms 4526828813
  video: 1366x768 white_on_black 0
  Theme:
    font_size 0
    white_on_black 0
    menu_inset 0
    menuitem_gap_y 0

Scenes:
  Scene 7: name 'main'
    title_id 0 ((none))
    highlight_id 0 ((none))
    Object 9 (logo): type image
      flags dirty
      bbox: (50,20)-(210,180)
      dims: 160x160
      Image: data ff000000
    Object 10 (text): type text
      flags dirty
      bbox: (400,100)-(526,140)
      dims: 126x40
      Text: str_id 20 font_name 'cantoraone_regular' font_size 40
        str 'my string'
    Object 11 (text): type text
      flags dirty
      bbox: (200,600)-(496,660)
      dims: 296x60
      Text: str_id 21 font_name 'nimbus_sans_l_regular' font_size 60
        str 'another string'
    Object 12 (text): type text
      flags size_valid, dirty
      bbox: (500,200)-(1000,350)
      dims: 477x240
      Text: str_id 22 font_name 'nimbus_sans_l_regular' font_size 60
        str 'this is yet
another string, with word-wrap and it goes on for quite a while'
    Object 13 (main): type menu
      flags size_valid, dirty
      bbox: (50,400)-(210,560)
      dims: 160x160
      Menu: pointer_id 45 title_id 14 manual 0
        Item 35: name 'item1' label_id 36 desc_id 37
        Item 40: name 'item2' label_id 41 desc_id 42
    Object 14 (title): type text
      flags dirty
      bbox: (50,400)-(122,418)
      dims: 72x18
      Text: str_id 24 font_name '(default)' font_size 0
        str 'Main Menu'
    Object 45 (cur_item): type text
      flags dirty
      bbox: (147,436)-(156,454)
      dims: 9x18
      Text: str_id 25 font_name '(default)' font_size 0
        str '>'
    Object 36 (label1): type text
      flags dirty
      bbox: (50,436)-(79,454)
      dims: 28x18
      Text: str_id 26 font_name '(default)' font_size 0
        str 'Play'
    Object 37 (item1-txt): type text
      flags dirty
      bbox: (227,436)-(316,454)
      dims: 89x18
      Text: str_id 27 font_name '(default)' font_size 0
        str 'Lord Melchett'
    Object 38 (item1-key): type text
      flags dirty
      bbox: (177,436)-(186,454)
      dims: 9x18
      Text: str_id 28 font_name '(default)' font_size 0
        str '1'
    Object 41 (label2): type text
      flags dirty
      bbox: (50,454)-(79,472)
      dims: 29x18
      Text: str_id 30 font_name '(default)' font_size 0
        str 'Now'
    Object 42 (item2-txt): type text
      flags dirty
      bbox: (227,454)-(316,472)
      dims: 70x18
      Text: str_id 31 font_name '(default)' font_size 0
        str 'Lord Percy'
    Object 43 (item2-key): type text
      flags dirty
      bbox: (177,454)-(186,472)
      dims: 9x18
      Text: str_id 32 font_name '(default)' font_size 0
        str '2'
    Object 15 (box): type box
      flags size_valid, dirty
      bbox: (500,200)-(1000,350)
      dims: 160x0
      Box: fill 0 width 3
    Object 16 (box2): type box
      flags 
      bbox: (0,0)-(0,0)
      dims: 160x0
      Box: fill 0 width 1
    Object 17 (editor): type textedit
      flags size_valid, dirty
      bbox: (100,200)-(400,650)
      dims: 289x54
      Textedit: str_id 23 font_name '(default)' font_size 0
    Object 18 (overlap): type text
      flags dirty
      bbox: (405,105)-(480,123)
      dims: 75x18
      Text: str_id 34 font_name '(default)' font_size 0
        str 'overlap text'

To print it, use puts() which has no line limits and will just dump the entire block to the console.

One nice thing about this dump is that you can compare it with a desired expo state.

What’s next? It would be nice to be able to move the mouse around and get information about the objects underneath the mouse. Another useful feature would be to write the dump to a file.

For now, it’s a start. Give it a try!




The RISC OS mouse pointer

My first ARM machine was an Archimedes way back in about 1987. I received the first unit sold in New Zealand. At some point my machine started running Acorn’s RISC OS. For me, some of the amazing things about RISC OS were anti-aliased outline fonts, a string-sound generator which worked without a data table and a really nice mouse pointer. There is a fairly recent article on The Register which talks about how it came to be.

Anyway, for U-Boot I’ve chosen to use this same pointer for the mouse. It has good contrast and has a cheerful colour schema. Of course you can change it if you like, but see what you think!




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!




Dealing with test images

Testing boot scenarios in very important but there are quite a lot of them! Concept currently has about 13 images containing various partitions and files used by the bootstd tests. These are listed in test.dts, for example:

	aliases {
...
		mmc7 = "/mmc7";
		mmc8 = "/mmc8";
...
	};

	/* This is used for Android boot image v4 tests */
	mmc7 {
		status = "disabled";
		compatible = "sandbox,mmc";
		filename = "mmc7.img";
	};

	/* This is used for Android boot image v2 tests. */
	mmc8 {
		status = "disabled";
		compatible = "sandbox,mmc";
		filename = "mmc8.img";
	};

All images are built by test_ut_dm_init_bootstd(), but most of these are disabled by default, to avoid making sandbox unwieldy and confusing. It also means that a test only has to worry about its particular disk image (plus a few common ones) and doesn’t have to be be updated every time a new test case is added in the code base.

But sometimes it is useful to enable a particular image. Tests do this individually, by enabling a devicetree node and then binding a device to it. But there is no way to do this from the command line. The only way is to edit test.dts to remove the status = "disabled" line for a node.

Until now. A very simple addition to sandbox allows an image to be enabled from the command line: the sb devon command. Simply specify the device name (e.g. mmc11) and it will set up a device. From then on the device appears as normal, so it can be used with the bootflow command, ls, etc.

When you have finished with the device sb devoff disables it again, putting things back to normal. Here is an example:

=> mmc list
mmc2: 2 (SD)
mmc1: 1 (SD)
mmc0: 0 (SD)
=> ls mmc b:1
** Bad device specification mmc b **
Couldn't find partition mmc b:1
=>
=> sb devon mmc11
Device 'mmc11' enabled
=> mmc list
mmc2: 2 (SD)
mmc1: 1 (SD)
mmc0: 0 (SD)
mmc11: 11 (SD)
=> ls mmc b:1    
            extlinux/
        7   initrd.img-6.8.0-53-generic
     1620   vmlinuz-6.8.0-53-generic

2 file(s), 1 dir(s)

=> sb devoff mmc11
Device 'mmc11' disabled
=> mmc list       
mmc2: 2 (SD)
mmc1: 1 (SD)
mmc0: 0 (SD)
=> 

Give it a try!




Unlocking Disks Earlier: Basic LUKS1 Support Arrives in U-Boot

A new 24-patch series in Concept that introduces basic support for unlocking LUKS1 encrypted partitions directly within U-Boot. This is a foundational step toward a more integrated and user-friendly full-disk encryption (FDE) experience.

🤔 The Problem with “Late” Unlocking

Traditionally, FDE on Linux systems is handled late in the boot process. U-Boot loads a kernel and an initial ramdisk (initramfs), and it’s the initramfs’s job to prompt the user for a passphrase and unlock the main root filesystem.

This common approach works, but it has several drawbacks:

  • Firmware is blind: U-Boot has no way of knowing if the boot will succeed until long after it has handed off control.
  • Confusing user experience: The passphrase prompt appears late in the boot sequence, sometimes after the vendor logo has disappeared.
  • No integrated UI: It’s not possible to create a single, polished firmware UI that handles both boot selection and disk decryption.
  • Inflexible for automation: In VM environments where a key might be known in advance, there’s no way for the firmware to use it, so the ramdisk must handle this through attestation, etc.
  • Ramdisk is required: You can’t boot from an encrypted disk unless you also use a ramdisk to perform the unlock.

💡 What’s New: The luks Command

This patch series takes a small step to improve this by bringing decryption capabilities into U-Boot itself. The new feature set is centered around the luks command, which allows U-Boot to interact with LUKS-encrypted partitions.

The command introduces three main subcommands:

  1. luks detect: Checks if a given partition is a valid LUKS device.
  2. luks info: Parses and displays the LUKS header metadata. This works for both LUKS1 and LUKS2 partitions, thanks to a new simple JSON parser included in the series for handling the LUKS2 header.
  3. luks unlock: unlocks a LUKS1 partition using a provided passphrase.

Here’s a look at it in action:

=> luks detect mmc 1:2
LUKS1 encrypted partition detected

=> luks info mmc 1:2
Version:         1
Cipher name:     aes
Cipher mode:     cbc-essiv:sha256
Hash spec:       sha256
Payload offset:  4096 sectors
Key bytes:       32

=> luks unlock mmc 1:2 mysecretpassphrase
Unlocked LUKS partition as blkmap device 'luks-mmc-1:2'
=>

🔓 Accessing Decrypted Data via blkmap

Once a partition is unlocked, how do you access the data? The luks unlock command integrates with U-Boot’s blkmap subsystem.

When a partition is successfully unlocked, a new virtual blkmap device is created. This device provides read-only access to the decrypted data on-the-fly.

This means you can now use standard U-Boot commands to read files directly from the encrypted partition, just as if it were a normal, unencrypted disk:

=> ls blkmap 0 /
          ./
          ../
          lost+found/
2481008   vmlinuz-6.8.0-53-generic
1616      initrd.img-6.8.0-53-generic

=> ext4load blkmap 0 ${kernel_addr_r} /vmlinuz-6.8.0-53-generic
2481008 bytes read in 64 ms (37.0 MiB/s)
=>

This simple feature allows U-Boot to load a kernel, read a configuration file, or access any other data from an encrypted volume before booting.

🛠️ Under the Hood: What Made This Possible

There is quite a bit of foundational work included in this series:

  • Crypto: The mbedtls library in U-Boot was enhanced to enable PKCS#5 (PBKDF2) functions, which are essential for deriving keys from passphrases in LUKS. A fix for AES-192 and AES-256 key-size handling was also included.
  • blkmap Enhancement: The blkmap driver was extended with a new blkmap_map_crypt() function to handle the on-the-fly decryption.
  • Testing: A large portion of the series is dedicated to building solid testing infrastructure. This includes updates to the Docker image, CI configuration and new Python test helpers (fs_helper.py) to create, encrypt, and test against real LUKS1 and LUKS2 disk images.

This series lays the groundwork for a more secure and streamlined boot process. While full LUKS2 unlock support and read-write access are topics for another day, this is a step forward.





Pass a boot command to QEMU!

The build-qemu script provides lots of useful features and is an easy way to run U-Boot under QEMU with an OS, with or without video, etc. Now in Concept it is possible to pass a boot command!

The -b/–bootcmd option creates a special ‘file’ within QEMU that contains the requested command. Then U-Boot uses an event in main.c to check for that file and read it. The command is then executed instead of the autoboot command. In effect, it provides a way to control what U-Boot does when it starts up.

Possible uses may include setting the bootmeth order to boot using extlinux instead of EFI.

Note that this does not affect the preboot command.

The main patch is the addition of a new EVT_BOOTCMD event which is emitted in main_loop() before starting the main CLI loop. Under the hood, QEMU provides the information in its internal filesystem, the the opt/u-boot/bootcmd file. You can see this using the ‘qfw’ command:

$ ./scripts/build-qemu -a x86 -rs -b 'echo hi'

U-Boot Concept SPL 2025.10
Trying to boot from SPI


U-Boot Concept 2025.10

CPU:   QEMU Virtual CPU version 2.5+
DRAM:  512 MiB
Core:  19 devices, 12 uclasses, devicetree: separate
Loading Environment from FAT... ** Bad device specification virtio 0 **
Model: QEMU x86 (Q35)
Net:   eth0: virtio-net#0

Hit any key to stop autoboot:  0 
hi
=> qfw list
    Addr     Size Sel Name
-------- -------- --- ------------
       0        0  20 bios-geometry                                           
       0        0  21 bootorder                                               
1ec21000       14  22 etc/acpi/rsdp                                           
1ec21040    20000  23 etc/acpi/tables                                         
       0        4  24 etc/boot-fail-wait                                      
       0       28  25 etc/e820                                                
       0       18  26 etc/smbios/smbios-anchor                                
       0      13b  27 etc/smbios/smbios-tables                                
       0        1  28 etc/smi/features-ok                                     
       0        8  29 etc/smi/requested-features                              
       0        8  2a etc/smi/supported-features                              
       0        6  2b etc/system-states                                       
       0     1000  2c etc/table-loader                                        
       0        0  2d etc/tpm/log                                             
       0     2400  2e genroms/kvmvapic.bin                                    
       0        7  2f opt/u-boot/bootcmd                                      
=> 

For now this option is only supported on x86, but ARM support should land in the next few weeks.

The build-efi script also supports this option for the app, but it is implemented in a completely different way. It creates an environment file on the FAT filesystem which U-Boot can read when it starts up. A later series provides this feature which is has also landed in Concept.

The ability to pass information from the host is a useful feature in QEMU. Keep it in mind if you are working in an emulation environment!