Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ A light-weight bootloader written in Rust with a fail-safe NOR-flash backed stat
This framework can run on any platform if support for the platform is implemented.
It is only opinionated with regards to how the state is stored.

Currently only supports the NXP IMXRT685S and IMXRT633S where it acts as a stage-two bootloader and copies the program to application RAM.
Also contains a tool for signing images, flashing them to the device, setting fuses (or shadow registers) containing crypto keys,
and an example application to showcase the bootloaders A/B state functionality for this family of chipsets.

## Organisation

This repository is split up into three parts:
Expand All @@ -16,28 +12,28 @@ This repository is split up into three parts:
* bootloader-tool: a command-line utility only used to perform operations related to the NXP RT685S platform.
It uses the NXP SPSDK tooling to generate keys, sign images, and flash them to the target device. Also integrates probe-rs and allows for attaching to the RTT buffer for displaying `defmt` output.
This tool is not relevant if you want to use `ec-slimloader` with any other platform.
* examples/mcxa-577app: example bootloader and blinky demo application, can be run on MCXA5xx evalutation kit.

The libraries are split out as follows:
* ec-slimloader: general library crate providing a basic structure to build your bootloader binary application.
* ec-slimloader-state: library crate with all code relating to managing the state journal. Used by both the bootloader and the application to change which image slot should be booted.
* ec-slimloader-imxrt: library crate implementing support for the NXP IMXRT685S and IMXRT633S.
* imxrt-rom: library crate implementing Rust support for the NXP ROM API which provides access to fuses and allows calling into a verification routine for images.
* ec-slimloader-mcxa: library crate implementing support for the NXP MCXA5xx family with PQC cryptography support.

## How it works
Assuming your platform is already supported, you can define:
* a region of NOR-flash memory containing at least 2 pages for the bootloader state.
* at least two regions of any memory that will fit an application image.

Using the library crate for your platform (like `ec-slimloader-imxrt`) you can then implement your own bootloader binary by calling the `start` function in the `ec-slimloader` library crate.
Using the library crate for your platform (e.g., `ec-slimloader-mcxa`) you can implement your own bootloader binary by calling the `start` function in the `ec-slimloader` library crate.

The `ec-slimloader` crate will handle for you:
* it will read from the state journal what image slot will be booted.
* on subsequent reboots, it will fall back to your defined backup slot if you do not mark your current application image as `confirmed`.

However, some aspects are handled by the platform support crate (and can differ from project-to-project):
* how application images are loaded. For `ec-slimloader-imxrt` images are copied to RAM in a quite chip-specific way. Typically for other platforms you might want to swap images between on-chip NOR flash and external NOR flash. The latter method is not implemented in this repository (yet).
* how application images are verified. By default the images themselves are not checked at all. `ec-slimloader-imxrt` leverages the native NXP authentication routines to check image integrity.
* how application images are bootloaded, or in other words are jumped to. This differs for cortex-m or RISCV processors.
* how application images are loaded.
* how application images are verified.
* how application images are bootloaded, or in other words are jumped to.

Even when using `ec-slimloader-imxrt`, you will still have to implement a few details:
* from what memory is the `ec-slimloader` started, and what memory range is used for the bootloader data?
Expand All @@ -49,7 +45,7 @@ Finally, your application needs to also work with the state journal to:
* after rebooting, mark the current image slot from which the application is running as `confirmed`.
If the application does not do this, the bootloader will load the old 'backup' image and mark the current boot as `failed`.

For a full tour on how to use this framework, please refer to the `examples/rt685s` folder.
For a full tour on how to use this framework for MCXA5xx, refer to the MCX examples.

## Quick guide
This guide details how to use this repository on the NXP MIMXRT685S-EVK. First step is compiling the bootloader and application:
Expand Down Expand Up @@ -123,3 +119,6 @@ cargo run -- run application -i ../examples/rt685s/target/thumbv8m.main-none-eab
You can use the `USER_1` button to change the state journal to either `confirmed` or try the other slot in state `initial` if the current image is already `confirmed`.

You can use the `USER_2` button the reboot into the bootloader, which will set an image to `failed` if it does not verify or if it was in `attempting` without putting the state in `confirmed`.

The following describes the process for generating artifacts and signing/flashing for MCXA:
...
14 changes: 14 additions & 0 deletions examples/mcxa-577app/app/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[build]
target = "thumbv8m.main-none-eabihf"

[target.thumbv8m.main-none-eabihf]
runner = "echo"

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = [
"-C", "linker=flip-link",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "link-arg=--nmagic",
"-C", "force-frame-pointers=yes",
]
23 changes: 23 additions & 0 deletions examples/mcxa-577app/app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "mcxa-577app"
version = "0.1.0"
edition = "2021"

