rp2040
The rp2040
example uses a Raspberry Pi Pico Board. The board has 1 user LED which will be blinking at different frequencies depending on whether we're executing boot-firmware or update-firmware.
Raspberry Pi Pico's Boot Sequence
Raspberry Pi Pico board has an external QSPI Flash chip W25Q080
with support for cached execute-in-place.
It has a first stage bootloader baked into ROM. This is always the very first thing that runs when the RP2040 starts up.
The built-in bootloader has a fixed sequence, it checks if the BOOTSEL button is pressed, and if so, enters the USB mass storage mode for code upload.
If the BOOTSEL button is not pressed, and it looks like the flash contains a valid program, then it starts executing the “program” from flash.
In flash, first 256 bytes are second-stage bootloader which are required to configure external QSPI flash for high speed and efficient code access.
Note: For this example we are using two Pico boards, one board will be used as debug probe and other will be a target board. You can learn more about this here
Partitioning
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the Pico
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "rp2040")] pub const SECTOR_SIZE: usize = 0x1000; #[cfg(feature = "rp2040")] pub const PARTITION_SIZE: usize = 0x20000; #[cfg(feature = "rp2040")] pub const BOOT_PARTITION_ADDRESS: usize = 0x10020000; #[cfg(feature = "rp2040")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x10040000; #[cfg(feature = "rp2040")] pub const SWAP_PARTITION_ADDRESS: usize = 0x10060000; }
Note: Choose the number of sectors based on your boot and update firmware sizes.
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address 0x10000100
.
BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the Pico
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f334 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command and its output that should be produced.
cargo rp2040 build-sign-flash rustBoot 1234 1235
C:\Users\ABHISHEK\Documents\rustBoot> cargo rp2040 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target\debug\xtask.exe rp2040 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Finished release [optimized] target(s) in 0.21s
$ cargo build --release
Finished release [optimized] target(s) in 0.16s
$ cargo build --release
Finished release [optimized] target(s) in 0.26s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv6m-none-eabi/release/rp2040_bootfw -O binary rp2040_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv6m-none-eabi/release/rp2040_updtfw -O binary rp2040_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/rp2040_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `C:\Users\ABHISHEK\Documents\rustBoot\target\debug\rbsigner.exe mcu-image ../boards/sign_images/signed_images/rp2040_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: rp2040_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: rp2040_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1644 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/rp2040_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `C:\Users\ABHISHEK\Documents\rustBoot\target\debug\rbsigner.exe mcu-image ../boards/sign_images/signed_images/rp2040_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: rp2040_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: rp2040_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1612 bytes.
$ probe-rs-cli download --format Bin --base-address 0x10020000 --chip RP2040 rp2040_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 23.50KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 10.21KiB/s (eta 0s )
Finished in 0.494s
$ probe-rs-cli download --format Bin --base-address 0x10040000 --chip RP2040 rp2040_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 23.77KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 10.27KiB/s (eta 0s )
Finished in 0.495s
$ cargo flash --chip RP2040 --release
Finished release [optimized] target(s) in 0.18s
Flashing C:\Users\ABHISHEK\Documents\rustBoot\boards\target\thumbv6m-none-eabi\release\rp2040
Erasing sectors ✔ [00:00:00] [################################################################] 48.00KiB/48.00KiB @ 45.42KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [################################################################] 48.00KiB/48.00KiB @ 9.73KiB/s (eta 0s )
Finished in 3.174s
Verifying:
user led
is used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
user led
for a few seconds, at an interval of1 second
five times.- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
user led
at an interval of0.125 second
continuously.
- and set a