Skip to content

Repository files navigation

cancan

CAN-based bootloader for embassy-based firmwares, powered by embassy_boot. Power-fail-safe A/B updates with arbitrary health checks and auto-rollback.

Design

Most of the heavy lifting is done by embassy_boot. That crate takes care of all of the hard stuff, like the actual flash handling, partition swapping, etc. This project adds a simple CAN protocol for using it, focussing on making including this capability in an application firmware as simple and painless as possible.

embassy_boot requires a flash partitioning scheme as follows (values are from STM32F105 example):

  0x00000000  +----------------------+
              | BOOTLOADER    24 KB  |
  0x00006000  +----------------------+
              | STATE          2 KB  |
  0x00006800  +----------------------+
              | ACTIVE       114 KB  | <- your firmware lives here
  0x00023000  +----------------------+
              | DFU          116 KB  |
  0x00040000  +----------------------+

Firmware updates are streamed to the board while its regular firmware is running (out of ACTIVE), stored in the DFU partition. Only if the complete firmware has been written and CRC-verified, is the board actually rebooted. The bootloader will swap ACTIVE and DFU.

After the new firmware has started executing, it is responsible for calling a method that marks it as correctly booted. If the board resets before that state is reached (broken clock config, panic during boot, watchdog firing, etc.), the bootloader will rollback.

Using state stored in STATE, and exposed by cancan at runtime, the application firmware can tell in which of these states it is running (e.g. if the current firmware has been booted into as a result of a rollback from an attempted update).

Protocol

Firmware is transmitted to the board via CAN using ISO-TP, reserving two CAN IDs (one for each direction), by default 0x7C0 and 0x7C1. Since flashing is intended to happen in situ, while the board itself and others are using the bus, you will have to ensure that these do not collide. You can change these IDs to whatever you want.

Nodes on the bus are addressed using a single byte, meaning that 256 boards can share a bus. The mechanism to assign each board a unique ID is out of scope, that's your problem.

Reproducibility

Using cancan-build, a timestamp and pseudorandom id are embedded in the firmware binary during each build. This allows querying the last time a board was flashed using the CLI and it also allows correctly detecting if the newly flashed firmware is actually the one running. This breaks reproducibility, with every build producing a new build id. You can force constant values for build id and timestamp using environment variables. See that crate.

Installation

CLI

The cancan CLI is installed on the host (Linux) system, and uses a SocketCAN interface to perform the flashing. To install it:

$ cargo install --path cancan-cli

Firmware

This repo features two examples, for STM32F105 and H743-based boards. A rough outline of the steps necessary to add CAN-based flashing to a firmware currently using something like probe-rs:

  • Ensure your firmware is small enough that half your flash is enough.
  • Disable any memory-x Embassy feature you are currently using. You will need to provide your own memory.x for the layout including the DFU partition. See examples.
  • Add a second, bootloader binary/crate to your project, mirroring the bootloader in the example. This one also needs a correct memory.x (consider the INCLUDE mechanism as shown in the example to keep the layout in sync between app and bootloader). Flash this once using probe-rs.
  • Figure out a way to assign your board a node ID. Hardcode it, read it from an env var during build, whatever.
  • Add cancan-build as a build dependency, call cancan_build::emit from your firmware's build script and include the generated metadata from your binary.
  • Build a CanCanConfig in your firmware and run the CanCan service next to your firmware, after calling CanCan::confirm to mark the running firmware as healthy.
  • Wire up CanCanRx and CanCanTx to ensure update packets reach cancan and the responses reach the host.

See the examples for details.

Usage

Flashing

To flash a board for the first time, first flash the bootloader using a runner like probe-rs:

$ cd examples/stm32f105/bootloader
$ cargo run --release

If this is the first time flashing this bootloader, getting errors after flashing is expected. Then, flash the application firmware once using probe-rs as well (ensure the correct runner is set in .cargo/config.toml).:

$ cd ../app
$ cargo run --release

From now on, you can flash new versions using cancan either manually:

$ cargo build --release
$ cancan flash 0x01 target/thumbv7m-none-eabi/release/cancan-example-stm32f105

... or by setting cancan to be the default runner using the firmware's .cargo/config.toml:

# .cargo/config.toml

[target.thumbv7m-none-eabi]
runner = "cancan flash 0x01"

Then you can just run cargo run [--release].

Querying Boards

To query a single board (firmware name, chip, build id/timestamp, boot state, uptime):

$ cancan info 0x01

To discover which node ids are live on the bus, probe all 256 and list the boards that answer:

$ cancan scan

Disclaimers

  • There is no cryptography or firmware signing, firmware is only CRCed, anyone on the bus can flash your board.
  • The MCU may lock up during active flash writes and real-time tasks may be disrupted. Obviously don't flash boards while they are doing something critical.

About

CAN-based A/B bootloader using embassy-boot

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages