Updating Raspberry Pi boot firmware files using Yocto Project and Mender

Introduction

The Raspberry Pi family of boards are undeniably very popular, both for hobbyists and in professional settings. For this reason this tutorial was created to look at one of the limitations of the Raspberry Pi family boards and how to work around it.

In the context of supporting robust operating system level software updates, Raspberry Pi boards are actually very complicated to work with. This primarily relates to the fact that the Raspberry Pi has a set of firmware files that it needs to boot, which are impossible to update robustly and atomically. These firmware files reside on the vfat boot partition and content is something like this:

bcm2708-rpi-b.dtb       bcm2708-rpi-zero-w.dtb  bcm2710-rpi-3-b-plus.dtb  boot.scr       fixup4cd.dat  fixup_cd.dat  issue.txt     kernel.img        start4db.elf  start_db.elf
bcm2708-rpi-b-plus.dtb  bcm2709-rpi-2-b.dtb     bcm2710-rpi-cm3.dtb       cmdline.txt    fixup4.dat    fixup.dat     kernel7.img   LICENCE.broadcom  start4.elf    start.elf
bcm2708-rpi-cm.dtb      bcm2710-rpi-2-b.dtb     bcm2711-rpi-4-b.dtb       config.txt     fixup4db.dat  fixup_db.dat  kernel7l.img  overlays          start4x.elf   start_x.elf
bcm2708-rpi-zero.dtb    bcm2710-rpi-3-b.dtb     bootcode.bin              COPYING.linux  fixup4x.dat   fixup_x.dat   kernel8.img   start4cd.elf      start_cd.elf  uboot-git-log.txt

For more information about the files and how they are using during the boost process, take a look at this post. Note that when you are using Mender, you are booting U-Boot first instead of the Linux kernel directory. This is because Mender integrates with U-Boot to be able to switch between partitions.

What is important to know is that the firmware files have a relationship to the Linux kernel. Even if this is not explicit, history has shown that there have been firmware changes that require you to update the Linux kernel or the other way around. This means that through the lifetime of your device, you should expect that you need to update these firmware files.

Why is updating these files a problem?

When we talk about robust OTA software updates, we mean that it is tolerant to failures, e.g power-loss during the update process, and the device should always rollback to a known working state should something go wrong. For root filesystem updates, this can be solved by having redundancy (A/B setup), meaning you have two root filesystem partitions and you switch between them in an atomic way, ensuring that you always have something to fallback to in case of failure. It is not possible to implement this fail-safe mechanism for the Raspberry Pi firmware files, as you only have one location to store them and this location is hardcoded in the GPU (yes, GPU) boot ROM code on the Raspberry Pi. If these files are corrupted your device will stop booting.

Another problem with updating these files, is that it is not possible to do this atomically. This means that you can end up with 50 % of the files being updated in case you are interrupted by a power-loss or reset of the device. This puts the device in an unknown state, and if you are lucky it might still boot, if not it will not.

As we mentioned earlier you will probably need to update these files through the lifecycle of your device which means that you need to accept this risk upfront. In this tutorial we will explain how you can update these files (non robustly) using the Yocto Project and Mender. We will use a Raspberry Pi 3 as an example, but this is applicable to all boards and SoM’s within the family.

This tutorial assumes that you are already familiar with Mender and the Yocto Project, and you are looking for a way to update the Raspberry Pi firmware.

Prerequisites

  • A Raspberry Pi 3 Model B or B+ with 8 GB of microSD as a storage medium.
  • A supported Linux distribution and dependencies installed on your workstation/laptop as described in the Yocto Mega Manual
    • NOTE. Instructions depend on which Yocto version you intend to use.
  • Google repo tool installed and in your PATH.

Step 1 - Initial setup of the Yocto Project environment

Set the Yocto Project branch you are building for:

export BRANCH="warrior"

Create a directory for your mender-raspberrypi setup to live in and clone the meta information.

mkdir mender-raspberrypi && cd mender-raspberrypi

Initialize repo manifest:

repo init \
    -u https://github.com/mendersoftware/meta-mender-community \
    -m meta-mender-raspberrypi/scripts/manifest-raspberrypi.xml \
    -b ${BRANCH}

