From e0775fbc8ab1ecc83bce42fe6e11accf481bc9e1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 11 Jan 2024 18:55:59 +0100 Subject: Flatten embassy-boot dir tree --- .github/ci/doc.sh | 8 +- .github/ci/test.sh | 6 +- ci.sh | 8 +- embassy-boot-nrf/Cargo.toml | 44 +++ embassy-boot-nrf/README.md | 11 + embassy-boot-nrf/src/fmt.rs | 258 +++++++++++++ embassy-boot-nrf/src/lib.rs | 145 ++++++++ embassy-boot-rp/Cargo.toml | 79 ++++ embassy-boot-rp/README.md | 12 + embassy-boot-rp/build.rs | 8 + embassy-boot-rp/src/fmt.rs | 258 +++++++++++++ embassy-boot-rp/src/lib.rs | 90 +++++ embassy-boot-stm32/Cargo.toml | 70 ++++ embassy-boot-stm32/README.md | 10 + embassy-boot-stm32/build.rs | 8 + embassy-boot-stm32/src/fmt.rs | 258 +++++++++++++ embassy-boot-stm32/src/lib.rs | 44 +++ embassy-boot/Cargo.toml | 51 +++ embassy-boot/README.md | 35 ++ embassy-boot/boot/Cargo.toml | 51 --- embassy-boot/boot/README.md | 35 -- embassy-boot/boot/src/boot_loader.rs | 411 --------------------- .../boot/src/digest_adapters/ed25519_dalek.rs | 30 -- embassy-boot/boot/src/digest_adapters/mod.rs | 5 - embassy-boot/boot/src/digest_adapters/salty.rs | 29 -- embassy-boot/boot/src/firmware_updater/asynch.rs | 329 ----------------- embassy-boot/boot/src/firmware_updater/blocking.rs | 340 ----------------- embassy-boot/boot/src/firmware_updater/mod.rs | 49 --- embassy-boot/boot/src/fmt.rs | 258 ------------- embassy-boot/boot/src/lib.rs | 323 ---------------- embassy-boot/boot/src/mem_flash.rs | 170 --------- embassy-boot/boot/src/test_flash/asynch.rs | 64 ---- embassy-boot/boot/src/test_flash/blocking.rs | 68 ---- embassy-boot/boot/src/test_flash/mod.rs | 5 - embassy-boot/nrf/Cargo.toml | 44 --- embassy-boot/nrf/README.md | 11 - embassy-boot/nrf/src/fmt.rs | 258 ------------- embassy-boot/nrf/src/lib.rs | 145 -------- embassy-boot/rp/Cargo.toml | 79 ---- embassy-boot/rp/README.md | 12 - embassy-boot/rp/build.rs | 8 - embassy-boot/rp/src/fmt.rs | 258 ------------- embassy-boot/rp/src/lib.rs | 90 ----- embassy-boot/src/boot_loader.rs | 411 +++++++++++++++++++++ embassy-boot/src/digest_adapters/ed25519_dalek.rs | 30 ++ embassy-boot/src/digest_adapters/mod.rs | 5 + embassy-boot/src/digest_adapters/salty.rs | 29 ++ embassy-boot/src/firmware_updater/asynch.rs | 329 +++++++++++++++++ embassy-boot/src/firmware_updater/blocking.rs | 340 +++++++++++++++++ embassy-boot/src/firmware_updater/mod.rs | 49 +++ embassy-boot/src/fmt.rs | 258 +++++++++++++ embassy-boot/src/lib.rs | 323 ++++++++++++++++ embassy-boot/src/mem_flash.rs | 170 +++++++++ embassy-boot/src/test_flash/asynch.rs | 64 ++++ embassy-boot/src/test_flash/blocking.rs | 68 ++++ embassy-boot/src/test_flash/mod.rs | 5 + embassy-boot/stm32/Cargo.toml | 70 ---- embassy-boot/stm32/README.md | 10 - embassy-boot/stm32/build.rs | 8 - embassy-boot/stm32/src/fmt.rs | 258 ------------- embassy-boot/stm32/src/lib.rs | 44 --- embassy-usb-dfu/Cargo.toml | 2 +- examples/boot/application/nrf/Cargo.toml | 4 +- examples/boot/application/rp/Cargo.toml | 2 +- examples/boot/application/stm32f3/Cargo.toml | 2 +- examples/boot/application/stm32f7/Cargo.toml | 2 +- examples/boot/application/stm32h7/Cargo.toml | 2 +- examples/boot/application/stm32l0/Cargo.toml | 2 +- examples/boot/application/stm32l1/Cargo.toml | 2 +- examples/boot/application/stm32l4/Cargo.toml | 2 +- examples/boot/application/stm32wb-dfu/Cargo.toml | 2 +- examples/boot/application/stm32wl/Cargo.toml | 2 +- examples/boot/bootloader/nrf/Cargo.toml | 2 +- examples/boot/bootloader/rp/Cargo.toml | 2 +- examples/boot/bootloader/stm32/Cargo.toml | 2 +- examples/boot/bootloader/stm32wb-dfu/Cargo.toml | 2 +- 76 files changed, 3489 insertions(+), 3489 deletions(-) create mode 100644 embassy-boot-nrf/Cargo.toml create mode 100644 embassy-boot-nrf/README.md create mode 100644 embassy-boot-nrf/src/fmt.rs create mode 100644 embassy-boot-nrf/src/lib.rs create mode 100644 embassy-boot-rp/Cargo.toml create mode 100644 embassy-boot-rp/README.md create mode 100644 embassy-boot-rp/build.rs create mode 100644 embassy-boot-rp/src/fmt.rs create mode 100644 embassy-boot-rp/src/lib.rs create mode 100644 embassy-boot-stm32/Cargo.toml create mode 100644 embassy-boot-stm32/README.md create mode 100644 embassy-boot-stm32/build.rs create mode 100644 embassy-boot-stm32/src/fmt.rs create mode 100644 embassy-boot-stm32/src/lib.rs create mode 100644 embassy-boot/Cargo.toml create mode 100644 embassy-boot/README.md delete mode 100644 embassy-boot/boot/Cargo.toml delete mode 100644 embassy-boot/boot/README.md delete mode 100644 embassy-boot/boot/src/boot_loader.rs delete mode 100644 embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs delete mode 100644 embassy-boot/boot/src/digest_adapters/mod.rs delete mode 100644 embassy-boot/boot/src/digest_adapters/salty.rs delete mode 100644 embassy-boot/boot/src/firmware_updater/asynch.rs delete mode 100644 embassy-boot/boot/src/firmware_updater/blocking.rs delete mode 100644 embassy-boot/boot/src/firmware_updater/mod.rs delete mode 100644 embassy-boot/boot/src/fmt.rs delete mode 100644 embassy-boot/boot/src/lib.rs delete mode 100644 embassy-boot/boot/src/mem_flash.rs delete mode 100644 embassy-boot/boot/src/test_flash/asynch.rs delete mode 100644 embassy-boot/boot/src/test_flash/blocking.rs delete mode 100644 embassy-boot/boot/src/test_flash/mod.rs delete mode 100644 embassy-boot/nrf/Cargo.toml delete mode 100644 embassy-boot/nrf/README.md delete mode 100644 embassy-boot/nrf/src/fmt.rs delete mode 100644 embassy-boot/nrf/src/lib.rs delete mode 100644 embassy-boot/rp/Cargo.toml delete mode 100644 embassy-boot/rp/README.md delete mode 100644 embassy-boot/rp/build.rs delete mode 100644 embassy-boot/rp/src/fmt.rs delete mode 100644 embassy-boot/rp/src/lib.rs create mode 100644 embassy-boot/src/boot_loader.rs create mode 100644 embassy-boot/src/digest_adapters/ed25519_dalek.rs create mode 100644 embassy-boot/src/digest_adapters/mod.rs create mode 100644 embassy-boot/src/digest_adapters/salty.rs create mode 100644 embassy-boot/src/firmware_updater/asynch.rs create mode 100644 embassy-boot/src/firmware_updater/blocking.rs create mode 100644 embassy-boot/src/firmware_updater/mod.rs create mode 100644 embassy-boot/src/fmt.rs create mode 100644 embassy-boot/src/lib.rs create mode 100644 embassy-boot/src/mem_flash.rs create mode 100644 embassy-boot/src/test_flash/asynch.rs create mode 100644 embassy-boot/src/test_flash/blocking.rs create mode 100644 embassy-boot/src/test_flash/mod.rs delete mode 100644 embassy-boot/stm32/Cargo.toml delete mode 100644 embassy-boot/stm32/README.md delete mode 100644 embassy-boot/stm32/build.rs delete mode 100644 embassy-boot/stm32/src/fmt.rs delete mode 100644 embassy-boot/stm32/src/lib.rs diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 0bbe7f690..aaccb8a67 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -16,10 +16,10 @@ mv rust-toolchain-nightly.toml rust-toolchain.toml # which makes rustup very sad rustc --version > /dev/null -docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup -docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup -docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup -docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup +docserver-builder -i ./embassy-boot -o webroot/crates/embassy-boot/git.zup +docserver-builder -i ./embassy-boot-nrf -o webroot/crates/embassy-boot-nrf/git.zup +docserver-builder -i ./embassy-boot-rp -o webroot/crates/embassy-boot-rp/git.zup +docserver-builder -i ./embassy-boot-stm32 -o webroot/crates/embassy-boot-stm32/git.zup docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup diff --git a/.github/ci/test.sh b/.github/ci/test.sh index b6a5bcd56..8a58939f6 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -14,9 +14,9 @@ cargo test --manifest-path ./embassy-hal-internal/Cargo.toml cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue,mock-driver cargo test --manifest-path ./embassy-time-driver/Cargo.toml -cargo test --manifest-path ./embassy-boot/boot/Cargo.toml -cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-dalek -cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-salty +cargo test --manifest-path ./embassy-boot/Cargo.toml +cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-dalek +cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-salty cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nrf52840,time-driver-rtc1,gpiote diff --git a/ci.sh b/ci.sh index ac0457679..7805c8ad1 100755 --- a/ci.sh +++ b/ci.sh @@ -136,10 +136,10 @@ cargo batch \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \ --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \ - --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ - --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ - --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \ - --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ --- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ diff --git a/embassy-boot-nrf/Cargo.toml b/embassy-boot-nrf/Cargo.toml new file mode 100644 index 000000000..7fc53648a --- /dev/null +++ b/embassy-boot-nrf/Cargo.toml @@ -0,0 +1,44 @@ +[package] +edition = "2021" +name = "embassy-boot-nrf" +version = "0.1.0" +description = "Bootloader lib for nRF chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot-nrf/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-nrf/src/" +features = ["embassy-nrf/nrf52840"] +target = "thumbv7em-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } + +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-nrf = { version = "0.1.0", path = "../embassy-nrf", default-features = false } +embassy-boot = { version = "0.1.0", path = "../embassy-boot" } +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +nrf-softdevice-mbr = { version = "0.2.0", optional = true } + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-nrf/defmt", +] +softdevice = [ + "nrf-softdevice-mbr", +] diff --git a/embassy-boot-nrf/README.md b/embassy-boot-nrf/README.md new file mode 100644 index 000000000..9dc5b0eb9 --- /dev/null +++ b/embassy-boot-nrf/README.md @@ -0,0 +1,11 @@ +# embassy-boot-nrf + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for nRF. + +## Features + +* Load applications with or without the softdevice. +* Configure bootloader partitions based on linker script. +* Using watchdog timer to detect application failure. diff --git a/embassy-boot-nrf/src/fmt.rs b/embassy-boot-nrf/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot-nrf/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot-nrf/src/lib.rs b/embassy-boot-nrf/src/lib.rs new file mode 100644 index 000000000..5b20a93c6 --- /dev/null +++ b/embassy-boot-nrf/src/lib.rs @@ -0,0 +1,145 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, + FirmwareUpdaterConfig, +}; +use embassy_nrf::nvmc::PAGE_SIZE; +use embassy_nrf::peripherals::WDT; +use embassy_nrf::wdt; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for nRF devices. +pub struct BootLoader; + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); + Self + } + + /// Boots the application without softdevice mechanisms. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + #[cfg(not(feature = "softdevice"))] + pub unsafe fn load(self, start: u32) -> ! { + let mut p = cortex_m::Peripherals::steal(); + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + cortex_m::asm::bootload(start as *const u32) + } + + /// Boots the application assuming softdevice is present. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + #[cfg(feature = "softdevice")] + pub unsafe fn load(self, _app: u32) -> ! { + use nrf_softdevice_mbr as mbr; + const NRF_SUCCESS: u32 = 0; + + // Address of softdevice which we'll forward interrupts to + let addr = 0x1000; + let mut cmd = mbr::sd_mbr_command_t { + command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, + params: mbr::sd_mbr_command_t__bindgen_ty_1 { + irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr }, + }, + }; + let ret = mbr::sd_mbr_command(&mut cmd); + assert_eq!(ret, NRF_SUCCESS); + + let msp = *(addr as *const u32); + let rv = *((addr + 4) as *const u32); + + trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv); + + // These instructions perform the following operations: + // + // * Modify control register to use MSP as stack pointer (clear spsel bit) + // * Synchronize instruction barrier + // * Initialize stack pointer (0x1000) + // * Set link register to not return (0xFF) + // * Jump to softdevice reset vector + core::arch::asm!( + "mrs {tmp}, CONTROL", + "bics {tmp}, {spsel}", + "msr CONTROL, {tmp}", + "isb", + "msr MSP, {msp}", + "mov lr, {new_lr}", + "bx {rv}", + // `out(reg) _` is not permitted in a `noreturn` asm! call, + // so instead use `in(reg) 0` and don't restore it afterwards. + tmp = in(reg) 0, + spsel = in(reg) 2, + new_lr = in(reg) 0xFFFFFFFFu32, + msp = in(reg) msp, + rv = in(reg) rv, + options(noreturn), + ); + } +} + +/// A flash implementation that wraps any flash and will pet a watchdog when touching flash. +pub struct WatchdogFlash { + flash: FLASH, + wdt: wdt::WatchdogHandle, +} + +impl WatchdogFlash { + /// Start a new watchdog with a given flash and WDT peripheral and a timeout + pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { + let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { + Ok(x) => x, + Err(_) => { + // In case the watchdog is already running, just spin and let it expire, since + // we can't configure it anyway. This usually happens when we first program + // the device and the watchdog was previously active + info!("Watchdog already active with wrong config, waiting for it to timeout..."); + loop {} + } + }; + Self { flash, wdt } + } +} + +impl ErrorType for WatchdogFlash { + type Error = FLASH::Error; +} + +impl NorFlash for WatchdogFlash { + const WRITE_SIZE: usize = FLASH::WRITE_SIZE; + const ERASE_SIZE: usize = FLASH::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.write(offset, data) + } +} + +impl ReadNorFlash for WatchdogFlash { + const READ_SIZE: usize = FLASH::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy-boot-rp/Cargo.toml b/embassy-boot-rp/Cargo.toml new file mode 100644 index 000000000..dacb27747 --- /dev/null +++ b/embassy-boot-rp/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2021" +name = "embassy-boot-rp" +version = "0.1.0" +description = "Bootloader lib for RP2040 chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-rp/src/" +target = "thumbv6m-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-rp = { version = "0.1.0", path = "../embassy-rp", default-features = false } +embassy-boot = { version = "0.1.0", path = "../embassy-boot" } +embassy-time = { version = "0.2.0", path = "../embassy-time" } + +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-rp/defmt", +] +log = [ + "dep:log", + "embassy-boot/log", + "embassy-rp/log", +] +debug = ["defmt-rtt"] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy-boot-rp/README.md b/embassy-boot-rp/README.md new file mode 100644 index 000000000..b664145a9 --- /dev/null +++ b/embassy-boot-rp/README.md @@ -0,0 +1,12 @@ +# embassy-boot-rp + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for RP2040. + +NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. diff --git a/embassy-boot-rp/build.rs b/embassy-boot-rp/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot-rp/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } +} diff --git a/embassy-boot-rp/src/fmt.rs b/embassy-boot-rp/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot-rp/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot-rp/src/lib.rs b/embassy-boot-rp/src/lib.rs new file mode 100644 index 000000000..07a5b3f4d --- /dev/null +++ b/embassy-boot-rp/src/lib.rs @@ -0,0 +1,90 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, + FirmwareUpdaterConfig, State, +}; +use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; +use embassy_rp::peripherals::{FLASH, WATCHDOG}; +use embassy_rp::watchdog::Watchdog; +use embassy_time::Duration; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for RP2040 devices. +pub struct BootLoader; + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self + } + + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(self, start: u32) -> ! { + trace!("Loading app at 0x{:x}", start); + #[allow(unused_mut)] + let mut p = cortex_m::Peripherals::steal(); + #[cfg(not(armv6m))] + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + + cortex_m::asm::bootload(start as *const u32) + } +} + +/// A flash implementation that will feed a watchdog when touching flash. +pub struct WatchdogFlash<'d, const SIZE: usize> { + flash: Flash<'d, FLASH, Blocking, SIZE>, + watchdog: Watchdog, +} + +impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { + /// Start a new watchdog with a given flash and watchdog peripheral and a timeout + pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { + let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); + let mut watchdog = Watchdog::new(watchdog); + watchdog.start(timeout); + Self { flash, watchdog } + } +} + +impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { + type Error = as ErrorType>::Error; +} + +impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { + const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE; + const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_write(offset, data) + } +} + +impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { + const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy-boot-stm32/Cargo.toml b/embassy-boot-stm32/Cargo.toml new file mode 100644 index 000000000..f4e31bae8 --- /dev/null +++ b/embassy-boot-stm32/Cargo.toml @@ -0,0 +1,70 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32" +version = "0.1.0" +description = "Bootloader lib for STM32 chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot-stm32/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-stm32/src/" +features = ["embassy-stm32/stm32f429zi"] +target = "thumbv7em-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false } +embassy-boot = { version = "0.1.0", path = "../embassy-boot" } +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +[features] +defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"] +log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"] +debug = ["defmt-rtt"] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy-boot-stm32/README.md b/embassy-boot-stm32/README.md new file mode 100644 index 000000000..f6dadc8e7 --- /dev/null +++ b/embassy-boot-stm32/README.md @@ -0,0 +1,10 @@ +# embassy-boot-stm32 + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for STM32. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. diff --git a/embassy-boot-stm32/build.rs b/embassy-boot-stm32/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot-stm32/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } +} diff --git a/embassy-boot-stm32/src/fmt.rs b/embassy-boot-stm32/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot-stm32/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot-stm32/src/lib.rs b/embassy-boot-stm32/src/lib.rs new file mode 100644 index 000000000..4b4091ac9 --- /dev/null +++ b/embassy-boot-stm32/src/lib.rs @@ -0,0 +1,44 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, + FirmwareUpdaterConfig, State, +}; +use embedded_storage::nor_flash::NorFlash; + +/// A bootloader for STM32 devices. +pub struct BootLoader { + /// The reported state of the bootloader after preparing for boot + pub state: State, +} + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self { state } + } + + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(self, start: u32) -> ! { + trace!("Loading app at 0x{:x}", start); + #[allow(unused_mut)] + let mut p = cortex_m::Peripherals::steal(); + #[cfg(not(armv6m))] + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + + cortex_m::asm::bootload(start as *const u32) + } +} diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml new file mode 100644 index 000000000..a70849018 --- /dev/null +++ b/embassy-boot/Cargo.toml @@ -0,0 +1,51 @@ +[package] +edition = "2021" +name = "embassy-boot" +version = "0.1.1" +description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +digest = "0.10" +log = { version = "0.4", optional = true } +ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } +embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +salty = { version = "0.3", optional = true } +signature = { version = "2.0", default-features = false } + +[dev-dependencies] +log = "0.4" +env_logger = "0.9" +rand = "0.8" +futures = { version = "0.3", features = ["executor"] } +sha1 = "0.10.5" +critical-section = { version = "1.1.1", features = ["std"] } +ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } + +[features] +ed25519-dalek = ["dep:ed25519-dalek", "_verify"] +ed25519-salty = ["dep:salty", "_verify"] + +#Internal features +_verify = [] diff --git a/embassy-boot/README.md b/embassy-boot/README.md new file mode 100644 index 000000000..3c2d45e96 --- /dev/null +++ b/embassy-boot/README.md @@ -0,0 +1,35 @@ +# embassy-boot + +An [Embassy](https://embassy.dev) project. + +A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. + +The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. + +By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. + +## Overview + +The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: + +* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. +* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application. +* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition. +* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. + +For any partition, the following preconditions are required: + +* Partitions must be aligned on the page size. +* Partitions must be a multiple of the page size. + +The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. + +For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html). + +## Hardware support + +The bootloader supports different hardware in separate crates: + +* `embassy-boot-nrf` - for the nRF microcontrollers. +* `embassy-boot-rp` - for the RP2040 microcontrollers. +* `embassy-boot-stm32` - for the STM32 microcontrollers. diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml deleted file mode 100644 index 3c84ffcd3..000000000 --- a/embassy-boot/boot/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot" -version = "0.1.1" -description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" -target = "thumbv7em-none-eabi" -features = ["defmt"] - -[package.metadata.docs.rs] -features = ["defmt"] - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } -digest = "0.10" -log = { version = "0.4", optional = true } -ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -salty = { version = "0.3", optional = true } -signature = { version = "2.0", default-features = false } - -[dev-dependencies] -log = "0.4" -env_logger = "0.9" -rand = "0.8" -futures = { version = "0.3", features = ["executor"] } -sha1 = "0.10.5" -critical-section = { version = "1.1.1", features = ["std"] } -ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } - -[features] -ed25519-dalek = ["dep:ed25519-dalek", "_verify"] -ed25519-salty = ["dep:salty", "_verify"] - -#Internal features -_verify = [] diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md deleted file mode 100644 index 3c2d45e96..000000000 --- a/embassy-boot/boot/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# embassy-boot - -An [Embassy](https://embassy.dev) project. - -A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. - -The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. - -By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. - -## Overview - -The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: - -* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. -* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application. -* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition. -* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. - -For any partition, the following preconditions are required: - -* Partitions must be aligned on the page size. -* Partitions must be a multiple of the page size. - -The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. - -For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html). - -## Hardware support - -The bootloader supports different hardware in separate crates: - -* `embassy-boot-nrf` - for the nRF microcontrollers. -* `embassy-boot-rp` - for the RP2040 microcontrollers. -* `embassy-boot-stm32` - for the STM32 microcontrollers. diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs deleted file mode 100644 index e568001bc..000000000 --- a/embassy-boot/boot/src/boot_loader.rs +++ /dev/null @@ -1,411 +0,0 @@ -use core::cell::RefCell; - -use embassy_embedded_hal::flash::partition::BlockingPartition; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; - -use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; - -/// Errors returned by bootloader -#[derive(PartialEq, Eq, Debug)] -pub enum BootError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Invalid bootloader magic - BadMagic, -} - -#[cfg(feature = "defmt")] -impl defmt::Format for BootError { - fn format(&self, fmt: defmt::Formatter) { - match self { - BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), - BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), - } - } -} - -impl From for BootError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - BootError::Flash(error.kind()) - } -} - -/// Bootloader flash configuration holding the three flashes used by the bootloader -/// -/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. -/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition -/// the provided flash according to symbols defined in the linkerfile. -pub struct BootLoaderConfig { - /// Flash type used for the active partition - the partition which will be booted from. - pub active: ACTIVE, - /// Flash type used for the dfu partition - the partition which will be swapped in when requested. - pub dfu: DFU, - /// Flash type used for the state partition. - pub state: STATE, -} - -impl<'a, FLASH: NorFlash> - BootLoaderConfig< - BlockingPartition<'a, NoopRawMutex, FLASH>, - BlockingPartition<'a, NoopRawMutex, FLASH>, - BlockingPartition<'a, NoopRawMutex, FLASH>, - > -{ - /// Create a bootloader config from the flash and address symbols defined in the linkerfile - // #[cfg(target_os = "none")] - pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - let start = &__bootloader_active_start as *const u32 as u32; - let end = &__bootloader_active_end as *const u32 as u32; - trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - let dfu = unsafe { - let start = &__bootloader_dfu_start as *const u32 as u32; - let end = &__bootloader_dfu_end as *const u32 as u32; - trace!("DFU: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - let state = unsafe { - let start = &__bootloader_state_start as *const u32 as u32; - let end = &__bootloader_state_end as *const u32 as u32; - trace!("STATE: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - - Self { active, dfu, state } - } -} - -/// BootLoader works with any flash implementing embedded_storage. -pub struct BootLoader { - active: ACTIVE, - dfu: DFU, - /// The state partition has the following format: - /// All ranges are in multiples of WRITE_SIZE bytes. - /// | Range | Description | - /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | - /// | 2..2 + N | Progress index used while swapping or reverting - state: STATE, -} - -impl BootLoader { - /// Get the page size which is the "unit of operation" within the bootloader. - const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { - ACTIVE::ERASE_SIZE as u32 - } else { - DFU::ERASE_SIZE as u32 - }; - - /// Create a new instance of a bootloader with the flash partitions. - /// - /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. - /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. - pub fn new(config: BootLoaderConfig) -> Self { - Self { - active: config.active, - dfu: config.dfu, - state: config.state, - } - } - - /// Perform necessary boot preparations like swapping images. - /// - /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap - /// algorithm to work correctly. - /// - /// The provided aligned_buf argument must satisfy any alignment requirements - /// given by the partition flashes. All flash operations will use this buffer. - /// - /// ## SWAPPING - /// - /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. - /// The swap index contains the copy progress, as to allow continuation of the copy process on - /// power failure. The index counter is represented within 1 or more pages (depending on total - /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) - /// contains a zero value. This ensures that index updates can be performed atomically and - /// avoid a situation where the wrong index value is set (page write size is "atomic"). - /// - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 0 | 1 | 2 | 3 | - | - /// | DFU | 0 | 3 | 2 | 1 | X | - /// - /// The algorithm starts by copying 'backwards', and after the first step, the layout is - /// as follows: - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 1 | 1 | 2 | 1 | - | - /// | DFU | 1 | 3 | 2 | 1 | 3 | - /// - /// The next iteration performs the same steps - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 2 | 1 | 2 | 1 | - | - /// | DFU | 2 | 3 | 2 | 2 | 3 | - /// - /// And again until we're done - /// - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|------------|--------|--------|--------|--------| - /// | Active | 3 | 3 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// - /// ## REVERTING - /// - /// The reverting algorithm uses the swap index to discover that images were swapped, but that - /// the application failed to mark the boot successful. In this case, the revert algorithm will - /// run. - /// - /// The revert index is located separately from the swap index, to ensure that revert can continue - /// on power failure. - /// - /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. - /// - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|--------------|--------|--------|--------|--------| - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// - /// - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|--------------|--------|--------|--------|--------| - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 2 | 2 | 3 | - /// - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// |-----------|--------------|--------|--------|--------|--------| - /// | Active | 3 | 1 | 2 | 3 | - | - /// | DFU | 3 | 3 | 2 | 1 | 3 | - /// - pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { - // Ensure we have enough progress pages to store copy progress - assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); - assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); - assert!(aligned_buf.len() >= STATE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); - - // Ensure our partitions are able to handle boot operations - assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); - - // Copy contents from partition N to active - let state = self.read_state(aligned_buf)?; - if state == State::Swap { - // - // Check if we already swapped. If we're in the swap state, this means we should revert - // since the app has failed to mark boot as successful - // - if !self.is_swapped(aligned_buf)? { - trace!("Swapping"); - self.swap(aligned_buf)?; - trace!("Swapping done"); - } else { - trace!("Reverting"); - self.revert(aligned_buf)?; - - let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; - - // Invalidate progress - state_word.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, state_word)?; - - // Clear magic and progress - self.state.erase(0, self.state.capacity() as u32)?; - - // Set magic - state_word.fill(BOOT_MAGIC); - self.state.write(0, state_word)?; - } - } - Ok(state) - } - - fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { - let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; - let progress = self.current_progress(aligned_buf)?; - - Ok(progress >= page_count * 2) - } - - fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { - let write_size = STATE::WRITE_SIZE as u32; - let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; - let state_word = &mut aligned_buf[..write_size as usize]; - - self.state.read(write_size, state_word)?; - if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { - // Progress is invalid - return Ok(max_index); - } - - for index in 0..max_index { - self.state.read((2 + index) as u32 * write_size, state_word)?; - - if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { - return Ok(index); - } - } - Ok(max_index) - } - - fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; - state_word.fill(!STATE_ERASE_VALUE); - self.state - .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; - Ok(()) - } - - fn copy_page_once_to_active( - &mut self, - progress_index: usize, - from_offset: u32, - to_offset: u32, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - if self.current_progress(aligned_buf)? <= progress_index { - let page_size = Self::PAGE_SIZE as u32; - - self.active.erase(to_offset, to_offset + page_size)?; - - for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; - self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; - } - - self.update_progress(progress_index, aligned_buf)?; - } - Ok(()) - } - - fn copy_page_once_to_dfu( - &mut self, - progress_index: usize, - from_offset: u32, - to_offset: u32, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - if self.current_progress(aligned_buf)? <= progress_index { - let page_size = Self::PAGE_SIZE as u32; - - self.dfu.erase(to_offset as u32, to_offset + page_size)?; - - for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; - self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; - } - - self.update_progress(progress_index, aligned_buf)?; - } - Ok(()) - } - - fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; - for page_num in 0..page_count { - let progress_index = (page_num * 2) as usize; - - // Copy active page to the 'next' DFU page. - let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; - let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; - //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; - - // Copy DFU page to the active page - let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; - let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; - //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; - } - - Ok(()) - } - - fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; - for page_num in 0..page_count { - let progress_index = (page_count * 2 + page_num * 2) as usize; - - // Copy the bad active page to the DFU page - let active_from_offset = page_num * Self::PAGE_SIZE; - let dfu_to_offset = page_num * Self::PAGE_SIZE; - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; - - // Copy the DFU page back to the active page - let active_to_offset = page_num * Self::PAGE_SIZE; - let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; - } - - Ok(()) - } - - fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { - let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; - self.state.read(0, state_word)?; - - if !state_word.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } - } -} - -fn assert_partitions( - active: &ACTIVE, - dfu: &DFU, - state: &STATE, - page_size: u32, -) { - assert_eq!(active.capacity() as u32 % page_size, 0); - assert_eq!(dfu.capacity() as u32 % page_size, 0); - // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm - assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); - assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - #[should_panic] - fn test_range_asserts() { - const ACTIVE_SIZE: usize = 4194304 - 4096; - const DFU_SIZE: usize = 4194304; - const STATE_SIZE: usize = 4096; - static ACTIVE: MemFlash = MemFlash::new(0xFF); - static DFU: MemFlash = MemFlash::new(0xFF); - static STATE: MemFlash = MemFlash::new(0xFF); - assert_partitions(&ACTIVE, &DFU, &STATE, 4096); - } -} diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs deleted file mode 100644 index 2e4e03da3..000000000 --- a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs +++ /dev/null @@ -1,30 +0,0 @@ -use digest::typenum::U64; -use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; -use ed25519_dalek::Digest; - -pub struct Sha512(ed25519_dalek::Sha512); - -impl Default for Sha512 { - fn default() -> Self { - Self(ed25519_dalek::Sha512::new()) - } -} - -impl Update for Sha512 { - fn update(&mut self, data: &[u8]) { - Digest::update(&mut self.0, data) - } -} - -impl FixedOutput for Sha512 { - fn finalize_into(self, out: &mut digest::Output) { - let result = self.0.finalize(); - out.as_mut_slice().copy_from_slice(result.as_slice()) - } -} - -impl OutputSizeUser for Sha512 { - type OutputSize = U64; -} - -impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs deleted file mode 100644 index 9b4b4b60c..000000000 --- a/embassy-boot/boot/src/digest_adapters/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(feature = "ed25519-dalek")] -pub(crate) mod ed25519_dalek; - -#[cfg(feature = "ed25519-salty")] -pub(crate) mod salty; diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs deleted file mode 100644 index 2b5dcf3af..000000000 --- a/embassy-boot/boot/src/digest_adapters/salty.rs +++ /dev/null @@ -1,29 +0,0 @@ -use digest::typenum::U64; -use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; - -pub struct Sha512(salty::Sha512); - -impl Default for Sha512 { - fn default() -> Self { - Self(salty::Sha512::new()) - } -} - -impl Update for Sha512 { - fn update(&mut self, data: &[u8]) { - self.0.update(data) - } -} - -impl FixedOutput for Sha512 { - fn finalize_into(self, out: &mut digest::Output) { - let result = self.0.finalize(); - out.as_mut_slice().copy_from_slice(result.as_slice()) - } -} - -impl OutputSizeUser for Sha512 { - type OutputSize = U64; -} - -impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs deleted file mode 100644 index 2e43e1cc1..000000000 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ /dev/null @@ -1,329 +0,0 @@ -use digest::Digest; -#[cfg(target_os = "none")] -use embassy_embedded_hal::flash::partition::Partition; -#[cfg(target_os = "none")] -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embedded_storage_async::nor_flash::NorFlash; - -use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; - -/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { - dfu: DFU, - state: FirmwareState<'d, STATE>, -} - -#[cfg(target_os = "none")] -impl<'a, FLASH: NorFlash> - FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> -{ - /// Create a firmware updater config from the flash and address symbols defined in the linkerfile - pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - let start = &__bootloader_dfu_start as *const u32 as u32; - let end = &__bootloader_dfu_end as *const u32 as u32; - trace!("DFU: 0x{:x} - 0x{:x}", start, end); - - Partition::new(flash, start, end - start) - }; - let state = unsafe { - let start = &__bootloader_state_start as *const u32 as u32; - let end = &__bootloader_state_end as *const u32 as u32; - trace!("STATE: 0x{:x} - 0x{:x}", start, end); - - Partition::new(flash, start, end - start) - }; - - Self { dfu, state } - } -} - -impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self { - dfu: config.dfu, - state: FirmwareState::new(config.state, aligned), - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub async fn get_state(&mut self) -> Result { - self.state.get_state().await - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - #[cfg(feature = "_verify")] - pub async fn verify_and_mark_updated( - &mut self, - _public_key: &[u8; 32], - _signature: &[u8; 64], - _update_len: u32, - ) -> Result<(), FirmwareUpdaterError> { - assert!(_update_len <= self.dfu.capacity() as u32); - - self.state.verify_booted().await?; - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature); - - let mut chunk_buf = [0; 2]; - let mut message = [0; 64]; - self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; - let signature = Signature::try_from(_signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - let mut chunk_buf = [0; 2]; - self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.state.mark_updated().await - } - - /// Verify the update in DFU with any digest. - pub async fn hash( - &mut self, - update_len: u32, - chunk_buf: &mut [u8], - output: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let mut digest = D::new(); - for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read(offset, chunk_buf).await?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - #[cfg(not(feature = "_verify"))] - pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_updated().await - } - - /// Mark to trigger USB DFU on next boot. - pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.verify_booted().await?; - self.state.mark_dfu().await - } - - /// Mark firmware boot successful and stop rollback on reset. - pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_booted().await - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= DFU::ERASE_SIZE); - - self.state.verify_booted().await?; - - self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; - - self.dfu.write(offset as u32, data).await?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { - self.state.verify_booted().await?; - self.dfu.erase(0, self.dfu.capacity() as u32).await?; - - Ok(&mut self.dfu) - } -} - -/// Manages the state partition of the firmware update. -/// -/// Can be used standalone for more fine grained control, or as part of the updater. -pub struct FirmwareState<'d, STATE> { - state: STATE, - aligned: &'d mut [u8], -} - -impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { - /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self::new(config.state, aligned) - } - - /// Create a firmware state instance with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, - /// and follow the alignment rules for the flash being read from and written to. - pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { - assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); - Self { state, aligned } - } - - // Make sure we are running a booted firmware to avoid reverting to a bad state. - async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state().await? == State::Boot { - Ok(()) - } else { - Err(FirmwareUpdaterError::BadState) - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub async fn get_state(&mut self) -> Result { - self.state.read(0, &mut self.aligned).await?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Mark to trigger firmware swap on next boot. - pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(SWAP_MAGIC).await - } - - /// Mark to trigger USB DFU on next boot. - pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(DFU_DETACH_MAGIC).await - } - - /// Mark firmware boot successful and stop rollback on reset. - pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(BOOT_MAGIC).await - } - - async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { - self.state.read(0, &mut self.aligned).await?; - - if self.aligned.iter().any(|&b| b != magic) { - // Read progress validity - self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; - - if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { - // The current progress validity marker is invalid - } else { - // Invalidate progress - self.aligned.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; - } - - // Clear magic and progress - self.state.erase(0, self.state.capacity() as u32).await?; - - // Set magic - self.aligned.fill(magic); - self.state.write(0, &self.aligned).await?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use embassy_embedded_hal::flash::partition::Partition; - use embassy_sync::blocking_mutex::raw::NoopRawMutex; - use embassy_sync::mutex::Mutex; - use futures::executor::block_on; - use sha1::{Digest, Sha1}; - - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - fn can_verify_sha1() { - let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); - let state = Partition::new(&flash, 0, 4096); - let dfu = Partition::new(&flash, 65536, 65536); - let mut aligned = [0; 8]; - - let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; - let mut to_write = [0; 4096]; - to_write[..7].copy_from_slice(update.as_slice()); - - let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); - block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); - let mut chunk_buf = [0; 2]; - let mut hash = [0; 20]; - block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); - - assert_eq!(Sha1::digest(update).as_slice(), hash); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs deleted file mode 100644 index f1368540d..000000000 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ /dev/null @@ -1,340 +0,0 @@ -use digest::Digest; -#[cfg(target_os = "none")] -use embassy_embedded_hal::flash::partition::BlockingPartition; -#[cfg(target_os = "none")] -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embedded_storage::nor_flash::NorFlash; - -use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; - -/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { - dfu: DFU, - state: BlockingFirmwareState<'d, STATE>, -} - -#[cfg(target_os = "none")] -impl<'a, FLASH: NorFlash> - FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> -{ - /// Create a firmware updater config from the flash and address symbols defined in the linkerfile - pub fn from_linkerfile_blocking( - flash: &'a embassy_sync::blocking_mutex::Mutex>, - ) -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - let start = &__bootloader_dfu_start as *const u32 as u32; - let end = &__bootloader_dfu_end as *const u32 as u32; - trace!("DFU: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - let state = unsafe { - let start = &__bootloader_state_start as *const u32 as u32; - let end = &__bootloader_state_end as *const u32 as u32; - trace!("STATE: 0x{:x} - 0x{:x}", start, end); - - BlockingPartition::new(flash, start, end - start) - }; - - Self { dfu, state } - } -} - -impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self { - dfu: config.dfu, - state: BlockingFirmwareState::new(config.state, aligned), - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub fn get_state(&mut self) -> Result { - self.state.get_state() - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - #[cfg(feature = "_verify")] - pub fn verify_and_mark_updated( - &mut self, - _public_key: &[u8; 32], - _signature: &[u8; 64], - _update_len: u32, - ) -> Result<(), FirmwareUpdaterError> { - assert!(_update_len <= self.dfu.capacity() as u32); - - self.state.verify_booted()?; - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature); - - let mut message = [0; 64]; - let mut chunk_buf = [0; 2]; - self.hash::(_update_len, &mut chunk_buf, &mut message)?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; - let signature = Signature::try_from(_signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - let mut chunk_buf = [0; 2]; - self.hash::(_update_len, &mut chunk_buf, &mut message)?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.state.mark_updated() - } - - /// Verify the update in DFU with any digest. - pub fn hash( - &mut self, - update_len: u32, - chunk_buf: &mut [u8], - output: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let mut digest = D::new(); - for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read(offset, chunk_buf)?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - #[cfg(not(feature = "_verify"))] - pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_updated() - } - - /// Mark to trigger USB DFU device on next boot. - pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.verify_booted()?; - self.state.mark_dfu() - } - - /// Mark firmware boot successful and stop rollback on reset. - pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.state.mark_booted() - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= DFU::ERASE_SIZE); - self.state.verify_booted()?; - - self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; - - self.dfu.write(offset as u32, data)?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { - self.state.verify_booted()?; - self.dfu.erase(0, self.dfu.capacity() as u32)?; - - Ok(&mut self.dfu) - } -} - -/// Manages the state partition of the firmware update. -/// -/// Can be used standalone for more fine grained control, or as part of the updater. -pub struct BlockingFirmwareState<'d, STATE> { - state: STATE, - aligned: &'d mut [u8], -} - -impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { - /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { - Self::new(config.state, aligned) - } - - /// Create a firmware state instance with a buffer for magic content and state partition. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { - assert_eq!(aligned.len(), STATE::WRITE_SIZE); - Self { state, aligned } - } - - // Make sure we are running a booted firmware to avoid reverting to a bad state. - fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { - Ok(()) - } else { - Err(FirmwareUpdaterError::BadState) - } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub fn get_state(&mut self) -> Result { - self.state.read(0, &mut self.aligned)?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } - } - - /// Mark to trigger firmware swap on next boot. - pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(SWAP_MAGIC) - } - - /// Mark to trigger USB DFU on next boot. - pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(DFU_DETACH_MAGIC) - } - - /// Mark firmware boot successful and stop rollback on reset. - pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - self.set_magic(BOOT_MAGIC) - } - - fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { - self.state.read(0, &mut self.aligned)?; - - if self.aligned.iter().any(|&b| b != magic) { - // Read progress validity - self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; - - if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { - // The current progress validity marker is invalid - } else { - // Invalidate progress - self.aligned.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; - } - - // Clear magic and progress - self.state.erase(0, self.state.capacity() as u32)?; - - // Set magic - self.aligned.fill(magic); - self.state.write(0, &self.aligned)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use core::cell::RefCell; - - use embassy_embedded_hal::flash::partition::BlockingPartition; - use embassy_sync::blocking_mutex::raw::NoopRawMutex; - use embassy_sync::blocking_mutex::Mutex; - use sha1::{Digest, Sha1}; - - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - fn can_verify_sha1() { - let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); - let state = BlockingPartition::new(&flash, 0, 4096); - let dfu = BlockingPartition::new(&flash, 65536, 65536); - let mut aligned = [0; 8]; - - let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; - let mut to_write = [0; 4096]; - to_write[..7].copy_from_slice(update.as_slice()); - - let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); - updater.write_firmware(0, to_write.as_slice()).unwrap(); - let mut chunk_buf = [0; 2]; - let mut hash = [0; 20]; - updater - .hash::(update.len() as u32, &mut chunk_buf, &mut hash) - .unwrap(); - - assert_eq!(Sha1::digest(update).as_slice(), hash); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs deleted file mode 100644 index 4814786bf..000000000 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -mod asynch; -mod blocking; - -pub use asynch::{FirmwareState, FirmwareUpdater}; -pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; -use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; - -/// Firmware updater flash configuration holding the two flashes used by the updater -/// -/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. -/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition -/// the provided flash according to symbols defined in the linkerfile. -pub struct FirmwareUpdaterConfig { - /// The dfu flash partition - pub dfu: DFU, - /// The state flash partition - pub state: STATE, -} - -/// Errors returned by FirmwareUpdater -#[derive(Debug)] -pub enum FirmwareUpdaterError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Signature errors. - Signature(signature::Error), - /// Bad state. - BadState, -} - -#[cfg(feature = "defmt")] -impl defmt::Format for FirmwareUpdaterError { - fn format(&self, fmt: defmt::Formatter) { - match self { - FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), - FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), - FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), - } - } -} - -impl From for FirmwareUpdaterError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - FirmwareUpdaterError::Flash(error.kind()) - } -} diff --git a/embassy-boot/boot/src/fmt.rs b/embassy-boot/boot/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/boot/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs deleted file mode 100644 index b4f03e01e..000000000 --- a/embassy-boot/boot/src/lib.rs +++ /dev/null @@ -1,323 +0,0 @@ -#![no_std] -#![allow(async_fn_in_trait)] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -mod boot_loader; -mod digest_adapters; -mod firmware_updater; -#[cfg(test)] -mod mem_flash; -#[cfg(test)] -mod test_flash; - -// The expected value of the flash after an erase -// TODO: Use the value provided by NorFlash when available -pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; -pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; -pub use firmware_updater::{ - BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, - FirmwareUpdaterError, -}; - -pub(crate) const BOOT_MAGIC: u8 = 0xD0; -pub(crate) const SWAP_MAGIC: u8 = 0xF0; -pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; - -/// The state of the bootloader after running prepare. -#[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { - /// Bootloader is ready to boot the active partition. - Boot, - /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. - Swap, - /// Application has received a request to reboot into DFU mode to apply an update. - DfuDetach, -} - -/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. -#[repr(align(32))] -pub struct AlignedBuffer(pub [u8; N]); - -impl AsRef<[u8]> for AlignedBuffer { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl AsMut<[u8]> for AlignedBuffer { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } -} - -#[cfg(test)] -mod tests { - #![allow(unused_imports)] - - use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; - use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; - use futures::executor::block_on; - - use super::*; - use crate::boot_loader::BootLoaderConfig; - use crate::firmware_updater::FirmwareUpdaterConfig; - use crate::mem_flash::MemFlash; - use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; - - /* - #[test] - fn test_bad_magic() { - let mut flash = MemFlash([0xff; 131072]); - let mut flash = SingleFlashConfig::new(&mut flash); - - let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); - - assert_eq!( - bootloader.prepare_boot(&mut flash), - Err(BootError::BadMagic) - ); - } - */ - - #[test] - fn test_boot_state() { - let flash = BlockingTestFlash::new(BootLoaderConfig { - active: MemFlash::<57344, 4096, 4>::default(), - dfu: MemFlash::<61440, 4096, 4>::default(), - state: MemFlash::<4096, 4096, 4>::default(), - }); - - flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); - - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - - let mut page = [0; 4096]; - assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); - } - - #[test] - #[cfg(not(feature = "_verify"))] - fn test_swap_state() { - const FIRMWARE_SIZE: usize = 57344; - let flash = AsyncTestFlash::new(BootLoaderConfig { - active: MemFlash::::default(), - dfu: MemFlash::<61440, 4096, 4>::default(), - state: MemFlash::<4096, 4096, 4>::default(), - }); - - const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; - const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; - let mut aligned = [0; 4]; - - block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); - block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); - block_on(updater.mark_updated()).unwrap(); - - // Writing after marking updated is not allowed until marked as booted. - let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); - assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - - let mut page = [0; 1024]; - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - // First DFU page is untouched - flash.dfu().read(4096, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - - // Running again should cause a revert - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - // Last DFU page is untouched - flash.dfu().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - - // Mark as booted - let flash = flash.into_async(); - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.mark_booted()).unwrap(); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); - } - - #[test] - #[cfg(not(feature = "_verify"))] - fn test_swap_state_active_page_biggest() { - const FIRMWARE_SIZE: usize = 12288; - let flash = AsyncTestFlash::new(BootLoaderConfig { - active: MemFlash::<12288, 4096, 8>::random(), - dfu: MemFlash::<16384, 2048, 8>::random(), - state: MemFlash::<2048, 128, 4>::random(), - }); - - const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; - const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; - let mut aligned = [0; 4]; - - block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); - block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); - block_on(updater.mark_updated()).unwrap(); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - - let mut page = [0; 4096]; - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - // First DFU page is untouched - flash.dfu().read(4096, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - } - - #[test] - #[cfg(not(feature = "_verify"))] - fn test_swap_state_dfu_page_biggest() { - const FIRMWARE_SIZE: usize = 12288; - let flash = AsyncTestFlash::new(BootLoaderConfig { - active: MemFlash::::random(), - dfu: MemFlash::<16384, 4096, 8>::random(), - state: MemFlash::<2048, 128, 4>::random(), - }); - - const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; - const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; - let mut aligned = [0; 4]; - - block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); - block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); - block_on(updater.mark_updated()).unwrap(); - - let flash = flash.into_blocking(); - let mut bootloader = BootLoader::new(BootLoaderConfig { - active: flash.active(), - dfu: flash.dfu(), - state: flash.state(), - }); - let mut page = [0; 4096]; - assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - - let mut read_buf = [0; FIRMWARE_SIZE]; - flash.active().read(0, &mut read_buf).unwrap(); - assert_eq!(UPDATE, read_buf); - // First DFU page is untouched - flash.dfu().read(4096, &mut read_buf).unwrap(); - assert_eq!(ORIGINAL, read_buf); - } - - #[test] - #[cfg(feature = "_verify")] - fn test_verify() { - // The following key setup is based on: - // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example - - use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; - use rand::rngs::OsRng; - - let mut csprng = OsRng {}; - let keypair = SigningKey::generate(&mut csprng); - - let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; - let mut digest = Sha512::new(); - digest.update(&firmware); - let message = digest.finalize(); - let signature: Signature = keypair.sign(&message); - - let public_key = keypair.verifying_key(); - - // Setup flash - let flash = BlockingTestFlash::new(BootLoaderConfig { - active: MemFlash::<0, 0, 0>::default(), - dfu: MemFlash::<4096, 4096, 4>::default(), - state: MemFlash::<4096, 4096, 4>::default(), - }); - - let firmware_len = firmware.len(); - - let mut write_buf = [0; 4096]; - write_buf[0..firmware_len].copy_from_slice(firmware); - flash.dfu().write(0, &write_buf).unwrap(); - - // On with the test - let flash = flash.into_async(); - let mut aligned = [0; 4]; - let mut updater = FirmwareUpdater::new( - FirmwareUpdaterConfig { - dfu: flash.dfu(), - state: flash.state(), - }, - &mut aligned, - ); - - assert!(block_on(updater.verify_and_mark_updated( - &public_key.to_bytes(), - &signature.to_bytes(), - firmware_len as u32, - )) - .is_ok()); - } -} diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs deleted file mode 100644 index 40f352c8d..000000000 --- a/embassy-boot/boot/src/mem_flash.rs +++ /dev/null @@ -1,170 +0,0 @@ -#![allow(unused)] - -use core::ops::{Bound, Range, RangeBounds}; - -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; - -pub struct MemFlash { - pub mem: [u8; SIZE], - pub pending_write_successes: Option, -} - -#[derive(Debug)] -pub struct MemFlashError; - -impl MemFlash { - pub const fn new(fill: u8) -> Self { - Self { - mem: [fill; SIZE], - pending_write_successes: None, - } - } - - #[cfg(test)] - pub fn random() -> Self { - let mut mem = [0; SIZE]; - for byte in mem.iter_mut() { - *byte = rand::random::(); - } - Self { - mem, - pending_write_successes: None, - } - } - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { - let len = bytes.len(); - bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); - Ok(()) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { - let offset = offset as usize; - assert!(bytes.len() % WRITE_SIZE == 0); - assert!(offset % WRITE_SIZE == 0); - assert!(offset + bytes.len() <= SIZE); - - if let Some(pending_successes) = self.pending_write_successes { - if pending_successes > 0 { - self.pending_write_successes = Some(pending_successes - 1); - } else { - return Err(MemFlashError); - } - } - - for ((offset, mem_byte), new_byte) in self - .mem - .iter_mut() - .enumerate() - .skip(offset) - .take(bytes.len()) - .zip(bytes) - { - assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); - *mem_byte = *new_byte; - } - - Ok(()) - } - - fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { - let from = from as usize; - let to = to as usize; - assert!(from % ERASE_SIZE == 0); - assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); - for i in from..to { - self.mem[i] = 0xFF; - } - Ok(()) - } - - pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { - let offset = offset as usize; - assert!(bytes.len() % WRITE_SIZE == 0); - assert!(offset % WRITE_SIZE == 0); - assert!(offset + bytes.len() <= SIZE); - - self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); - - Ok(()) - } -} - -impl Default - for MemFlash -{ - fn default() -> Self { - Self::new(0xFF) - } -} - -impl ErrorType - for MemFlash -{ - type Error = MemFlashError; -} - -impl NorFlashError for MemFlashError { - fn kind(&self) -> NorFlashErrorKind { - NorFlashErrorKind::Other - } -} - -impl ReadNorFlash - for MemFlash -{ - const READ_SIZE: usize = 1; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes) - } - - fn capacity(&self) -> usize { - SIZE - } -} - -impl NorFlash - for MemFlash -{ - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes) - } - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to) - } -} - -impl AsyncReadNorFlash - for MemFlash -{ - const READ_SIZE: usize = 1; - - async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes) - } - - fn capacity(&self) -> usize { - SIZE - } -} - -impl AsyncNorFlash - for MemFlash -{ - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes) - } - - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to) - } -} diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs deleted file mode 100644 index 3ac9e71ab..000000000 --- a/embassy-boot/boot/src/test_flash/asynch.rs +++ /dev/null @@ -1,64 +0,0 @@ -use embassy_embedded_hal::flash::partition::Partition; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::mutex::Mutex; -use embedded_storage_async::nor_flash::NorFlash; - -use crate::BootLoaderConfig; - -pub struct AsyncTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - active: Mutex, - dfu: Mutex, - state: Mutex, -} - -impl AsyncTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - pub fn new(config: BootLoaderConfig) -> Self { - Self { - active: Mutex::new(config.active), - dfu: Mutex::new(config.dfu), - state: Mutex::new(config.state), - } - } - - pub fn active(&self) -> Partition { - Self::create_partition(&self.active) - } - - pub fn dfu(&self) -> Partition { - Self::create_partition(&self.dfu) - } - - pub fn state(&self) -> Partition { - Self::create_partition(&self.state) - } - - fn create_partition(mutex: &Mutex) -> Partition { - Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) - } -} - -impl AsyncTestFlash -where - ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, - DFU: NorFlash + embedded_storage::nor_flash::NorFlash, - STATE: NorFlash + embedded_storage::nor_flash::NorFlash, -{ - pub fn into_blocking(self) -> super::BlockingTestFlash { - let config = BootLoaderConfig { - active: self.active.into_inner(), - dfu: self.dfu.into_inner(), - state: self.state.into_inner(), - }; - super::BlockingTestFlash::new(config) - } -} diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs deleted file mode 100644 index 5ec476c65..000000000 --- a/embassy-boot/boot/src/test_flash/blocking.rs +++ /dev/null @@ -1,68 +0,0 @@ -use core::cell::RefCell; - -use embassy_embedded_hal::flash::partition::BlockingPartition; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use embedded_storage::nor_flash::NorFlash; - -use crate::BootLoaderConfig; - -pub struct BlockingTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - active: Mutex>, - dfu: Mutex>, - state: Mutex>, -} - -impl BlockingTestFlash -where - ACTIVE: NorFlash, - DFU: NorFlash, - STATE: NorFlash, -{ - pub fn new(config: BootLoaderConfig) -> Self { - Self { - active: Mutex::new(RefCell::new(config.active)), - dfu: Mutex::new(RefCell::new(config.dfu)), - state: Mutex::new(RefCell::new(config.state)), - } - } - - pub fn active(&self) -> BlockingPartition { - Self::create_partition(&self.active) - } - - pub fn dfu(&self) -> BlockingPartition { - Self::create_partition(&self.dfu) - } - - pub fn state(&self) -> BlockingPartition { - Self::create_partition(&self.state) - } - - pub fn create_partition( - mutex: &Mutex>, - ) -> BlockingPartition { - BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) - } -} - -impl BlockingTestFlash -where - ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, - DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, - STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, -{ - pub fn into_async(self) -> super::AsyncTestFlash { - let config = BootLoaderConfig { - active: self.active.into_inner().into_inner(), - dfu: self.dfu.into_inner().into_inner(), - state: self.state.into_inner().into_inner(), - }; - super::AsyncTestFlash::new(config) - } -} diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/boot/src/test_flash/mod.rs deleted file mode 100644 index 79b15a081..000000000 --- a/embassy-boot/boot/src/test_flash/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod asynch; -mod blocking; - -pub(crate) use asynch::AsyncTestFlash; -pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml deleted file mode 100644 index 9f74fb126..000000000 --- a/embassy-boot/nrf/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot-nrf" -version = "0.1.0" -description = "Bootloader lib for nRF chips" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/nrf/src/" -features = ["embassy-nrf/nrf52840"] -target = "thumbv7em-none-eabi" - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } - -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embassy-nrf = { path = "../../embassy-nrf" } -embassy-boot = { path = "../boot", default-features = false } -cortex-m = { version = "0.7.6" } -cortex-m-rt = { version = "0.7" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -cfg-if = "1.0.0" - -nrf-softdevice-mbr = { version = "0.2.0", optional = true } - -[features] -defmt = [ - "dep:defmt", - "embassy-boot/defmt", - "embassy-nrf/defmt", -] -softdevice = [ - "nrf-softdevice-mbr", -] diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md deleted file mode 100644 index 9dc5b0eb9..000000000 --- a/embassy-boot/nrf/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# embassy-boot-nrf - -An [Embassy](https://embassy.dev) project. - -An adaptation of `embassy-boot` for nRF. - -## Features - -* Load applications with or without the softdevice. -* Configure bootloader partitions based on linker script. -* Using watchdog timer to detect application failure. diff --git a/embassy-boot/nrf/src/fmt.rs b/embassy-boot/nrf/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/nrf/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs deleted file mode 100644 index 5b20a93c6..000000000 --- a/embassy-boot/nrf/src/lib.rs +++ /dev/null @@ -1,145 +0,0 @@ -#![no_std] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, -}; -use embassy_nrf::nvmc::PAGE_SIZE; -use embassy_nrf::peripherals::WDT; -use embassy_nrf::wdt; -use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; - -/// A bootloader for nRF devices. -pub struct BootLoader; - -impl BootLoader { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. - pub fn prepare( - config: BootLoaderConfig, - ) -> Self { - let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); - let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); - Self - } - - /// Boots the application without softdevice mechanisms. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - #[cfg(not(feature = "softdevice"))] - pub unsafe fn load(self, start: u32) -> ! { - let mut p = cortex_m::Peripherals::steal(); - p.SCB.invalidate_icache(); - p.SCB.vtor.write(start); - cortex_m::asm::bootload(start as *const u32) - } - - /// Boots the application assuming softdevice is present. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - #[cfg(feature = "softdevice")] - pub unsafe fn load(self, _app: u32) -> ! { - use nrf_softdevice_mbr as mbr; - const NRF_SUCCESS: u32 = 0; - - // Address of softdevice which we'll forward interrupts to - let addr = 0x1000; - let mut cmd = mbr::sd_mbr_command_t { - command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, - params: mbr::sd_mbr_command_t__bindgen_ty_1 { - irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr }, - }, - }; - let ret = mbr::sd_mbr_command(&mut cmd); - assert_eq!(ret, NRF_SUCCESS); - - let msp = *(addr as *const u32); - let rv = *((addr + 4) as *const u32); - - trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv); - - // These instructions perform the following operations: - // - // * Modify control register to use MSP as stack pointer (clear spsel bit) - // * Synchronize instruction barrier - // * Initialize stack pointer (0x1000) - // * Set link register to not return (0xFF) - // * Jump to softdevice reset vector - core::arch::asm!( - "mrs {tmp}, CONTROL", - "bics {tmp}, {spsel}", - "msr CONTROL, {tmp}", - "isb", - "msr MSP, {msp}", - "mov lr, {new_lr}", - "bx {rv}", - // `out(reg) _` is not permitted in a `noreturn` asm! call, - // so instead use `in(reg) 0` and don't restore it afterwards. - tmp = in(reg) 0, - spsel = in(reg) 2, - new_lr = in(reg) 0xFFFFFFFFu32, - msp = in(reg) msp, - rv = in(reg) rv, - options(noreturn), - ); - } -} - -/// A flash implementation that wraps any flash and will pet a watchdog when touching flash. -pub struct WatchdogFlash { - flash: FLASH, - wdt: wdt::WatchdogHandle, -} - -impl WatchdogFlash { - /// Start a new watchdog with a given flash and WDT peripheral and a timeout - pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { - let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { - Ok(x) => x, - Err(_) => { - // In case the watchdog is already running, just spin and let it expire, since - // we can't configure it anyway. This usually happens when we first program - // the device and the watchdog was previously active - info!("Watchdog already active with wrong config, waiting for it to timeout..."); - loop {} - } - }; - Self { flash, wdt } - } -} - -impl ErrorType for WatchdogFlash { - type Error = FLASH::Error; -} - -impl NorFlash for WatchdogFlash { - const WRITE_SIZE: usize = FLASH::WRITE_SIZE; - const ERASE_SIZE: usize = FLASH::ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.wdt.pet(); - self.flash.erase(from, to) - } - fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - self.wdt.pet(); - self.flash.write(offset, data) - } -} - -impl ReadNorFlash for WatchdogFlash { - const READ_SIZE: usize = FLASH::READ_SIZE; - fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { - self.wdt.pet(); - self.flash.read(offset, data) - } - fn capacity(&self) -> usize { - self.flash.capacity() - } -} diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml deleted file mode 100644 index 90bab0996..000000000 --- a/embassy-boot/rp/Cargo.toml +++ /dev/null @@ -1,79 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot-rp" -version = "0.1.0" -description = "Bootloader lib for RP2040 chips" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/" -target = "thumbv6m-none-eabi" - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } -log = { version = "0.4", optional = true } - -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embassy-rp = { path = "../../embassy-rp", default-features = false } -embassy-boot = { path = "../boot", default-features = false } -embassy-time = { path = "../../embassy-time" } - -cortex-m = { version = "0.7.6" } -cortex-m-rt = { version = "0.7" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -cfg-if = "1.0.0" - -[features] -defmt = [ - "dep:defmt", - "embassy-boot/defmt", - "embassy-rp/defmt", -] -log = [ - "dep:log", - "embassy-boot/log", - "embassy-rp/log", -] -debug = ["defmt-rtt"] - -[profile.dev] -debug = 2 -debug-assertions = true -incremental = false -opt-level = 'z' -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false -incremental = false -lto = 'fat' -opt-level = 'z' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md deleted file mode 100644 index b664145a9..000000000 --- a/embassy-boot/rp/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# embassy-boot-rp - -An [Embassy](https://embassy.dev) project. - -An adaptation of `embassy-boot` for RP2040. - -NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. - -## Features - -* Configure bootloader partitions based on linker script. -* Load applications from active partition. diff --git a/embassy-boot/rp/build.rs b/embassy-boot/rp/build.rs deleted file mode 100644 index 2cbc7ef5e..000000000 --- a/embassy-boot/rp/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - if target.starts_with("thumbv6m-") { - println!("cargo:rustc-cfg=armv6m"); - } -} diff --git a/embassy-boot/rp/src/fmt.rs b/embassy-boot/rp/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/rp/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs deleted file mode 100644 index 07a5b3f4d..000000000 --- a/embassy-boot/rp/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![no_std] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, State, -}; -use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; -use embassy_rp::peripherals::{FLASH, WATCHDOG}; -use embassy_rp::watchdog::Watchdog; -use embassy_time::Duration; -use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; - -/// A bootloader for RP2040 devices. -pub struct BootLoader; - -impl BootLoader { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware - pub fn prepare( - config: BootLoaderConfig, - ) -> Self { - let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); - let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self - } - - /// Boots the application. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(self, start: u32) -> ! { - trace!("Loading app at 0x{:x}", start); - #[allow(unused_mut)] - let mut p = cortex_m::Peripherals::steal(); - #[cfg(not(armv6m))] - p.SCB.invalidate_icache(); - p.SCB.vtor.write(start); - - cortex_m::asm::bootload(start as *const u32) - } -} - -/// A flash implementation that will feed a watchdog when touching flash. -pub struct WatchdogFlash<'d, const SIZE: usize> { - flash: Flash<'d, FLASH, Blocking, SIZE>, - watchdog: Watchdog, -} - -impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { - /// Start a new watchdog with a given flash and watchdog peripheral and a timeout - pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { - let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); - let mut watchdog = Watchdog::new(watchdog); - watchdog.start(timeout); - Self { flash, watchdog } - } -} - -impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { - type Error = as ErrorType>::Error; -} - -impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { - const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE; - const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.watchdog.feed(); - self.flash.blocking_erase(from, to) - } - fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - self.watchdog.feed(); - self.flash.blocking_write(offset, data) - } -} - -impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { - const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; - fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { - self.watchdog.feed(); - self.flash.blocking_read(offset, data) - } - fn capacity(&self) -> usize { - self.flash.capacity() - } -} diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs new file mode 100644 index 000000000..e568001bc --- /dev/null +++ b/embassy-boot/src/boot_loader.rs @@ -0,0 +1,411 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; + +use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Errors returned by bootloader +#[derive(PartialEq, Eq, Debug)] +pub enum BootError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Invalid bootloader magic + BadMagic, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for BootError { + fn format(&self, fmt: defmt::Formatter) { + match self { + BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), + BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), + } + } +} + +impl From for BootError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + BootError::Flash(error.kind()) + } +} + +/// Bootloader flash configuration holding the three flashes used by the bootloader +/// +/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. +/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct BootLoaderConfig { + /// Flash type used for the active partition - the partition which will be booted from. + pub active: ACTIVE, + /// Flash type used for the dfu partition - the partition which will be swapped in when requested. + pub dfu: DFU, + /// Flash type used for the state partition. + pub state: STATE, +} + +impl<'a, FLASH: NorFlash> + BootLoaderConfig< + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + > +{ + /// Create a bootloader config from the flash and address symbols defined in the linkerfile + // #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + let start = &__bootloader_active_start as *const u32 as u32; + let end = &__bootloader_active_end as *const u32 as u32; + trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { active, dfu, state } + } +} + +/// BootLoader works with any flash implementing embedded_storage. +pub struct BootLoader { + active: ACTIVE, + dfu: DFU, + /// The state partition has the following format: + /// All ranges are in multiples of WRITE_SIZE bytes. + /// | Range | Description | + /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | + /// | 2..2 + N | Progress index used while swapping or reverting + state: STATE, +} + +impl BootLoader { + /// Get the page size which is the "unit of operation" within the bootloader. + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; + + /// Create a new instance of a bootloader with the flash partitions. + /// + /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. + /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } + } + + /// Perform necessary boot preparations like swapping images. + /// + /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap + /// algorithm to work correctly. + /// + /// The provided aligned_buf argument must satisfy any alignment requirements + /// given by the partition flashes. All flash operations will use this buffer. + /// + /// ## SWAPPING + /// + /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. + /// The swap index contains the copy progress, as to allow continuation of the copy process on + /// power failure. The index counter is represented within 1 or more pages (depending on total + /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) + /// contains a zero value. This ensures that index updates can be performed atomically and + /// avoid a situation where the wrong index value is set (page write size is "atomic"). + /// + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 0 | 1 | 2 | 3 | - | + /// | DFU | 0 | 3 | 2 | 1 | X | + /// + /// The algorithm starts by copying 'backwards', and after the first step, the layout is + /// as follows: + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 1 | 1 | 2 | 1 | - | + /// | DFU | 1 | 3 | 2 | 1 | 3 | + /// + /// The next iteration performs the same steps + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 2 | 1 | 2 | 1 | - | + /// | DFU | 2 | 3 | 2 | 2 | 3 | + /// + /// And again until we're done + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 3 | 3 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// + /// ## REVERTING + /// + /// The reverting algorithm uses the swap index to discover that images were swapped, but that + /// the application failed to mark the boot successful. In this case, the revert algorithm will + /// run. + /// + /// The revert index is located separately from the swap index, to ensure that revert can continue + /// on power failure. + /// + /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 2 | 2 | 3 | + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 3 | - | + /// | DFU | 3 | 3 | 2 | 1 | 3 | + /// + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { + // Ensure we have enough progress pages to store copy progress + assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); + assert!(aligned_buf.len() >= STATE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); + + // Ensure our partitions are able to handle boot operations + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); + + // Copy contents from partition N to active + let state = self.read_state(aligned_buf)?; + if state == State::Swap { + // + // Check if we already swapped. If we're in the swap state, this means we should revert + // since the app has failed to mark boot as successful + // + if !self.is_swapped(aligned_buf)? { + trace!("Swapping"); + self.swap(aligned_buf)?; + trace!("Swapping done"); + } else { + trace!("Reverting"); + self.revert(aligned_buf)?; + + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + + // Invalidate progress + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + state_word.fill(BOOT_MAGIC); + self.state.write(0, state_word)?; + } + } + Ok(state) + } + + fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; + let progress = self.current_progress(aligned_buf)?; + + Ok(progress >= page_count * 2) + } + + fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { + let write_size = STATE::WRITE_SIZE as u32; + let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; + let state_word = &mut aligned_buf[..write_size as usize]; + + self.state.read(write_size, state_word)?; + if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { + // Progress is invalid + return Ok(max_index); + } + + for index in 0..max_index { + self.state.read((2 + index) as u32 * write_size, state_word)?; + + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { + return Ok(index); + } + } + Ok(max_index) + } + + fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + state_word.fill(!STATE_ERASE_VALUE); + self.state + .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; + Ok(()) + } + + fn copy_page_once_to_active( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.active.erase(to_offset, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn copy_page_once_to_dfu( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.dfu.erase(to_offset as u32, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_num * 2) as usize; + + // Copy active page to the 'next' DFU page. + let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; + //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy DFU page to the active page + let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_count * 2 + page_num * 2) as usize; + + // Copy the bad active page to the DFU page + let active_from_offset = page_num * Self::PAGE_SIZE; + let dfu_to_offset = page_num * Self::PAGE_SIZE; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy the DFU page back to the active page + let active_to_offset = page_num * Self::PAGE_SIZE; + let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + self.state.read(0, state_word)?; + + if !state_word.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { + Ok(State::DfuDetach) + } else { + Ok(State::Boot) + } + } +} + +fn assert_partitions( + active: &ACTIVE, + dfu: &DFU, + state: &STATE, + page_size: u32, +) { + assert_eq!(active.capacity() as u32 % page_size, 0); + assert_eq!(dfu.capacity() as u32 % page_size, 0); + // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm + assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); + assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + #[should_panic] + fn test_range_asserts() { + const ACTIVE_SIZE: usize = 4194304 - 4096; + const DFU_SIZE: usize = 4194304; + const STATE_SIZE: usize = 4096; + static ACTIVE: MemFlash = MemFlash::new(0xFF); + static DFU: MemFlash = MemFlash::new(0xFF); + static STATE: MemFlash = MemFlash::new(0xFF); + assert_partitions(&ACTIVE, &DFU, &STATE, 4096); + } +} diff --git a/embassy-boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..2e4e03da3 --- /dev/null +++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs @@ -0,0 +1,30 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; +use ed25519_dalek::Digest; + +pub struct Sha512(ed25519_dalek::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(ed25519_dalek::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + Digest::update(&mut self.0, data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/src/digest_adapters/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "ed25519-dalek")] +pub(crate) mod ed25519_dalek; + +#[cfg(feature = "ed25519-salty")] +pub(crate) mod salty; diff --git a/embassy-boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/src/digest_adapters/salty.rs @@ -0,0 +1,29 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; + +pub struct Sha512(salty::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(salty::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..2e43e1cc1 --- /dev/null +++ b/embassy-boot/src/firmware_updater/asynch.rs @@ -0,0 +1,329 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::Partition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage_async::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { + dfu: DFU, + state: FirmwareState<'d, STATE>, +} + +#[cfg(target_os = "none")] +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self { + dfu: config.dfu, + state: FirmwareState::new(config.state, aligned), + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub async fn get_state(&mut self) -> Result { + self.state.get_state().await + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _public_key: &[u8; 32], + _signature: &[u8; 64], + _update_len: u32, + ) -> Result<(), FirmwareUpdaterError> { + assert!(_update_len <= self.dfu.capacity() as u32); + + self.state.verify_booted().await?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature); + + let mut chunk_buf = [0; 2]; + let mut message = [0; 64]; + self.hash::(_update_len, &mut chunk_buf, &mut message).await?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; + let signature = Signature::try_from(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message).await?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.state.mark_updated().await + } + + /// Verify the update in DFU with any digest. + pub async fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf).await?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_updated().await + } + + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.state.mark_dfu().await + } + + /// Mark firmware boot successful and stop rollback on reset. + pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_booted().await + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); + + self.state.verify_booted().await?; + + self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; + + self.dfu.write(offset as u32, data).await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.dfu.erase(0, self.dfu.capacity() as u32).await?; + + Ok(&mut self.dfu) + } +} + +/// Manages the state partition of the firmware update. +/// +/// Can be used standalone for more fine grained control, or as part of the updater. +pub struct FirmwareState<'d, STATE> { + state: STATE, + aligned: &'d mut [u8], +} + +impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { + /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + + /// Create a firmware state instance with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, + /// and follow the alignment rules for the flash being read from and written to. + pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { + assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); + Self { state, aligned } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + if self.get_state().await? == State::Boot { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub async fn get_state(&mut self) -> Result { + self.state.read(0, &mut self.aligned).await?; + + if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Mark to trigger firmware swap on next boot. + pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(SWAP_MAGIC).await + } + + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC).await + } + + /// Mark firmware boot successful and stop rollback on reset. + pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(BOOT_MAGIC).await + } + + async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, &mut self.aligned).await?; + + if self.aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; + + if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + self.aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32).await?; + + // Set magic + self.aligned.fill(magic); + self.state.write(0, &self.aligned).await?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embassy_embedded_hal::flash::partition::Partition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::mutex::Mutex; + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs new file mode 100644 index 000000000..f1368540d --- /dev/null +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -0,0 +1,340 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::BlockingPartition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { + dfu: DFU, + state: BlockingFirmwareState<'d, STATE>, +} + +#[cfg(target_os = "none")] +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile_blocking( + flash: &'a embassy_sync::blocking_mutex::Mutex>, + ) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self { + dfu: config.dfu, + state: BlockingFirmwareState::new(config.state, aligned), + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub fn get_state(&mut self) -> Result { + self.state.get_state() + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + #[cfg(feature = "_verify")] + pub fn verify_and_mark_updated( + &mut self, + _public_key: &[u8; 32], + _signature: &[u8; 64], + _update_len: u32, + ) -> Result<(), FirmwareUpdaterError> { + assert!(_update_len <= self.dfu.capacity() as u32); + + self.state.verify_booted()?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature); + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message)?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; + let signature = Signature::try_from(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message)?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.state.mark_updated() + } + + /// Verify the update in DFU with any digest. + pub fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + #[cfg(not(feature = "_verify"))] + pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_updated() + } + + /// Mark to trigger USB DFU device on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted()?; + self.state.mark_dfu() + } + + /// Mark firmware boot successful and stop rollback on reset. + pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_booted() + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); + self.state.verify_booted()?; + + self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; + + self.dfu.write(offset as u32, data)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.state.verify_booted()?; + self.dfu.erase(0, self.dfu.capacity() as u32)?; + + Ok(&mut self.dfu) + } +} + +/// Manages the state partition of the firmware update. +/// +/// Can be used standalone for more fine grained control, or as part of the updater. +pub struct BlockingFirmwareState<'d, STATE> { + state: STATE, + aligned: &'d mut [u8], +} + +impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { + /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + + /// Create a firmware state instance with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + Self { state, aligned } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub fn get_state(&mut self) -> Result { + self.state.read(0, &mut self.aligned)?; + + if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { + Ok(State::DfuDetach) + } else { + Ok(State::Boot) + } + } + + /// Mark to trigger firmware swap on next boot. + pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(SWAP_MAGIC) + } + + /// Mark to trigger USB DFU on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC) + } + + /// Mark firmware boot successful and stop rollback on reset. + pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(BOOT_MAGIC) + } + + fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, &mut self.aligned)?; + + if self.aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; + + if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + self.aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + self.aligned.fill(magic); + self.state.write(0, &self.aligned)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::cell::RefCell; + + use embassy_embedded_hal::flash::partition::BlockingPartition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::blocking_mutex::Mutex; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + updater.write_firmware(0, to_write.as_slice()).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs new file mode 100644 index 000000000..4814786bf --- /dev/null +++ b/embassy-boot/src/firmware_updater/mod.rs @@ -0,0 +1,49 @@ +mod asynch; +mod blocking; + +pub use asynch::{FirmwareState, FirmwareUpdater}; +pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +/// Firmware updater flash configuration holding the two flashes used by the updater +/// +/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct FirmwareUpdaterConfig { + /// The dfu flash partition + pub dfu: DFU, + /// The state flash partition + pub state: STATE, +} + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), + /// Bad state. + BadState, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} diff --git a/embassy-boot/src/fmt.rs b/embassy-boot/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs new file mode 100644 index 000000000..b4f03e01e --- /dev/null +++ b/embassy-boot/src/lib.rs @@ -0,0 +1,323 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +mod boot_loader; +mod digest_adapters; +mod firmware_updater; +#[cfg(test)] +mod mem_flash; +#[cfg(test)] +mod test_flash; + +// The expected value of the flash after an erase +// TODO: Use the value provided by NorFlash when available +pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; +pub use firmware_updater::{ + BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, + FirmwareUpdaterError, +}; + +pub(crate) const BOOT_MAGIC: u8 = 0xD0; +pub(crate) const SWAP_MAGIC: u8 = 0xF0; +pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; + +/// The state of the bootloader after running prepare. +#[derive(PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + /// Bootloader is ready to boot the active partition. + Boot, + /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. + Swap, + /// Application has received a request to reboot into DFU mode to apply an update. + DfuDetach, +} + +/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. +#[repr(align(32))] +pub struct AlignedBuffer(pub [u8; N]); + +impl AsRef<[u8]> for AlignedBuffer { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for AlignedBuffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + #![allow(unused_imports)] + + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + use futures::executor::block_on; + + use super::*; + use crate::boot_loader::BootLoaderConfig; + use crate::firmware_updater::FirmwareUpdaterConfig; + use crate::mem_flash::MemFlash; + use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; + + /* + #[test] + fn test_bad_magic() { + let mut flash = MemFlash([0xff; 131072]); + let mut flash = SingleFlashConfig::new(&mut flash); + + let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); + + assert_eq!( + bootloader.prepare_boot(&mut flash), + Err(BootError::BadMagic) + ); + } + */ + + #[test] + fn test_boot_state() { + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<57344, 4096, 4>::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); + + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 4096]; + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state() { + const FIRMWARE_SIZE: usize = 57344; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + // Writing after marking updated is not allowed until marked as booted. + let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); + assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 1024]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + + // Running again should cause a revert + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + // Last DFU page is untouched + flash.dfu().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + + // Mark as booted + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.mark_booted()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state_active_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::<12288, 4096, 8>::random(), + dfu: MemFlash::<16384, 2048, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state_dfu_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::random(), + dfu: MemFlash::<16384, 4096, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + } + + #[test] + #[cfg(feature = "_verify")] + fn test_verify() { + // The following key setup is based on: + // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example + + use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; + use rand::rngs::OsRng; + + let mut csprng = OsRng {}; + let keypair = SigningKey::generate(&mut csprng); + + let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; + let mut digest = Sha512::new(); + digest.update(&firmware); + let message = digest.finalize(); + let signature: Signature = keypair.sign(&message); + + let public_key = keypair.verifying_key(); + + // Setup flash + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<0, 0, 0>::default(), + dfu: MemFlash::<4096, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + let firmware_len = firmware.len(); + + let mut write_buf = [0; 4096]; + write_buf[0..firmware_len].copy_from_slice(firmware); + flash.dfu().write(0, &write_buf).unwrap(); + + // On with the test + let flash = flash.into_async(); + let mut aligned = [0; 4]; + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + + assert!(block_on(updater.verify_and_mark_updated( + &public_key.to_bytes(), + &signature.to_bytes(), + firmware_len as u32, + )) + .is_ok()); + } +} diff --git a/embassy-boot/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs new file mode 100644 index 000000000..40f352c8d --- /dev/null +++ b/embassy-boot/src/mem_flash.rs @@ -0,0 +1,170 @@ +#![allow(unused)] + +use core::ops::{Bound, Range, RangeBounds}; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +pub struct MemFlash { + pub mem: [u8; SIZE], + pub pending_write_successes: Option, +} + +#[derive(Debug)] +pub struct MemFlashError; + +impl MemFlash { + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + pending_write_successes: None, + } + } + + #[cfg(test)] + pub fn random() -> Self { + let mut mem = [0; SIZE]; + for byte in mem.iter_mut() { + *byte = rand::random::(); + } + Self { + mem, + pending_write_successes: None, + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + if let Some(pending_successes) = self.pending_write_successes { + if pending_successes > 0 { + self.pending_write_successes = Some(pending_successes - 1); + } else { + return Err(MemFlashError); + } + } + + for ((offset, mem_byte), new_byte) in self + .mem + .iter_mut() + .enumerate() + .skip(offset) + .take(bytes.len()) + .zip(bytes) + { + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + *mem_byte = *new_byte; + } + + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { + let from = from as usize; + let to = to as usize; + assert!(from % ERASE_SIZE == 0); + assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); + for i in from..to { + self.mem[i] = 0xFF; + } + Ok(()) + } + + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + + Ok(()) + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xFF) + } +} + +impl ErrorType + for MemFlash +{ + type Error = MemFlashError; +} + +impl NorFlashError for MemFlashError { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} + +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} diff --git a/embassy-boot/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs new file mode 100644 index 000000000..3ac9e71ab --- /dev/null +++ b/embassy-boot/src/test_flash/asynch.rs @@ -0,0 +1,64 @@ +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage_async::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex, + dfu: Mutex, + state: Mutex, +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(config.active), + dfu: Mutex::new(config.dfu), + state: Mutex::new(config.state), + } + } + + pub fn active(&self) -> Partition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> Partition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> Partition { + Self::create_partition(&self.state) + } + + fn create_partition(mutex: &Mutex) -> Partition { + Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) + } +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage::nor_flash::NorFlash, +{ + pub fn into_blocking(self) -> super::BlockingTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner(), + dfu: self.dfu.into_inner(), + state: self.state.into_inner(), + }; + super::BlockingTestFlash::new(config) + } +} diff --git a/embassy-boot/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs new file mode 100644 index 000000000..5ec476c65 --- /dev/null +++ b/embassy-boot/src/test_flash/blocking.rs @@ -0,0 +1,68 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex>, + dfu: Mutex>, + state: Mutex>, +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(RefCell::new(config.active)), + dfu: Mutex::new(RefCell::new(config.dfu)), + state: Mutex::new(RefCell::new(config.state)), + } + } + + pub fn active(&self) -> BlockingPartition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> BlockingPartition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> BlockingPartition { + Self::create_partition(&self.state) + } + + pub fn create_partition( + mutex: &Mutex>, + ) -> BlockingPartition { + BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) + } +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, +{ + pub fn into_async(self) -> super::AsyncTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner().into_inner(), + dfu: self.dfu.into_inner().into_inner(), + state: self.state.into_inner().into_inner(), + }; + super::AsyncTestFlash::new(config) + } +} diff --git a/embassy-boot/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs new file mode 100644 index 000000000..79b15a081 --- /dev/null +++ b/embassy-boot/src/test_flash/mod.rs @@ -0,0 +1,5 @@ +mod asynch; +mod blocking; + +pub(crate) use asynch::AsyncTestFlash; +pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml deleted file mode 100644 index 70919b76d..000000000 --- a/embassy-boot/stm32/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -edition = "2021" -name = "embassy-boot-stm32" -version = "0.1.0" -description = "Bootloader lib for STM32 chips" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/stm32/src/" -features = ["embassy-stm32/stm32f429zi"] -target = "thumbv7em-none-eabi" - -[lib] - -[dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } -log = { version = "0.4", optional = true } - -embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } -embassy-stm32 = { path = "../../embassy-stm32", default-features = false } -embassy-boot = { path = "../boot", default-features = false } -cortex-m = { version = "0.7.6" } -cortex-m-rt = { version = "0.7" } -embedded-storage = "0.3.1" -embedded-storage-async = { version = "0.4.1" } -cfg-if = "1.0.0" - -[features] -defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"] -log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"] -debug = ["defmt-rtt"] - -[profile.dev] -debug = 2 -debug-assertions = true -incremental = false -opt-level = 'z' -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false -incremental = false -lto = 'fat' -opt-level = 'z' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md deleted file mode 100644 index f6dadc8e7..000000000 --- a/embassy-boot/stm32/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# embassy-boot-stm32 - -An [Embassy](https://embassy.dev) project. - -An adaptation of `embassy-boot` for STM32. - -## Features - -* Configure bootloader partitions based on linker script. -* Load applications from active partition. diff --git a/embassy-boot/stm32/build.rs b/embassy-boot/stm32/build.rs deleted file mode 100644 index 2cbc7ef5e..000000000 --- a/embassy-boot/stm32/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - if target.starts_with("thumbv6m-") { - println!("cargo:rustc-cfg=armv6m"); - } -} diff --git a/embassy-boot/stm32/src/fmt.rs b/embassy-boot/stm32/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/stm32/src/fmt.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![macro_use] -#![allow(unused_macros)] - -use core::fmt::{Debug, Display, LowerHex}; - -#[cfg(all(feature = "defmt", feature = "log"))] -compile_error!("You may not enable both `defmt` and `log` features."); - -macro_rules! assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert!($($x)*); - } - }; -} - -macro_rules! assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_eq!($($x)*); - } - }; -} - -macro_rules! assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::assert_ne!($($x)*); - } - }; -} - -macro_rules! debug_assert { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert!($($x)*); - } - }; -} - -macro_rules! debug_assert_eq { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_eq!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_eq!($($x)*); - } - }; -} - -macro_rules! debug_assert_ne { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::debug_assert_ne!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::debug_assert_ne!($($x)*); - } - }; -} - -macro_rules! todo { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::todo!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::todo!($($x)*); - } - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unreachable { - ($($x:tt)*) => { - ::core::unreachable!($($x)*) - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unreachable { - ($($x:tt)*) => { - ::defmt::unreachable!($($x)*) - }; -} - -macro_rules! panic { - ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::panic!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::panic!($($x)*); - } - }; -} - -macro_rules! trace { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::trace!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::trace!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! debug { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::debug!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::debug!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! info { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::info!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::info!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! warn { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::warn!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::warn!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -macro_rules! error { - ($s:literal $(, $x:expr)* $(,)?) => { - { - #[cfg(feature = "log")] - ::log::error!($s $(, $x)*); - #[cfg(feature = "defmt")] - ::defmt::error!($s $(, $x)*); - #[cfg(not(any(feature = "log", feature="defmt")))] - let _ = ($( & $x ),*); - } - }; -} - -#[cfg(feature = "defmt")] -macro_rules! unwrap { - ($($x:tt)*) => { - ::defmt::unwrap!($($x)*) - }; -} - -#[cfg(not(feature = "defmt"))] -macro_rules! unwrap { - ($arg:expr) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); - } - } - }; - ($arg:expr, $($msg:expr),+ $(,)? ) => { - match $crate::fmt::Try::into_result($arg) { - ::core::result::Result::Ok(t) => t, - ::core::result::Result::Err(e) => { - ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); - } - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct NoneError; - -pub trait Try { - type Ok; - type Error; - fn into_result(self) -> Result; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} - -#[allow(unused)] -pub(crate) struct Bytes<'a>(pub &'a [u8]); - -impl<'a> Debug for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> Display for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -impl<'a> LowerHex for Bytes<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:#02x?}", self.0) - } -} - -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Bytes<'a> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "{:02x}", self.0) - } -} diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs deleted file mode 100644 index 4b4091ac9..000000000 --- a/embassy-boot/stm32/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![no_std] -#![warn(missing_docs)] -#![doc = include_str!("../README.md")] -mod fmt; - -pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, State, -}; -use embedded_storage::nor_flash::NorFlash; - -/// A bootloader for STM32 devices. -pub struct BootLoader { - /// The reported state of the bootloader after preparing for boot - pub state: State, -} - -impl BootLoader { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware - pub fn prepare( - config: BootLoaderConfig, - ) -> Self { - let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); - let mut boot = embassy_boot::BootLoader::new(config); - let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self { state } - } - - /// Boots the application. - /// - /// # Safety - /// - /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(self, start: u32) -> ! { - trace!("Loading app at 0x{:x}", start); - #[allow(unused_mut)] - let mut p = cortex_m::Peripherals::steal(); - #[cfg(not(armv6m))] - p.SCB.invalidate_icache(); - p.SCB.vtor.write(start); - - cortex_m::asm::bootload(start as *const u32) - } -} diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index ee110ee87..2d8895123 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -17,7 +17,7 @@ categories = [ bitflags = "2.4.1" cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } defmt = { version = "0.3.5", optional = true } -embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" } +embassy-boot = { version = "0.1.1", path = "../embassy-boot" } # embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } embassy-sync = { version = "0.5.0", path = "../embassy-sync" } diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index eba9a0579..7b62d9a20 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -9,8 +9,8 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [] } embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } -embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot/boot", features = [] } -embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf", features = [] } +embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot-nrf", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 38c9f8cff..ccaa9f8ef 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [] } embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", ] } -embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot/rp", features = [] } +embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot-rp", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = "0.3" diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 9c0aeb463..f4bc285bf 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32" } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index e81ff618e..575220ade 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index f75ffc8e6..12c34565a 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index f58bd9557..9f705dc26 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index 887126f9d..7ba5f143b 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index d5fabac82..08cb87e0b 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 394546b75..58bba66d7 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb" } embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 6c4dc7975..7ce560de2 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index a7273175f..3e41d1479 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", features = [] } -embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" } +embassy-boot-nrf = { path = "../../../../embassy-boot-nrf" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index 5bc61e9ec..3cf61a002 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-rp = { path = "../../../../embassy-rp", features = [] } -embassy-boot-rp = { path = "../../../../embassy-boot/rp" } +embassy-boot-rp = { path = "../../../../embassy-boot-rp" } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } embassy-time = { path = "../../../../embassy-time", features = [] } diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index 2d88b0f78..74c01b0f4 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } -embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index ada073970..96635afa2 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -10,7 +10,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] } -embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } -- cgit