As part of a recent project at Bootlin, we implemented A/B Over-The-Air (OTA) updates on a a system based on the RaspberryPi 5 using RAUC. We ended up not using U-Boot as a bootloader and instead rely solely on the RaspberryPi firmware as a bootloader. This post will dive into the details of making this happen, and some advanced features of RAUC.
Interfacing with the bootloader
RAUC (Robust Auto-Update Controller) is a popular open-source controller software for safe and secure updates on embedded systems. RAUC can use an A/B scheme to deploy updates safely, meaning it will install an update on the alternate slot (the one not currently booted), then temporarily make it the slot to be booted next. If the slot fails to boot, it should be marked as bad, and the system should go back to the untouched, previously booted slot.
RAUC can of course not rely on the operating system itself, since it will typically be part of the update. It must rely on a piece of software that is loaded before, and has visibility into the operating system: the bootloader.
RAUC does this using bootloader backends, and supports 4 bootloaders: Barebox, U-Boot, GRUB, and (U)EFI. A large number of embedded systems use U-Boot, and this is what our project started with. Unfortunately, at the time, and still as of the time of writing, U-Boot does not have PCIe support for the Broadcom BCM2712, the SoC that is the RPi 5’s Application Processor. That is an issue in our case, because that is the interface used by the M.2 HAT+ to connect to the NVMe drive storing the operating system in our project.
The Raspberry Pi firmware as bootloader
Starting with the Raspberry Pi 4, the second stage bootloader (that will be running on the VideoCore GPU) is contained in an on-board EEPROM. It is also configurable via text files, the most important being autoboot.txt, config.txt and cmdline.txt.
One important feature of the bootloader is tryboot, which will use an RPi firmware flag to load an alternate configuration file.
This can be set from userland using the Mailbox property interface, e.g. via the Raspberry Pi utils’ vcmailbox utility. As this commit shows, the 0x00038064
tag will set the reboot flag: vcmailbox 0x00038064 4 0 1
.
This makes the Raspberry Pi firmware a good candidate for implementing A/B updates without U-Boot’s intervention.
RAUC’s interface to the RPi firmware
As mentioned before, RAUC needs an interface to the bootloader, but it does not currently have an official RPi firmware backend, though there is a pull request out for it. This means, that as of this writing, it needs a custom backend, which will:
- Implement
get-primary
by parsingautoboot.txt
to find the partition set in theall
section - Implement
set-primary
either setting thetryboot
flag usingvcmailbox
or by swapping the partition in theall
and thetryboot
sections ofautoboot.txt
configuration iftryboot
is already active (The RPi firmware sets that in the DTB, in/proc/device-tree/chosen/bootloader/tryboot)
- Implement
get-state
by checking which partition was booted. The RPi firmware sets that in the DTB, in/proc/device-tree/chosen/bootloader/partition
- Implement
set-state
by swapping the partition in theall
and thetryboot
sections ofautoboot.txt
configuration
Enabling kernel updates
When not using an intermediate bootloader such as U-Boot or an initramfs, the RPi firmware will be responsible for loading and jumping to the Linux kernel directly.
The kernel therefore needs to reside on a vFAT partition, which is the only filesystem the RPi firmware supports, next to the config.txt
configuration file. As it is unlikely on an embedded device that the root
filesystem will be vFAT, RAUC will need separate slots for the vFAT boot partition and the root filesystem. Because the RPi firmware will not have knowledge of the root filesystem, but only of the booting partition (the vFAT one), we can’t test just the root filesystem, we’ll be testing the kernel and root filesystem at the same time, and if one of them is broken, or they turn out to be incompatible, both will be compromised.
We can make this clear in the RAUC configuration by grouping a rootFS with a dedicated vFAT partition.
Each vFAT partition will therefore be associated for RAUC to a specific root filesystem partition. The RPi firmware will be aware of that association thanks to the cmdline.txt
file, by setting the root=
argument to the root filesystem partition.
Limitations and pitfalls
Unfortunately, the Raspberry Pi firmware does not expose any scripting capabilities, or as rich an environment as U-Boot does, of which RAUC could take advantage, which leads to some limitations that we will discuss in the following sections.
Maintaining cmdline.txt
When deploying an update, RAUC will overwrite the alternate (not currently booted) slot with the filesystem in the update bundle. If the update is deployed to a fleet of devices, there is no way to know whether any individual device will deploy the update to its A
or B
slot. However, if cmdline.txt
is overwritten with the wrong root=
value, the slot grouping on which RAUC is relying will be broken.
We could therefore be booted on the A
slot, update the B
slot with a cmdline.txt
written for the A
slot, pointing to the A
rootFS. On reboot, the B
rootFS would not be tested, and the update could be applied with a broken B
rootFS. If the next update had a broken rootFS, it would be applied to the A
slot, and both rootFS would now be broken and the device unbootable.
This shows that maintaining cmdline.txt
is paramount.
Option 1: RAUC bundle hooks
Since we cannot make sure at build time that cmdline.txt
will point to the right rootFS, we could do it at update time using a RAUC bundle post-install
hook to edit and fix cmdline.txt
.
This is, of course, slightly hacky, and generating a bundle without the proper hook could be disastrous.
Option 2: the RPi firmware [boot_partition] conditional
This is the more robust option, but relies on a currently undocumented feature of the RPi 5 firmware.
In release 2025-03-10
of the rpi-eeprom, Raspberry Pi introduced the [boot_partition=N]
conditional, where N
is the number of the partition the system booted from.
This means that we can now use this construct:
[boot_partition=1] cmdline=cmdline-rootfs-A.txt [boot_partition=2] cmdline=cmdline-rootfs-B.txt
in config.txt
to point to the appropriate cmdline.txt
depending on the booted partition, which is determined by the RPi firmware dynamically.
We can therefore now ship the same vFAT partition update to either slot, without having to maintain the relationship to the appropriate rootFS.
Marking the slot as good
The Raspberry Pi firmware does not keep track of boot attempts. This means, that there is no way for the system once booted to know whether there was an attempt to boot the other slot, and whether that attempt failed.
Because of that, the logic to mark a slot as good or bad is rather elementary: if the slot has been booted, it is good, otherwise, it is considered bad.
Source of truth for the booted slot
This is a small issue, this time on the side of RAUC. The tool accepts several source to determine which slot is the one that is currently booted. They are documented here. When using a custom bootloader backend, implementing get-current
should be what RAUC uses to determine the booted slot.
We could use this to point RAUC to the DTB that the Raspberry Pi firmware populates.
However, that will only be the case if it is unable to determine it from the kernel command line e.g. by parsing rauc.slot=
.
One of the arguments that RAUC will use, unfortunately, is root=
, which we use to point the kernel to the right rootFS. This means, that there is no practical way to have RAUC use the RPi firmware-populated DTB.
The RAUC maintainers are aware, and considering changing this behaviour.
Conclusion
The Raspberry Pi firmware exposes some features (albeit one experimental) that make it reasonable to consider not using U-Boot as a secondary bootloader, while still retaining the capability to distribute updates using a mature framework in RAUC.
That would only be more true if RAUC indeed ends up merging support for the RPi firmware as a backend, though some small limitations might remain.