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 versionlf_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.