Br2rauc
Buildroot + RAUC
Install / Use
/learn @cdsteinkuehler/Br2raucREADME
Buildroot + RAUC
Overview
This project attempts to provide a working example system combining Buildroot, U-Boot, and RAUC in a "works out of the box" example for the Raspberry Pi Compute Module 4 (CM4). The intent is for this to be usable as a base system for some classes of IoT projects, and a hopefully easy to modify starting point if you need something more customized to your needs.
Current features:
- U-Boot bootloader with redundant environment storage
- Symmetric Root-FS Slots with fallback on failed updates
- Rescue slot for recovery mode (eg: hold button when booting)
- Atomic updates of bootloader vfat partition (no fallback)
- Boot-time watchdog support (via recent RPi Firmware)
- Run-time watchdog timeouts (via systemd)
- Persistent data partition
- Temporary data partition for updates
- Partition layout (fits on a 4G uSD card with room to spare):
- DOS MBR partition table
- 4 MB "empty" (matches RPi images, used for U-Boot environment)
- 256/512 MB vfat boot partition (uses boot-mbr-switch)
- 256 MB squashfs rescue partition
- 2x 900 MB A & B rootfs partitions
- 128 MB Persistent data partition
- 900 MB Upload partition (eg: storage for firmware updates)
Getting Started
This project is a Buildroot external directory and can not be used alone. You also need to have a local copy of the Buildroot project and a properly configured build. One simple way to do this would be:
Building a bootable uSD image from scratch
# Create a working directory
mkdir ~/MyWorkDir
cd ~/MyWorkDir
# Pull in the required projects
git clone --depth 1 --branch 2022.02.x --no-single-branch https://git.busybox.net/buildroot/
git clone https://github.com/cdsteinkuehler/br2rauc
# Create the certficate and keyring files needed for signing RAUC bundles
# Optional: Pass CA and ORG as arguments: openssl-ca.sh [ ORG [ CA ]]
# Optional: Create a symlink to your CA directory located elsewhere
( cd br2rauc/ ; ./openssl-ca.sh )
# Setup buildroot, keeping build artifacts outside the buildroot tree
# Note paths are relative to the buildroot directory
make -C buildroot/ BR2_EXTERNAL=../br2rauc O=../output raspberrypicm4io-64-rauc_defconfig
# ...or for the Raspberry Pi 4B:
make -C buildroot/ BR2_EXTERNAL=../br2rauc O=../output raspberrypi4-64-rauc_defconfig
# You can now run standard buildroot make commands with no options
# directly from the output directory
cd output
# You may want to run "make menuconfig" to enable ccache or review/tweak the
# default config selections
# Some suggested changes:
# Rename defconfig file to save customizations:
# Build options -> Location to save buildroot config
# Enable ccache:
# Build options -> Enable compiler cache
# Build options -> Compiler cache location
# Download a bootlin toolchain instead of building one from scratch:
# Toolchain -> Toolchain type -> External toolchain
# Toolchain -> Toolchain -> Bootlin toolchains
# Toolchain -> Bootlin toolchain variant -> aarch64 glibc stable 2021.11-1
# Build everything and generate the uSD image file:
make
# The bootable uSD is xz compressed with a bmap file in the images directory
# Write the image file to a uSD card with something like the following (or use
# your favorite imaging utility):
sudo bmaptool copy images/sdcard.img.xz /dev/sdX
Custom Rescue Filesystem
# Currently, the rescue partition simply uses a squashfs version of the
# generated root filesystem. If you add a lot of packages to your target
# filesystem, you may wish to use a simpler rescue filesystem with just enough
# to run RAUC and perform updates.
#
# WARNING:
# You will have to update the genimage.cfg config file and point it to the new
# rescue squashfs filesystem for this to work properly!
#
# To create another Buildroot working directory to use for the rescue partition:
cd ~/MyWorkDir
make -C buildroot/ BR2_EXTERNAL=../br2rauc O=../rescue raspberrypicm4io-64-rauc_defconfig
# You can now run standard buildroot make commands with no options
# directly from the output directory
cd rescue
# As above, you can run make menuconfig, or just run make...
# make menuconfig
make
# ...then rebuild the uSD image from your primary output directory
While the provided default configuration attempts to limit the changes made to the Buildroot default configuration, I highly recommend you enable ccache and switch to an external Bootlin toolchain unless you enjoy wasting time and CPU cycles building gcc and recompiling identical code multiple times. See the comments above for details.
For quick experiments, you may wish to modify the br2rauc tree directly, or a more sophisticated process might use a top-level project that includes several exteranl trees and multiple BR2_EXTERNAL entries, eg:
-- MyCoolProject
|-- buildroot (git external)
|-- br2rauc (git external)
|-- MyApp1 (external or part of MyCoolProject)
|-- MyApp2 (external or part of MyCoolProject)
|-- output (generated by Buildroot, can be renamed or moved elsewhere)
|-- rescue (optional, generated by Buildroot if you use an alternate rescue config)
cd MyCoolProject
# Generate output directory with BR2 Externals configured
# NOTE: There are additional parameters you may wish to pass to Buildroot, see
# the Buildroot documentation for details
make -C buildroot/ BR2_EXTERNAL=../br2rauc:../MyApp1:../MyApp2 O=../output my_defconfig
# Configure the system as desired...
cd output
make menuconfig
# ...and build an image
make
Usage
After completing a build, your Buildroot output directory should contain an image file (output/images/sdcard.img) that can be written to a uSD card for use with the Raspberry Pi Compute Module 4 IO Board. In addition to the uSD you will need a 3.3V serial cable connected to the standard serial console pins on the cm4io (gpio 14 & 15, pins 8 & 10 on the 40-pin connector).
Once the system boots, you can use the rauc command to examine the current status:
rauc status
...or to mark the current partition as good:
rauc status mark-good
Note that if you do not mark the status as good the boot count in the bootload will count down and eventually switch to the other partition. You can use the fw_printenv command to examine the current bootloader status:
$ sudo fw_printenv | grep BOOT_
BOOT_A_LEFT=2
BOOT_B_LEFT=3
BOOT_ORDER=A B
...and see what happens after marking the current boot as 'good':
$ rauc status mark-good
rauc-Message: 17:34:00.021: rauc status: marked slot rootfs.0 as good
$ sudo fw_printenv | grep BOOT_
BOOT_B_LEFT=3
BOOT_ORDER=A B
BOOT_A_LEFT=3
For full details, read through the RAUC manual.
Buildroot Changes
This project is based on the Buildroot provided 64-bit default configuration for the Raspberry Pi CM4 I/O board, with the following major changes:
- Switch to glibc, systemd, and udev : The RPi is not particularly resouce constrained, and using glibc, systemd, and udev provides the fewest surprises when migrating from a Raspberry Pi OS based system. In particular, if you are not using udev many device nodes will not get properly generated, particularly the video devices
- Switch to the U-Boot boot loader : U-Boot is required to allow intelligent switching between redundant filesystem images and handling failed updates.
- Add RAUC : This builds the required host and target tools needed to use RAUC.
- Undefine BR2_ARM_FPU_VFPV4 : 64-bit ARM cores are required to support ARMv8.
- Add a non-root user (user) and enable sudo without password
- Implement device tree customizations necessary for the cm4io
- Modify post-*.sh scripts as needed for RAUC
- Generate RAUC update bundles for boot and root filesystems
- Enable hardware watchdog support in systemd
Busybox
A configuration fragment file is used to slightly modify the default Busybox config supplied with Buildroot:
- Add blkdiscard (so you can easily wipe your uSD card while experimenting)
- Remove watchdog utility from busybox (handled by systemd)
- Remove klogd/syslogd (logging handled by systemd journald)
U-Boot
The U-Boot configuration leverages the default rpi_arm64 configuation provided by upstream. A configuration fragment file is used to override a few options:
- Store environment on the mmc device
- Enable redundant copies of the environment
- Increase environment size to 32K
- Enable squashfs support needed for rescue partition
Notes
The RPi firmware loads and modifies the device tree based on the contents of the config.txt file. The original device-tree file may not boot without some of these changes (eg: the dma-ranges property for the emmc controller is different between the BCM2711 C0T stepping revisions).
The RPi firmware uses more than just cmdline.txt to construct the kernel command line. If you do not duplicate these entries, your system may not boot (eg: the rootwait parameter is required when booting from emmc). To make it easy to modify the kernel command line without having to update the boot loader, the U-Boot script looks for a /boot/uEnv.txt file on the rootfs partition selected by the RAUC logic (either A, B, or Rescue) and supports the following two environment variables to control the kernel command line:
- bootargs_force: If set, this overrides the bootargs_default value set in the U-Boot environment. The RAUC arguments (root and rauc.slot) are appended to generate the final kernel command line.
- bootargs_extra: If set, the contents are appended to bootargs_default before the RAUC arguments. Ignored if bootargs_force is set.
The easiest way to determine exactly what the firmware is doing is to boot using the firmware provided settings (fdt blob at ${fdt_addr} with no bootargs specified by U-Boot), examine the run-time system that results, and compare with the original source files.
The provided U-Boot script will use the firmware provided device tree if the kernel command line includes the text "fw_d
