Introduction
Welcome, reader, to another Mind technical deep dive!
This post kicks off a new series where we walk through setting up an A/B boot and update system using qemu-x86_64.
The term “A/B” refers to a dual-partition storage setup that enables reliable firmware updates by keeping a fallback bootable copy of the system: Even if something goes wrong during an update, the system can still boot safely.
In this first article, we’ll explain what we mean with an A/B boot system and how this supports robustness during firmware updates. We’ll then configure Buildroot to create and deploy an A/B boot system using practical examples and shell commands in a qemu-x86_64 environment.
The upcoming posts in this series will guide you through testing, debugging, and refining your A/B boot configuration.
Context: FleetMinder, Device Fleet Simulation and Management
Our motivation for exploring A/B boot stems from FleetMinder — our in-house framework for testing and simulating software deployments across fleets of diverse embedded devices.
FleetMinder is still under active development. To kick off testing, we chose to first configure a qemu-x86_64 virtual setup. This then also serves as a launchpad for ARM64 and other qemu platforms later on. The instructions here require only minimal tweaks to work with qemu-arm64.
Simulating boot schemes — especially A/B setups — is an important element of fleet management and helps answer questions like:
- Will this update work on a device that skipped the last three firmware versions?
- Can the entire fleet continue functioning properly after multiple sequential updates?
- Is the update compatible with our backend systems?
- Should we update the backend as well?
Simulations let you explore these scenarios without any risk of bricking a device or halting operations.
What Do We Mean by A/B Boot?
A/B boot is a widely used technique in embedded systems to reduce the risk of failed boots after updates — whether due to power loss, flash corruption, or other issues.
The approach is straightforward: duplicate the bootable system components (such as kernel and the root filesystem) into two partitions — A and B. The system boots from one, updates the other, and then switches. If the new version fails to boot, the system falls back to the last known good one and notifies the operator.
Elegant in theory, tricky in practice.
Implementing A/B boot requires:
- A bootloader (like U-Boot) that can script boot logic and store current state persistently.
- Support for keeping application state consistent across updates.
- Considerations for security, such as verified or secure boot, with project-specific signature handling.
- Careful design if you ever want to update the bootloader itself without disrupting its current A/B state.
Even with tools like RAUC to manage and install update bundles, getting all this right is no small feat.
In short: A/B boot is resilient, but not plug-and-play.
This article lays the groundwork. We’ll use RAUC to create and install update bundles on the simulated embedded system, U-Boot to control the boot process and the selection of either the A or the B partition for booting, and Buildroot to build the entire system. Buildroot’s flexibility makes it ideal — abstracting away much of the boilerplate, while letting us dive deep into the details when needed.
Prerequisites
You’ll need the following on your Linux development system:
- A working qemu-x86_64 binary in your executable search path ($PATH),
- Standard build tools:
make,gcc,git, etc., - A Linux host for building and for running qemu,
opensslutilities,- An utility like
udisksctlto manage disk images.
Buildroot
Buildroot is a build system for generating complete embedded Linux images from source code. It handles:
- Cross-compilation toolchains,
- Bootloaders (like U-Boot),
- Linux kernels,
- User-space binaries,
- And bootable image creation.
Configuration is easy via the user-friendly menuconfig UI. The build steps and custom files can also be easily incorporated into the configuration, which is exactly what makes it a great fit for our A/B boot setup.
Let’s dive in.
Basic Setup of Buildroot
First create a working directory and call it blog-dir, then clone the Buildroot source code, and check out the 2025.02 release:
NOTE: Shell commands are typeset in a monospaced font.
mkdir blog-dir && cd blog-dir
git clone https://gitlab.com/buildroot.org/buildroot.git
cd buildroot
git checkout 2025.02
Next, load Buildroot’s default defconfig configuration for qemu-x86_64 and specify an out-of-tree build directory of blog-dir/br-qemu-x86_64 using the O= make argument:
make O=../br-qemu-x86_64 BR2_DEFCONFIG=configs/qemu_x86_64_defconfig defconfig
cd ..
A/B Boot Configuration
Setup
Prepare directories and placeholder files:
cd br-qemu-x86_64
# We are going to store various files specific to our configuration
mkdir -p ../board/qemu-x86_64-AB/overlay/mnt/uboot
mkdir -p ../board/qemu-x86_64-AB/overlay/data/rauc
echo 'echo empty' > ../board/qemu-x86_64-AB/ab-uboot.script
echo '#!/bin/bash' > ../board/qemu-x86_64-AB/post-image.sh
echo '#!/bin/bash' > ../board/qemu-x86_64-AB/post-build.sh
chmod +x ../board/qemu-x86_64-AB/post-image.sh
chmod +x ../board/qemu-x86_64-AB/post-build.sh
Menuconfig Customization
Run the menuconfig UI to customize the Buildroot configuration:
make menuconfig
Toolchain
Use an external toolchain to speed up builds:
NOTE: menuconfig navigation is typeset with a monospaced font and lists in a comment the configuration symbol it selects.
Toolchain > Toolchain type > External toolchain
#BR2_TOOLCHAIN_EXTERNAL=y
Toolchain > Toolchain > Bootlin toolchains
#BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
Toolchain > Bootlin toolchain variant > x86-64 glibc stable 2024.05-1
#BR2_TOOLCHAIN_EXTERNAL_BOOTLIN_X86_64_GLIBC_STABLE=y
U-Boot
Enable and configure U-Boot:
Bootloaders > U-Boot = y
#BR2_TARGET_UBOOT=y
Bootloaders > U-Boot > Build system = KConfig
#BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
Bootloaders > U-Boot > U-Boot version = 2025.01
#BR2_TARGET_UBOOT_CUSTOM_VERSION=y && BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE=2025.01
Bootloaders > U-Boot > Board defconfig = qemu-x86_64
#BR2_TARGET_UBOOT_BOARD_DEFCONFIG=qemu-x86_64
Bootloaders > U-Boot > U-Boot binary format > Custom (specify below) > U-Boot binary format: custom names = u-boot.rom
#BR2_TARGET_UBOOT_FORMAT_CUSTOM_NAME=u-boot.rom
Host Packages
Configure host-side packages that are required for generating the A/B boot system:
# Disable building of qemu to save time, we are going to use the qemu of our own system
Host utilities > host qemu = n
#BR2_PACKAGE_HOST_QEMU=n
#This will allow us to create update packages on the host
Host utilities > host rauc
# BR2_PACKAGE_HOST_RAUC=y
#The ab-uboot.script will be written later
Host utilities > host u-boot tools (=y) > Generate a U-Boot boot script (=y) > U-Boot boot script source =$(BASE_DIR)/../board/qemu-x86_64-AB/ab-uboot.script
#BR2_PACKAGE_HOST_UBOOT_TOOLS_BOOT_SCRIPT_SOURCE=$(BASE_DIR)/../board/qemu-x86_64-AB/ab-uboot.script
Host utilities > host genimage
#BR2_PACKAGE_HOST_GENIMAGE=y
Target Packages
Enable RAUC and its required features for A/B updates:
Target packages > System tools > rauc = y
#BR2_PACKAGE_RAUC=y
Target packages > System tools > rauc > dbus support = y
#BR2_PACKAGE_RAUC_DBUS=y
Target packages > System tools > rauc > GPT support = y
#BR2_PACKAGE_RAUC_GPT=y
Kernel
Use a modern kernel version and install it to /boot on the embedded system:
Kernel > Linux Kernel (=y) > Kernel version = 6.12.9
# BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=6.12.9
Kernel > Linux Kernel > Install kernel image to /boot in target = y
#BR2_LINUX_KERNEL_INSTALL_TARGET=y
Filesystem
Choose the ext4 filesystem:
Filesystem images > ext2/3/4 root filesystem > ext2/3/4 variant > ext4 = y
#BR2_TARGET_ROOTFS_EXT2_4=y
Post-build Customizations
Finally, configure your post-image build script and overlay files:
System configuration > Custom scripts to run after creating filesystem images = board/qemu/post-image.sh $(BASE_DIR)/../board/qemu-x86_64-AB/post-image.sh
#BR2_ROOTFS_POST_IMAGE_SCRIPT="board/qemu/post-image.sh $(BASE_DIR)/../board/qemu-x86_64-AB/post-image.sh"
System configuration > Custom scripts to run before creating filesystem images = board/qemu/post-image.sh $(BASE_DIR)/../board/qemu-x86_64-AB/post-build.sh
#BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/post-image.sh $(BASE_DIR)/../board/qemu-x86_64-AB/post-build.sh"
System configuration > Root filesystem overlay directories = $(BASE_DIR)/../board/qemu-x86_64-AB/overlay
#BR2_ROOTFS_OVERLAY=$(BASE_DIR)/../board/qemu-x86_64-AB/overlay
Our First Build
You’re ready to build! From the br-qemu-x86_64 directory:
make -j
The build may take a while depending on your hardware.
After the build has finished, you can launch qemu to boot either the built U-Boot loader or Linux system. While the infrastructure for an A/B setup is now in place, key components like partition layout and boot scripts aren’t yet implemented.
You can add this to the configuration as an exercise but keep in mind that these components will be discussed in the next article that explains how to implement the core of the A/B boot system.
Conclusion
This first article of the series laid the groundwork for implementing an A/B boot and update scheme using qemu-x86_64. We covered the concepts, introduced the toolchain, and configured Buildroot to support this goal.
Next up: diving into partitioning, scripting U-Boot, configuring RAUC, and stitching it all together into a working, fail-safe update system.
These topics will be discussed in the second article of this series.
Stay tuned!