Fetch layers in manifest:

repo sync

Setup the build environment:

source setup-environment raspberrypi

Set 2 - Enable update of firmware files

In meta-mender/meta-mender-raspberrypi we already have the necessary functionality that ensures that:

  • boot firmware files are installed into the root filesystem. This is needed if we are to copy them to the vfat partition
  • a state script is added to the Mender Artifact that will copy the firmware files to the vfat partition in the ArtifactInstall_Leave state

You can find more details in the pull-request that enabled this.

To enable mentioned functionality simply add the following to e.g local.conf:

INHERIT += "rpi-update-firmware"

And now run a build as you normally would:

bitbake core-image-base

The output will be a Mender Artifact that will also update the files found on the vfat partition. NOTE! Updating these files is a non-reversible operation and you are risking to brick your devices by doing this, e.g. if you experience a power loss during the update process.

When you have the Artifact make sure to disable this feature by removing the following from e.g local.conf

INHERIT += "rpi-update-firmware"

I would only recommend enabling rpi-update-firmware in situations where it is absolutely necessary, and is not something that you would keep enabled for all of your Artifacts.

Conclusion

For some additional background information on this topic check Updating a Raspberry boot partition.

The limitation of the Raspberry Pi firmware files is there and needs to be taken into account. In this tutorial we have presented a way to update these files, but this a non-robust workaround.

In an ideal world everything would reside in one location, e.g inside the root filesystem. This way we could utilize the available redundancy and fail-safe mechanisms for all parts of the operating system. Even though not to the same extent, this problem does exist on other ARM boards where the norm is to keep the bootloader in a isolated location on the storage medium, and updating the bootloader is hard to do robustly without support for it in hardware, which is why you would typically avoid updating the bootloader through the lifecycle of your device.

It is very important to keep the bootloader slim, and have very little logic there as you will have a problem updating it later once your devices are in the field and you find issues. The Raspberry Pi is a cautionary example of what can go wrong when you have too much logic in the boot process.


If this tutorial was useful to you, please press like, or leave a thank you note to the contributor who put valuable time into this and made it available to you. It will be much appreciated!

10 Likes

Just came looking for this very info, thank you for writing it out so clearly!

2 Likes

So I ended up having an issue. I was upgrading from a Warrior build to Dunfell on the Raspberry Pi 3B+, using a manual install of the artifact and manual reboot (essentially mender -install <artifact> && reboot) but when the system reloaded the new boot files were in place but the old partition was loaded. There didn’t seem to be any kind of rollback or execution of the U-Boot altboot command. As an experiment, I removed the rpi-update-firmware inherit, which prevented the boot files from being updated, but appeared to allow the system to boot the new partition (which failed due to the kernel not being updated, of course).

In the end it seemed that the U-Boot env variables were not being updated, and simply executing a fw_saveenv command with a dummy variable after installing the artifact but before initiating the reboot solved the issue. I’m not sure why this is necessary, I hadn’t made any changes to the partition layouts, though I did remove MENDER_PARTITION_ALIGNMENT = "4194304" from my build, since it seems it’s now unnecessary, (I am 95% sure that re-adding it didn’t help, I can check if you like). but the saveenv fixed it.

Updating the firmware files atomically is actually possible. You need two vfat partitions and exchange their entries in the MBR (i.e. a single 512B sector) atomically. I’m not the first to think of this; the folks at rauc are using the same mechanism: rauc/advanced.rst at 471e7e2acd88f21cb5306f04dcd6135ef557de2c · rauc/rauc · GitHub
It seems to me that having the Raspberry Pi firmware blobs, config.txt, the kernel image and cmdline.txt (which references the root filesystem) in the atomically-swappable boot partitions is enough to implement robust updates and u-boot could be eliminated.

1 Like

It looks like the new tryboot feature of the Raspberry Pi firmware could be an interesting option!

I started to collect some information here:

Fix the dtb handling during A/B update

1 Like

I have an issue when upgrading from Warrior to Dunfell on a cm3+. I enabled update of the firmware files by adding

INHERIT += "rpi-update-firmware"

