UTAT Device Firmware Upgrade with MCUboot
Documentation of the steps taken to implement MCUBoot for Device Firmware Upgrades.
Environnment config
Use the environment written in flake.nix by running nix develop
Set up Zephyr
Zephyr is the RTOS that runs on the Nucleo G431RB development board I'm using.
west init zephyrproject
cd zepyhrproject
west update
west zephyr-export
cd zepyhr
west sdk install
MCUBoot POC Implementation
MCUBoot is a bootloader for Zephyr, which allows multiple firmwares to exist on a board. MCUBoot can dynamically switch which firmware is active. This is useful in the event of one firmware failing.
Configure partition sizes
The devicetree specification file declares the hardware configuration. The flash storage needs three partitions, one for boot, and two for the two firmwares it will hold.
DeviceTree Specification documentation
The board I'm using has its DeviceTree Specification in zephyr/boards/st/nucleo_g431rb/nucleo_g431rb.dts
I created a partition for MCUBoot, two firmware slots, and additional storage in the flash storage.
&flash0 {
partitions {
#address-cells = <1>;
#size-cells = <1>;
ranges;
boot_partition: partition@0 {
compatible = "zephyr,mapped-partition";
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@C000 {
compatible = "zephyr,mapped-partition";
label = "image-0";
reg = <0x0000C000 0x0000A000>;
};
slot1_partition: partition@14000 {
compatible = "zephyr,mapped-partition";
label = "image-1";
reg = <0x00016000 0x0000A000>;
};
};
};
(Originally my partitions were 8000B each but MCUBoot would not build as its boot partition was too small. By using direct-xip there is no need for additional storage for copying images either.)
Enable building for MCUboot with direct-xip
Add these lines to bootloader/mcuboot/boot/zephyr/prj.conf
### Must be enabled to build applications for MCUboot
CONFIG_BOOTLOADER_MCUBOOT=y
### Allows in-place execution
CONFIG_BOOT_DIRECT_XIP=y
### Allows switching the image
CONFIG_BOOT_DIRECT_XIP_REVERT=y
Then in zephyr/samples/basic/blinky/prj.conf and zephyr/samples/basic/blinkyslow/prj.conf add the following:
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_IMG_MANAGER=y
CONFIG_FLASH=y
CONFIG_REBOOT=y
Enable switching firmware
Each time a peice of firmware is run, set the other one as the upgrade so it runs next time. Add this to the code early in main:
#include <zephyr/dfu/mcuboot.h>
#include <zephyr/sys/reboot.h>
int main() {
/* Confirm ourselves so MCUboot doesn't revert us on a later reset */
if (!boot_is_img_confirmed()) {
boot_write_img_confirmed();
}
/* Arm a permanent swap so the other image runs after the next reset */
boot_request_upgrade(BOOT_UPGRADE_PERMANENT);
...
}
Build the blinkys
The west build -DSB_CONFIG_BOOTLOADER_MCUBOOT=y flag enables MCUboot on the blinkys.
west build -p always -b nucleo_g431rb zephyr/samples/basic/blinky -d build/blinky --sysbuild -- -DSB_CONFIG_BOOTLOADER_MCUBOOT=y #-CONFIG_MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD=y
west build -p always -b nucleo_g431rb zephyr/samples/basic/blinkyslow -d build/blinkyslow --sysbuild -- -DSB_CONFIG_BOOTLOADER_MCUBOOT=y
Flash the blinkys
pyocd flash -t stm32g431rbtx build
/blinky/mcuboot/zephyr/zephyr.bin
pyocd flash -t stm32g431rbtx --base-address 0x0000C000 build/blinky/blinky/zephyr/zephyr.bin
(blinkyslow is a clone of blinky with a different frequency)
Set up crypto
python bootloader/mcuboot/scripts/imgtool.py keygen -k private.pem -t rsa-2048
openssl rsa -in private.pem -pubout > public.pem
Sign the blinkys
python bootloader/mcuboot/scripts/imgtool.py sign \
--key private.pem \
--header-size 0x200 \
--pad-header \
--align 8 \
--version 1.2 \
--slot-size 0x8000 \
build/blinky/zephyr/zephyr.bin \
blinky-signed.bin
python bootloader/mcuboot/scripts/imgtool.py sign \
--key private.pem \
--header-size 0x200 \
--pad-header \
--align 8 \
--version 1.2 \
--slot-size 0x8000 \
build/blinkyslow/zephyr/zephyr.bin \
blinkyslow-signed.bin
Build the firmware
west build -p always -b nucleo_g431rb -d build_mcuboot bootloader/mcuboot/boot/zephyr --
west build -p always -b nucleo_g431rb -d build_fast zephyr/samples/basic/blinky
west build -p always -b nucleo_g431rb -d build_slow zephyr/samples/basic/blinkyslow
Flash the firmware
su root allows flashing to usb devices, run nix develop again if using it.
flash0@0x08000000 in zephyr/dts/arm/st/g4/stm32g4.dtsi
pyocd flash -t stm32g431rbtx build_mcuboot/zephyr/zephyr.hex
pyocd flash -t stm32g431rbtx --base-address 0x0800C000 build_fast/zephyr/zephyr.bin
pyocd flash -t stm32g431rbtx --base-address 0x08014000 build_slow/zephyr/zephyr.bin