Safe updates using RAUC on Raspberry Pi 5

RAUC on RaspberryPi 5As 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 parsing autoboot.txt to find the partition set in the all section
  • Implement set-primary either setting the tryboot flag using vcmailbox or by swapping the partition in the all and the tryboot sections of autoboot.txt configuration if tryboot 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 the all and the tryboot sections of autoboot.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.

Leave a Reply