to local.conf
After installing the update locally with mender -install the firmware file are updated, but start_x.elf becomes “damaged”. With the “damaged” start_x.elf the compute module does not boot anymore. I can see an output defined in config.txt is set, but network or hdmi does not come up.
After replacing the “damaged” start_x.elf with the good new version from the new dunfell sdimg, the pi works fine again.
The “damaged” file has a different md5 than the good one, and the good one is different from the old Warrior one. I tried this multiple times and the “damaged” file always has a different md5, but the size is always about the same.

What could be the reason for this? Any help is appreciated. Thank you!

Mender team - is this approach being considered for robust firmware updates for Raspberry Pi 4/CM4?

1 Like

In the meantime I have posted some implementation details.

2 Likes

My company is planning a product based around Raspberry Pi CM4, and atomic updates will be critical for us. As noted above, @lueschem provided an implementation using scripts and the RPi4 “tryboot” feature.

Since Mender provides a Raspberry Pi reference implementation for the Yocto Project in the meta-mender-raspberrypi layer, I imagine it’s in Mender’s interest to address atomic firmware updates on Raspberry Pi 4 and CM4 especially due to the warnings on this thread that using rpi-update-firmware is not an atomic operation and could result in boot failures if the update is interrupted.

I would like to proceed with implementing atomic firmware updates on RPi4/CM4 devices, but I am not sure if I should follow @lueschem’s approach just in case Mender decides to implement it slightly differently. I am concerned about maintaining future compatibility with an implementation that may land in meta-mender-raspberrypi and that I may have to fix it with state scripts in the future. Could @mirzak, etc. please bless @lueschem’s approach or provide guidance about how I should adapt it to meta-mender-raspberrypi? Thanks!

1 Like

Hello @nowls and @lueschem,

Thanks for bringing this topic. This implementation looks promising and I would say that you could give it a try. But from the Mender’s approach we don’t really recommend to update the bootloader because of the risks involved.

As Mender is an Open Source tool, we encourage people to customize it and integrate it as part of their toolset, but we are not maintaining any third party application like this one and we can not confirm that it is robust enough as we have not tested at the level we test Mender itself.

If you are interested in Professional Services from Mender you canreach contact@mender.io and if you would like to handle as an Enhancement Request I think @eystein could help you with it.

Have a great day!
Luis

I fully agree with the statement from @lramirez that in general it is not a good idea to update the (U-Boot) bootloader. However, the Raspberry Pi engineers have provided a new feature that allows us to update (most of) the bootloader in a safe way: Raspberry Pi Documentation - Raspberry Pi Hardware
In the long run I believe that we should go into a direction where we can also update the bootloader in safe way. What I encountered in the past was really frustrating: I had a bootloader that was unable to boot a recent kernel. So I had to do an unsafe update of the bootloader at the same time I upgraded the entire OS because - to make things worse - the new bootloader was unable to start the old kernel. It was by the way happening with the U-Boot provided by a renowned CPU vendor…

1 Like

@lueschem @nowls I am completely on the same page as you guys. There is a real desire to be able to provide fail safe upgrades of the bootloader as well. Thinking about it in theory it should be possible to make things completely fail safe using the following techniques (This should work for all raspberry pi versions):

  1. Use the new tryboot feature in favour of uboot
  2. Use the boot layout as described by @lueschem
  3. Use the atomic boot partition update mechanism as used by the guys at RAUC (rauc/advanced.rst at 471e7e2acd88f21cb5306f04dcd6135ef557de2c · rauc/rauc · GitHub)

The process I am thinking of now:

  1. Update the root file system as always
  2. Copy the currently active boot partition to the inactive one
  3. Replace the file tree on the shadowed boot partition used by the newly updated OS to match the OS
  4. Swap the boot partitions

This way the only thing I can imagine to go wrong is when the files on the shadowed boot partition get corrupted - This should only ever happen if the update process has errors.

Maybe we could join forces to implement such a procedure!

Hi @sensslen, I think that my current setup is pretty close to what you are suggesting - except for step 3: Instead of implementing two boot partitions there is just one but I add a prefix (p3 and p4) to the bootloader files (start4.elf and fixup4.dat). Like this I can store them on the same partition. I also do not know how we could implement the atomic boot partition switching without having a binary like rauc or u-boot in the boot chain. So to be precise about the bootloader: I do not modify the part that is stored in the EEPROM of the Raspberry Pi 4 - but everything else gets “atomically” swapped using the tryboot feature.