[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = { version = "0.7", features = ["set-sp", "set-vtor"] }
defmt = "1.0"
defmt-rtt = "1.0"
embassy-mcxa = { git = "https://github.com/embassy-rs/embassy", default-features = false, features = ["rt", "defmt", "mcxa5xx"] }
embassy-executor = { git = "https://github.com/embassy-rs/embassy", default-features = false, features = ["platform-cortex-m", "executor-thread"] }
embassy-time = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "defmt-timestamp-uptime"] }
panic-probe = { version = "1.0", features = ["print-defmt"] }

[profile.dev]
panic = "abort"

[profile.release]
lto = false # Disable LTO to prevent stripping debug patterns
opt-level = 2 # Standard optimization


21 changes: 21 additions & 0 deletions examples/mcxa-577app/app/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());

// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}
11 changes: 11 additions & 0 deletions examples/mcxa-577app/app/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
MEMORY
{
/* MCXA577 app memory map */
/* NOTE 1 K = 1 KiBi = 1024 bytes */
/* Bootloader uses 0x0000_0000..0x0000_FFFF (64KiB). App starts at slot_a = 0x0001_0000. */
FLASH (rx) : ORIGIN = 0x00010000, LENGTH = 0x001F0000
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

/* Stack grows down from end of RAM */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
67 changes: 67 additions & 0 deletions examples/mcxa-577app/app/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::Timer;
use hal::bind_interrupts;
use hal::dma::DmaChannel;
use hal::gpio::{DriveStrength, Level, Output, SlewRate};
use hal::peripherals::SGI0;
use hal::sgi::hash::{DmaHasher, HashSize};
use hal::sgi::{InterruptHandler, Sgi};
use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};

bind_interrupts!(struct Irqs {
SGI => InterruptHandler<SGI0>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut p = hal::init(hal::config::Config::default());

defmt::info!("Blinky example with a sprinkle of SGI hashing");

let mut dma_ch0 = DmaChannel::new(p.DMA0_CH0.reborrow());
let mut hash_result = [0u8; 48];
let mut input_data = [0u8; 256];

for (index, byte) in input_data.iter_mut().enumerate() {
*byte = index as u8;
}

let sgi = Sgi::new(p.SGI0.reborrow(), Irqs).unwrap();
match DmaHasher::start_and_finalize(sgi, &mut dma_ch0, HashSize::Sha384, &input_data, &mut hash_result)
.await
{
Ok(()) => defmt::info!("DMA hash: {=[u8]:x}", &hash_result),
Err(e) => defmt::error!("DMA hash failed: {:?}", defmt::Debug2Format(&e)),
}

let mut red = Output::new(p.P2_14, Level::High, DriveStrength::Normal, SlewRate::Fast);
let mut green = Output::new(p.P2_22, Level::High, DriveStrength::Normal, SlewRate::Fast);
let mut blue = Output::new(p.P2_23, Level::High, DriveStrength::Normal, SlewRate::Fast);

let mut rate = 250;

defmt::info!("It's showtime...");

loop {
if rate > 1000 {
rate = 250; // wrap rate to avoid overflow and excessively long timers.
}
red.toggle();
Timer::after_millis(rate).await;

red.toggle();
green.toggle();
Timer::after_millis(rate).await;

green.toggle();
blue.toggle();
Timer::after_millis(rate).await;
blue.toggle();

Timer::after_millis(rate).await;
rate = rate.wrapping_add(100);
}
}
17 changes: 17 additions & 0 deletions examples/mcxa-577app/bootloader/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[build]
target = "thumbv8m.main-none-eabihf"

[target.thumbv8m.main-none-eabihf]
runner = "probe-rs run --chip MCXA577 --chip-description-path MCXA_custom.yaml"

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = [
"-C", "linker=flip-link",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "link-arg=--nmagic",
"-C", "force-frame-pointers=yes",
]

[env]
DEFMT_LOG = "trace"
37 changes: 37 additions & 0 deletions examples/mcxa-577app/bootloader/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "mcxa-577app-bootloader"
version = "0.1.0"
edition = "2021"

[features]
default = ["defmt"]
defmt = ["dep:defmt", "dep:defmt-or-log", "dep:defmt-rtt", "defmt-or-log/defmt"]
log = ["dep:defmt-or-log", "defmt-or-log/log"]

[[bin]]
name = "mcxa-577app-bootloader"
path = "src/main.rs"

[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"], default-features = false }
cortex-m-rt = "0.7"
panic-halt = "0.2"
defmt = { version = "1.0", optional = true }
defmt-or-log = { version = "0.2.3", optional = true }
defmt-rtt = { version = "1.0", optional = true }

ec-slimloader-mcxa = { path = "../../../libs/ec-slimloader-mcxa", default-features = false, features = ["internal-only", "mcxa5xx", "defmt"] }
ec-slimloader = { path = "../../../libs/ec-slimloader", default-features = false }

embassy-executor = { git = "https://github.com/embassy-rs/embassy", default-features = false, features = ["platform-cortex-m", "executor-thread"] }

[patch.crates-io]
embassy-time = { git = "https://github.com/embassy-rs/embassy" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy" }
embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy" }

[profile.release]
debug = 2 # Full DWARF info for defmt/probe-rs decoding
lto = false # Disable LTO to prevent stripping debug patterns
opt-level = 2 # Standard optimization
panic = "abort" # Smaller binaries
18 changes: 18 additions & 0 deletions examples/mcxa-577app/bootloader/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());

// Only re-run if memory.x changes.
println!("cargo:rerun-if-changed=memory.x");
}
15 changes: 15 additions & 0 deletions examples/mcxa-577app/bootloader/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
MEMORY
{
/* MCXA577 has 2MB flash total.
* This example reserves the first 64KB for the bootloader.
* Secure alias: 0x1000_0000 (Matrix0 Target Port0, Secure, All Initiators).
*/
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64K

/* Secure SRAM alias: 0x3000_0000 (Matrix0 Target Port 4, Secure, All Initiators).
*/
RAM (rwx) : ORIGIN = 0x30000000, LENGTH = 64K
}

/* Stack grows down from end of RAM */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
21 changes: 21 additions & 0 deletions examples/mcxa-577app/bootloader/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![no_std]
#![no_main]

#[cfg(any(feature = "defmt", feature = "log"))]
use defmt_or_log::info;
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_executor::Spawner;
use panic_halt as _;

const JOURNAL_BUFFER_SIZE: usize = 4096;

#[cfg(feature = "defmt")]
defmt::timestamp!("{=u32}", 0);

#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
#[cfg(any(feature = "defmt", feature = "log"))]
info!("Starting MCXA bootloader");
ec_slimloader::start::<ec_slimloader_mcxa::McxaBoard, JOURNAL_BUFFER_SIZE>(ec_slimloader_mcxa::Config).await
}
15 changes: 15 additions & 0 deletions libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"ec-slimloader",
"ec-slimloader-imxrt",
"ec-slimloader-state",
"ec-slimloader-mcxa",
"imxrt-rom",
]

