Skip to content

Updating U-Boot with an A/B Strategy

The commands for updating u-boot using an A/B strategy

Unlike consumer devices, machines, ECUs or medical devices are in the field for 15 or more years. There inevitably comes the time in their life, when you must update their boot loader. I’ll show you how to do this from an embedded Linux system running on an NXP iMX8M Plus. You can adapt the method to other SoCs and use it for OTA updates, as well.

Introduction

If you don’t have to update the boot loader over the lifetime of your device, be happy and don’t touch the boot loader. This “strategy” might work for a consumer device with a lifetime of three years or less. However, it is very unlikely if your device is in the field for 10, 15 or even 20 years, which is normal for devices in many non-consumer industries. Fixing a bug, applying a new feature or finding a security issue in compression, encryption, communication or any other functionality used by the boot loader is almost certain. The do-nothing strategy won’t work in this case. This leaves you with two updates options: parallel boot loaders and eMMC boot partitions (see Options 3 and 4 in Drew Moseley’s excellent article Considerations for Updating the Bootloader Over-the-Air (OTA)).

Many boards can boot from multiple sources like a removable SD card, an internal eMMC storage (lovingly called “NAND flash” or simply “flash”), a USB drive and a network server. The SD card is often used for recovery, when you bricked your board (e.g., when u-boot or the Linux kernel fail to start) or when you install the boot loader and root file system in the eMMC storage for the very first time. Similarly, you boot your device from SD card and install a new boot loader in one of the eMMC boot partitions. Older devices support only one eMMC boot partition, whereas newer devices support two eMMC partitions (see Section 7.2.1 Boot Partition of the eMMC Standard JESD84-A43 (PDF)). If the boot loader update fails, you still have a working system on the SD card and can try the update again.

In production, devices typically run from the internal eMMC storage. Access to the SD card slot may be tricky for users. Devices might not even have an SD card slot. With the now common two boot partitions, you can apply an A/B strategy for boot loader updates. If the device booted from partition A (the active partition), you install the new boot loader in partition B (the inactive partition) and vice versa. If the installation succeeds, you switch to the new partition and reboot the device from the new boot loader. Otherwise, you stay on the current boot partition.

You can also perform A/B updates of boot loaders over the air (OTA). I recently implemented this with an SwUpdate client on a Variscite iMX8M Plus SoM. In this post, I am going to teach you how to update u-boot with an A/B strategy from the Linux prompt. I offer the integration of u-boot updates with OTA update clients like SwUpdate, RAUC and Mender as a service.

Context

My device is powered by a DART-MX8M-PLUS SoM from Variscite (see Variscite’s Developer Guide). I built a custom u-boot and Linux image with Yocto 4.0 Kirkstone following Variscite’s instructions in Build Yocto from Source Code – naturally with a couple of improvements from my side. I had to add back the packages imx-boot and mmc-utils to the image, after minimising the images a bit too aggressively.

IMAGE_INSTALL:append += " imx-boot mmc-utils"

The package imx-boot adds some iMX-specific features to u-boot. The package mmc-utils provides the utility mmc to switch between the two boot paritions or to find out the current u-boot version or the current boot partition.

Variscite provides a recovery image for SD cards. For getting started, it’s enough to download the pre-built image. This image contains the script /usr/bin/install_yocto.sh to partition the internal eMMC storage into two root partitions, one data partition and two boot partitions. The script installs the root file system rootfs.ext4.gz and the boot loader imx-boot-sd.bin from the directory /opt/images/Yocto in the first rootfs partition and in the first boot partition, respectively. The second rootfs partition and the second boot partition remain empty. You achieve this with the command

# install_yocto.sh -u

Option -u creates the second rootfs partition enabling an A/B strategy for updating the rootfs. The two boot partitions will always be available without any help from the script, because they are mandatory from version 4.1 of the eMMC standard. The DART-MX8M-PLUS SoM, for example, implements version 5.1 of the eMMC standard. My partitioning of the internal eMMC storage looks like this:

# lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
mmcblk2      179:0    0 14.6G  0 disk
|-mmcblk2p1  179:1    0    4G  0 part /
|-mmcblk2p2  179:2    0    4G  0 part /run/media/mmcblk2p2
`-mmcblk2p3  179:3    0  6.6G  0 part /data
mmcblk2boot0 179:32   0    4M  1 disk
mmcblk2boot1 179:64   0    4M  1 disk

Note that I modified the installation script and the image recipe to adapt the partitioning to my customer’s requirement. By default, the data partition mmcblk2p3 has a size of 200 MB. Each rootfs partition takes up half of the remaining size of mmcblk2. How to change the partition size will be the topic of an upcoming article.

For the remainder of this article, it’s only important that the two boot partitions mmcblk2boot0 and mmcblk2boot1 exist. Following the instructions of the articles Build Yocto from Source Code and Yocto Recovery SD Card should get you to this point. The procedure for the boards of other vendors will vary. However, the way how to install u-boot in the boot partitions should be the same. Please let me know about your experience with other boards in the comments.

How to Distinguish U-Boot Versions

When you install a new u-boot version in the inactive boot partition, you want to check whether the system really boots from this new version. The first messages in the serial console after power-up tell you the u-boot version.

U-Boot SPL 2022.04-lf_v2022.04_var01+gf6390c6805 (Jul 10 2023 - 12:45:06 +0000)
SEC0:  RNG instantiated
Normal Boot
Trying to boot from BOOTROM
image offset 0x0, pagesize 0x200, ivt offset 0x0

U-Boot 2022.04-lf_v2022.04_var01+gf6390c6805 (Jul 10 2023 - 12:45:06 +0000)

The last message gives the u-boot version as 2022.04-lf_v2022.04_var01+gf6390c6805. This makes it easy to figure out from which version the system boots. Now, you could build a new u-boot version, say, lf_2022.04_var02 or even lf_v2023.04_var01. This might be too big a change and might lead to long debugging sessions to figure out why u-boot or the kernel don’t boot any more. This would only distract you from your task at hand. Just changing the version part lf_v2022.04_var01 to, say, lf_v2024.05_abc01 would be enough.

You are lucky. For your experiments, you can temporarily change LOCALVERSION in meta-variscite-bsp/recipes-bsp/u-boot/u-boot-variscite.bb:

LOCALVERSION ?= "-lf_v2024.05_abc01"

LOCALVERSION is not specific to Variscite SoMs, because the assignment in u-boot-variscite.bb overrides the earlier assignment in meta-freescale/classes/fsl-u-boot-localversion.bbclass. It should work for most SoCs of the iMX family.

The Yocto build produces a boot loader imx-boot-imx8mp-var-dart-sd.bin-flash_evk in DEPLOY_DIR_IMAGE with the new version, where imx8mp-var-dart is the machine. A quick check confirms this:

$ cd $DEPLOY_DIR_IMAGE
$ strings imx-boot-imx8mp-var-dart-sd.bin-flash_evk | grep "U-Boot 20"
U-Boot 2022.04-lf_v2024.05_abc01+gf6390c6805

Similarly, you can check the u-boot version on the board. The boot partition /dev/mmcblk2boot0 contains the u-boot binary with the old version lf_v2022.04_var01.

# strings /dev/mmcblk2boot0 | grep "U-Boot 20"
U-Boot 2022.04-lf_v2022.04_var01+gf6390c6805

You are now well-equipped to verify which u-boot version you installed in which boot partition.

Installing U-Boot into a Boot Partition

The situation on the board looks like this:

  • Boot partition 1 (/dev/mmcblk2boot0) contains u-boot version lf_v2022.04_var01. It is the active boot partition, from which the board boots.
  • Boot partition 2 (/dev/mmcblk2boot1) contains any u-boot version or no u-boot at all. It is the inactive boot partition, in which you want to install the new u-boot version.
  • You transferred the new u-boot binary to the directory /home/root on the device.

The boot messages don’t give a clue about the active boot partition. From the Linux prompt, you can find out the active boot partition with this command:

# mmc extcsd read /dev/mmcblk2 | grep -A2 PARTITION_CONFIG
Boot configuration bytes [PARTITION_CONFIG: 0x08]
 Boot Partition 1 enabled
 No access to boot partition

The command before the pipe lists the configuration of the block device /dev/mmcblk2. The grep after the pipe picks out the boot configuration. The bits of PARTITION_CONFIG have the following meaning (see also Boot configuration):

  • Bits 0-2 – partition access: 0 = no access to boot partition; 1 = read/write access to boot partition 1; 2 = read/write access to boot partition 2. Current value: 0.
  • Bits 3-5 – partition enable: 0 = boot not enabled; 1 = boot partition 1 enabled; 2 = boot partition 2 enabled; 7 = user area enabled for boot. Current value: 1
  • Bit 6 – acknowledge: 0 = no boot acknowledge; 1 = boot acknowledge during boot. Current value: 0.

The second and third output messages above are textual descriptions of the current values of “partition enable” and “partition access”, respectively. In short, boot partition 1 is the active partition.

The next three commands install the new u-boot binary into the inactive boot partition 2:

# echo 0 > /sys/class/block/mmcblk2boot1/force_ro
# dd if=/home/root/imx-boot-imx8mp-var-dart-sd.bin-flash_evk of=/dev/mmcblk2boot1
2858+1 records in
2858+1 records out
1463488 bytes (1.5 MB, 1.4 MiB) copied, 0.085924 s, 17.0 MB/s
# echo 1 > /sys/class/block/mmcblk2boot1/force_ro

By default, boot partitions are read-only. So, the file /sys/class/block/mmcblk2boot1/force_ro contains 1. The first command makes boot partition 2, /dev/mmcblk2boot1, writable. The second command writes the new u-boot binary /home/root/imx-boot-imx8mp-var-dart-sd.bin-flash_evk into boot partition 2, /dev/mmcblk2boot1. The third command makes boot partition 2 read-only again.

You can check that the new u-boot version ended up in boot partition 2 with this command:

# strings /dev/mmcblk2boot1 | grep "U-Boot 20"
U-Boot 2022.04-lf_v2024.05_abc01+gf6390c6805

The next command switches the boot partition from 1 to 2:

# mmc bootpart enable 2 0 /dev/mmcblk2

The general form of the command is:

mmc bootpart enable <boot-partition> <send-ack> <device>

<boot-partition> is the boot partition, which is always the number X from the device file name /dev/mmcblk2bootX plus 1. <send-ack> specifies, whether the <device> shall acknowledge the change of the boot partition (1 for acknowledge and 0 for no acknowledge). <device> is the device file name of the block device and not of one of the boot partitions (see Switch boot partition for more details).

In the running example, <device> must be /dev/mmcblk2 and not /dev/mmcblk2boot1. Using the latter is a pretty sure way to brick the board. Your way out of this mess is to boot the board from the recovery SD card and re-install u-boot and rootfs in the eMMC storage.

A quick check confirms that boot partition 2 is now active.

# mmc extcsd read /dev/mmcblk2 | grep -A2 PARTITION_CONFIG
Boot configuration bytes [PARTITION_CONFIG: 0x10]
 Boot Partition 2 enabled
 No access to boot partition

After rebooting the device, the first messages now show the new version lf_v2024.05_abc01:

U-Boot SPL 2022.04-lf_v2024.05_abc01+gf6390c6 (Jul 10 2023 - 12:45:06 +0000)
SEC0:  RNG instantiated
Normal Boot
Trying to boot from BOOTROM
image offset 0x0, pagesize 0x200, ivt offset 0x0

U-Boot 2022.04-lf_v2024.05_abc01+gf6390c6 (Jul 10 2023 - 12:45:06 +0000)

Checking the boot configuration reveals that boot partition 2 is now active: PARTITION_CONFIG = 0x10. If you want to switch to boot partition 1, you replace mmcblk2boot1 by mmcblk2boot0 and mmc bootpart enable 2 by mmc bootpart enable 1 in the above commands.

This gives you an A/B strategy for updating u-boot. You update the inactive boot partition and switch to it only if the update succeeded. If you have tested the new u-boot version, the risk of bricking your board is nearly 0.

2 thoughts on “Updating U-Boot with an A/B Strategy”

  1. Hi. Thank you for this article. I had a few doubts.
    On my imx-8mp device, u-boot is located in the raw partition of the emmc, at the very start of the emmc block. U-boot is loaded before a selection of memory devices can be done. So as per your article, flashing u-boot is possible in the boot partition?

    You mentioned that if the update of the u-boot is successful, you can switch to the alternate boot partition to boot the system. But what if something went wrong while loading the new u-boot and now you have no way of switching back to the other boot partition? Please correct me if I am wrong.

    1. Hi,

      Yes, flashing the u-boot image into /dev/mmcblk2boot[01] with dd works fine – as explained in the article. I have done it dozens of times manually or through SwUpdate.

      If the system fails to boot into the newly flashed boot partition, it will automatically fall back to old boot partition. This is guaranteed by the eMMC specification (version 4.1 or newer). Your iMX8M Plus supports this. You can check this by changing some bytes in the new u-boot image with a hex editor like ghex. Just make sure that you can tell apart the two u-boot images by their LOCALVERISON.

      Cheers,
      Burkhard

Leave a Reply

Your email address will not be published. Required fields are marked *