@sensslen @lueschem That sounds reasonable. I may take a stab at it but probably not until mid to late March. To take advantage of this approach, it looks like the firmware burned to Pi4’s EEPROM must be >= 2021-04-19. Thankfully this should be OK w/ us because we haven’t released devices to the field that would be risky to flash remotely. As for /boot partition(s), is an A/B necessary or preferred? It looks like with os_prefix the old and new boot files can be placed in different directories.

@nowls: I guess that the EEPROM part of the bootloader can just grab the files from one boot partition. Therefore I decided to store the A and the B variant on the same partition but with a different prefix. Please note that I also reconfigure start_file and fixup_file.

The reason I suggested an approach of swapping boot partitions is that the boot partition is formatted as fat - which is far from failure tolerant. I’m a bit worried what happens when power drops during boot partition modification. This could (as far as I understand) lead to a corrupted boot partition which in turn bricks the device.

With the approach I am suggesting the boot files are available 4 times - I’m suggesting the layout @lueschem suggests to be present on both boot partitions. This makes the update process failure tolerant even during updating the boot parition. Also the time of the update I suggest is just after updating the root file system. Since mender is a binary I guess it is perfectly feasable to make it alter the partition layout of the disk.

Cool - this sounds like a good idea!

Ok so would it work to do something like this (sorry if I missed or misunderstood something already covered above):

  1. Download & verify the replacement boot partition
  2. Mount it and copy the relevant boot files into an os_prefix/ directory on the current boot partition
  3. Also copy the current boot files over the replacement boot partition
  4. Try booting using os_prefix/ files on current partition using the tryboot feature
  5. If booting fails or power is lost, A/B boot part and A/B rootfs have not been changed (save for files in os_prefix/, ignored if tryboot fails) and the device should reboot using the original boot partition.
  6. If boot succeeds and gets to running mender -commit, swap both the boot partition and rootfs partition

It seems like the patched u-boot will still be needed for this, or am I missing something? (Also I’m trying to learn as I go; haven’t played around with Linux boot chain too much)

Edit: On second thought, I think the new bootfiles are shipped in the rootfs anyway; so should they be copied from there? Not sure how this interacts with an A/B boot partition scheme.

FYI, I proved out booting Raspberry Pi 4 with its bootfiles packaged in a RAMDISK called boot.img using this script. This also works with the tryboot feature. Basically you need:

  • boot.img - RAMDISK containing bootfiles and overlays
  • config.txt - Contents should be boot_ramdisk=1
  • tryboot.img - RAMDISK containing new bootfiles and overlays
  • tryboot.txt - Contents should be boot_ramdisk=1

I tried this because I want to integrate with Raspberry Pi 4’s secure boot mode, which allows loading only signed boot images. It should be possible to extend @lueschem’s work to use this format instead of using os_prefix/ but I’d no longer be able to use config.txt as the place to store the currently active partition. Instead my plan would be to have the Pi4 2nd stage bootloader boot U-boot and let it handle the partition switching as it’s currently done in meta-mender-raspberrypi using environment variables in a hidden part of the block device. Then on commit I’d have to simply swap boot.img and tryboot.img (and *.sig files for signed boot).

To make this really robust against corruption of the FAT32 partition, I agree with @sensslen that it’d be better to use the RUAC approach of swapping active and hidden FAT32 partitions through MBR modification. However I think writing the binary program that could make the modifications to the MBR is more involved than I’m ready to tackle.

See here for a write-up on my experiments with tryboot and signed boot: Tryboot + secure boot on Raspberry Pi 4 - Raspberry Pi Forums

1 Like

Hello,

@mirzak looks great. I tired it with new mender clients and it seems to work nicely.

However I run it with problems with mender client 1.7

Start updating from local image file: …
mydevice kern.err mender[588]: level=error msg=“Installation failed: installer: failed to read and install update: will not install artifact with state-scripts when installing from cmd-line. Use -f to override” module=rootfs

using with -f works but doesn’t upgrade the boot partition

Thanks