Expand All @@ -21,6 +22,20 @@ defmt = "0.3.7"
defmt-or-log = { version = "0.2.2", default-features = false }
embedded-storage-async = "0.4.1"
embassy-sync = "0.7.2"
embassy-futures = "0.1"
embassy-time = "0.5"
embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt.git", default-features = false, features = ["mimxrt685s"] }
embassy-mcxa = { git = "https://github.com/embassy-rs/embassy", rev = "30d627365bc9c62cf391aa02b691a4ae1a95e6cc", default-features = false }
log = "0.4"

cortex-m = { version = "0.7.7" }
panic-probe = "0.3"
arbitrary = "1.4"
crc = "3.2"
num_enum = { version = "0.7", default-features = false }
device-driver = "1.0"

[patch.crates-io]
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "30d627365bc9c62cf391aa02b691a4ae1a95e6cc" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "30d627365bc9c62cf391aa02b691a4ae1a95e6cc" }
embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy", rev = "30d627365bc9c62cf391aa02b691a4ae1a95e6cc" }
14 changes: 13 additions & 1 deletion libs/ec-slimloader-imxrt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl<C: ImxrtConfig + BootStatePolicy> Board for Imxrt<C> {
&mut self.journal
}

async fn check_and_boot(&mut self, slot: &Slot) -> BootError {
async fn check_and_boot<const JOURNAL_BUFFER_SIZE: usize>(&mut self, slot: &Slot) -> BootError {
let Some(slot_partition) = self.slots.get_mut(u8::from(*slot) as usize) else {
return BootError::SlotUnknown;
};
Expand Down Expand Up @@ -204,4 +204,16 @@ impl<C: ImxrtConfig + BootStatePolicy> Board for Imxrt<C> {
cortex_m::asm::wfi();
}
}

fn arm_mcu_reset(&mut self) -> ! {
const AIRCR: *mut u32 = 0xE000ED0C as *mut u32;
const AIRCR_VECTKEY: u32 = 0x5FA << 16;
const AIRCR_SYSRESETREQ: u32 = 1 << 2;
unsafe {
core::ptr::write_volatile(AIRCR, AIRCR_VECTKEY | AIRCR_SYSRESETREQ);
}
loop {
cortex_m::asm::wfi();
}
}
}
Loading
Loading