Simple bootloader for STM32L0 microcontrollers, written in Rust
The goal for this bootloader is to be simple and reliable. The bootloader should only do tasks that are not suitable to implement in user firmware.
Even for advanced scenario's such as Over-The-Air updates, the bootloader can still be kept simple. The update process can be split in two steps:
-
The user firmware somehow receives a new update candidate (this could be OTA, via UART, USB, or any other application-specific way).
-
The user firmware triggers a reset-to-bootloader. Bootloader verifies if the new firmware image is valid and then performs the update.
This keeps the bootloader compact and simple, while allowing a lot of flexibility for the user firmware on how and when updates should be performed.
This bootloader expects the firmware to be signed with ECC P-256
, sometimes refered to as prime256v1
.
The signature is appended to the end of the file as a raw 64-byte signature.
You need to create a private-public key pair. The private key is used to sign firmware images and should be kept secret (e.g. keep it offline, ideally on a hardware smartcard/yubikey). See docs on how to do it with a Yubikey
The public key (in .pem format) should be stored as pubkey.pem
(see pubkey.pem.example
in the repository).
An optional blacklist of known-bad firmwares can be written in blacklist.txt. To disable the blacklist, simply place an empty blacklist.txt in the project root.
NOTE this code MUST be built as a release. Compiler optimizations
are required to make the jump to usercode work correctly.
To make the bootloader work, some very low-level stuff needs to be done, including
writing directly to the MSP
register.
The register::msp::write()
call only works correctly if it is correctly inlined
by the compiler, otherwise the function call overhead itself messes up the stack pointer.
If you know a better way to do this (using stable rust), please let us know! Also note that this project is built with optimization flag "s". This is required in order to make the firmware fit in 32K of flash.
The bootloader supports multiple target boards. Board-specific source code is
specified by #[cfg(feature = "board-XXX")]
. Pass the desired board to cargo/bobbin
to build the right version. For example:
cargo build --release --features "board-6001-devkit"
Builds the 'board-6001-devkit' specific version.
You can also use bobbin to immediately flash the binary as well
bobbin load --bin mcu-bootloader-rust --release --features "board-6001-devkit"