diff --git a/Cargo.lock b/Cargo.lock index 1953f84..5047c94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "byteorder" version = "1.4.3" @@ -106,21 +112,22 @@ dependencies = [ [[package]] name = "defmt" -version = "0.2.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15fe96f5d208164afa70583ff8f062e7697cbbb0b98e5076fbf8ac6da9edff0f" +checksum = "d3a0ae7494d9bff013d7b89471f4c424356a71e9752e0c78abe7e6c608a16bb3" dependencies = [ + "bitflags", "defmt-macros", - "semver 1.0.7", ] [[package]] name = "defmt-macros" -version = "0.2.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd2c3949cb76c25f48c363e61b97f05b317efe3c12fa45d54a6599c3949c85e" +checksum = "6d944432e281084511691b36e5e9c794c19c33675822c9019e3b64f5b89e10da" dependencies = [ "defmt-parser", + "proc-macro-error", "proc-macro2 1.0.37", "quote 1.0.18", "syn 1.0.91", @@ -128,9 +135,9 @@ dependencies = [ [[package]] name = "defmt-parser" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc621c2b4f5f5635e34021c38af2ccb0c1dae38ba11ebee25258de8bb1cee9fe" +checksum = "0db23d29972d99baa3de2ee2ae3f104c10564a6d05a346eb3f4c4f2c0525a06e" [[package]] name = "desse" @@ -372,7 +379,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", + "semver", ] [[package]] @@ -390,12 +397,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" - [[package]] name = "semver-parser" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index a7d98f2..4ec6e59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ moonboot-macros = { path = "./macros", version = "0.1.2" } heapless = {version = "0.7", features = ["serde"] } serde = { version = "1.0", features = ["derive"], default-features = false, optional = true } cortex-m = { version = "0.7", optional = true } -defmt = { version = "0.2", optional = true } +defmt = { version = "0.3", optional = true } logger-crate = { version = "0.4", optional = true, package = "log" } crc = "2.0" desse = { version = "0.2.1", optional = true } @@ -38,7 +38,7 @@ defmt-warn = [] defmt-error = [] [package.metadata.release] -enable-features = ["ram-state", "ram-state", "cortex-m"] +enable-features = ["ram-state", "cortex-m"] shared-version = true dependent-version = "upgrade" pre-release-replacements = [ diff --git a/src/boot/mod.rs b/src/boot/mod.rs index 3db4df8..927716b 100644 --- a/src/boot/mod.rs +++ b/src/boot/mod.rs @@ -1,77 +1,57 @@ use crate::{ + exchange::Exchange, hardware::processor::Processor, hardware::{Bank, Config}, - state::{ExchangeProgress, State, Update, UpdateError}, - Address, + state::{ExchangeProgress, ExchangeStep, State, Update, UpdateError}, + Context, }; -use embedded_storage::Storage; - use crate::log; #[cfg(feature = "defmt")] use defmt::Format; - -/// Error occured during nemory access -#[cfg_attr(feature = "use-defmt", derive(Format))] -#[derive(Debug)] -enum MemoryError { - BankSizeNotEqual, - BankSizeZero, - ReadFailure, - WriteFailure, -} - /// Use this from your bootloader application and call boot() to do the magic, reading the current /// state via the State type and then jumping to the new image using the Jumper specified -pub struct MoonbootBoot< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, // TODO: Wrap these into a context struct like rubble? - const INTERNAL_PAGE_SIZE: usize, -> { +pub struct MoonbootBoot { config: Config, - internal_memory: InternalMemory, - state: HardwareState, - processor: CPU, + storage: CONTEXT::Storage, + state: CONTEXT::State, + processor: CONTEXT::Processor, + exchange: CONTEXT::Exchange, } -impl< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, - const INTERNAL_PAGE_SIZE: usize, - > MoonbootBoot -{ +impl MoonbootBoot { /// create a new instance of the bootloader pub fn new( config: Config, - internal_memory: InternalMemory, - state: HardwareState, - processor: CPU, - ) -> MoonbootBoot { + storage: CONTEXT::Storage, + state: CONTEXT::State, + processor: CONTEXT::Processor, + exchange: CONTEXT::Exchange, + ) -> Self { Self { config, - internal_memory, + storage, state, processor, + exchange, } } /// Destroy this instance of the bootloader and return access to the hardware peripheral - pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) { - (self.internal_memory, self.state, self.processor) + pub fn destroy(self) -> (CONTEXT::Storage, CONTEXT::State, CONTEXT::Processor) { + (self.storage, self.state, self.processor) } /// Execute the update and boot logic of the bootloader - pub fn boot(&mut self) -> Result { + pub fn boot(&mut self) -> Result::Error> { // TODO: consider error handling log::info!("Booting with moonboot!"); self.processor.setup(&self.config); - let mut state = self.state.read(); + let mut state = self.state.read()?; log::info!("Old State: {:?}", state); @@ -80,16 +60,14 @@ impl< Update::None => self.handle_none(), Update::Request(bank) => self.handle_request(bank), Update::Revert(bank) => self.handle_revert(bank), - Update::Exchanging(progress) => self.handle_exchanging(progress), + Update::Exchanging(progress) => self.handle_exchanging(progress)?, Update::Error(err) => Update::Error(err), }; - // TODO: Handle Progress Variable in state to recover from power loss - log::info!("New State: {:?}", state); // Step 2: Update state of Bootloader - self.state.write(state)?; + self.state.write(&state)?; // Step 3: Jump to new or unchanged firmware self.jump_to_firmware(); @@ -125,17 +103,24 @@ impl< // Handle a case of power interruption or similar, which lead to a exchange_banks being // interrupted. - fn handle_exchanging(&mut self, progress: ExchangeProgress) -> Update { + fn handle_exchanging( + &mut self, + progress: ExchangeProgress, + ) -> Result::Error> { log::error!( "Firmware Update was interrupted! Trying to recover with exchange operation: {:?}", progress ); - let exchange_result = - self.exchange_banks_with_start(progress.a, progress.b, progress.page_index); + let exchange_result = self.exchange.exchange::( + &self.config, + &mut self.storage, + &mut self.state, + progress, + ); - if exchange_result.is_ok() { - let state = self.state.read().update; + Ok(if exchange_result.is_ok() { + let state = self.state.read()?.update; match state { Update::Exchanging(progress) => { if progress.recovering { @@ -152,7 +137,7 @@ impl< exchange_result ); Update::Error(UpdateError::ImageExchangeFailed) - } + }) } // Revert the bootable image with the image in index new_firmware. Returns Revert on success if @@ -172,7 +157,18 @@ impl< ); // Try to exchange the firmware images - let exchange_result = self.exchange_banks(new, old); + let exchange_result = self.exchange.exchange::( + &self.config, + &mut self.storage, + &mut self.state, + ExchangeProgress { + a: new, + b: old, + page_index: 0, + recovering: false, + step: ExchangeStep::AToScratch, + }, + ); if exchange_result.is_ok() { if with_failsafe_revert { // Update Firmware Update State to revert. The Application will set this to @@ -197,102 +193,6 @@ impl< } } - fn exchange_banks(&mut self, a: Bank, b: Bank) -> Result<(), MemoryError> { - self.exchange_banks_with_start(a, b, 0) - } - - fn exchange_banks_with_start( - &mut self, - a: Bank, - b: Bank, - start_index: u32, - ) -> Result<(), MemoryError> { - // TODO: Sanity Check start_index - if a.size != b.size { - return Err(MemoryError::BankSizeNotEqual); - } - - if a.size == 0 || b.size == 0 { - return Err(MemoryError::BankSizeZero); - } - - let size = a.size; // Both are equal - - let full_pages = size / INTERNAL_PAGE_SIZE as Address; - let remaining_page_length = size as usize % INTERNAL_PAGE_SIZE; - - let mut page_a_buf = [0_u8; INTERNAL_PAGE_SIZE]; - let mut page_b_buf = [0_u8; INTERNAL_PAGE_SIZE]; - // can we reduce this to 1 buf and fancy operations? - // probably not with the read/write API. - // classic memory exchange problem :) - - // Set this in the exchanging part to know whether we are in a recovery process from a - // failed update or on the initial update - let recovering = matches!(self.state.read().update, Update::Revert(_)); - - // TODO: Fix - let a_location = a.location; - let b_location = b.location; - - for page_index in start_index..full_pages { - let offset = page_index * INTERNAL_PAGE_SIZE as u32; - log::trace!( - "Exchange: Page {}, from a ({}) to b ({})", - page_index, - a_location + offset, - b_location + offset - ); - self.internal_memory - .read(a_location + offset, &mut page_a_buf) - .map_err(|_| MemoryError::ReadFailure)?; - self.internal_memory - .read(b_location + offset, &mut page_b_buf) - .map_err(|_| MemoryError::ReadFailure)?; - self.internal_memory - .write(a_location + offset, &page_b_buf) - .map_err(|_| MemoryError::WriteFailure)?; - self.internal_memory - .write(b_location + offset, &page_a_buf) - .map_err(|_| MemoryError::WriteFailure)?; - - // Store the exchange progress - let mut state = self.state.read(); - state.update = Update::Exchanging(ExchangeProgress { - a, - b, - recovering, - page_index, - }); - // TODO: Ignore the error here? - let _ = self.state.write(state); - } - // TODO: Fit this into the while loop - if remaining_page_length > 0 { - let offset = full_pages * INTERNAL_PAGE_SIZE as u32; - - self.internal_memory - .read( - a.location + offset, - &mut page_a_buf[0..remaining_page_length], - ) - .map_err(|_| MemoryError::ReadFailure)?; - self.internal_memory - .read( - b.location + offset, - &mut page_b_buf[0..remaining_page_length], - ) - .map_err(|_| MemoryError::ReadFailure)?; - self.internal_memory - .write(a.location + offset, &page_a_buf[0..remaining_page_length]) - .map_err(|_| MemoryError::WriteFailure)?; - self.internal_memory - .write(b.location + offset, &page_b_buf[0..remaining_page_length]) - .map_err(|_| MemoryError::WriteFailure)?; - } - - Ok(()) - } // Jump to the firmware image marked as bootable fn jump_to_firmware(&mut self) -> ! { let app_exec_image = self.config.boot_bank; diff --git a/src/exchange/mod.rs b/src/exchange/mod.rs new file mode 100644 index 0000000..416e1e1 --- /dev/null +++ b/src/exchange/mod.rs @@ -0,0 +1,22 @@ +pub mod ram; + +pub mod scratch; + +use crate::{ + hardware::Config, + state::{ExchangeProgress, State}, +}; +use embedded_storage::Storage; + +/// Abstraction for the exchange operation of the current state. +pub trait Exchange { + type Error: core::fmt::Debug; + + fn exchange( + &mut self, + config: &Config, + storage: &mut STORAGE, + state: &mut STATE, + progress: ExchangeProgress, + ) -> Result<(), Self::Error>; +} diff --git a/src/exchange/ram.rs b/src/exchange/ram.rs new file mode 100644 index 0000000..984ad3f --- /dev/null +++ b/src/exchange/ram.rs @@ -0,0 +1,129 @@ +use embedded_storage::Storage; + +use crate::{ + exchange::Exchange, + hardware::Config, + log, + state::{ExchangeProgress, State, Update}, + Address, +}; + +pub struct Ram; + +pub enum ExchangeError { + Storage(STORAGE), + State(STATE), +} + +impl core::fmt::Debug for ExchangeError +where + STORAGE: core::fmt::Debug, + STATE: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Storage(arg0) => f.debug_tuple("Storage").field(arg0).finish(), + Self::State(arg0) => f.debug_tuple("State").field(arg0).finish(), + } + } +} + +impl Exchange for Ram +where + STORAGE::Error: core::fmt::Debug, + STATE::Error: core::fmt::Debug, +{ + type Error = ExchangeError; + + fn exchange( + &mut self, + _config: &Config, + storage: &mut STORAGE, + state: &mut STATE, + progress: ExchangeProgress, + ) -> Result<(), Self::Error> { + let ExchangeProgress { + a, + b, + page_index, + step, + .. + } = progress; + + assert_eq!(a.size, b.size); + assert_ne!(a.size, 0); + assert_ne!(b.size, 0); + + let size = a.size; // Both are equal + + let full_pages = size / INTERNAL_PAGE_SIZE as Address; + let remaining_page_length = size as usize % INTERNAL_PAGE_SIZE; + + let mut page_a_buf = [0_u8; INTERNAL_PAGE_SIZE]; + let mut page_b_buf = [0_u8; INTERNAL_PAGE_SIZE]; + + let mut last_state = state.read().map_err(ExchangeError::State)?; + + // Set this in the exchanging part to know whether we are in a recovery process from a + // failed update or on the initial update + let recovering = matches!(last_state.update, Update::Revert(_)); + + for page_index in page_index..full_pages { + let offset = page_index * INTERNAL_PAGE_SIZE as u32; + let a_location = a.location + offset; + let b_location = b.location + offset; + log::trace!( + "Exchange: Page {}, from a ({}) to b ({})", + page_index, + a_location, + b_location + ); + + storage + .read(a_location, &mut page_a_buf) + .map_err(ExchangeError::Storage)?; + storage + .read(b_location, &mut page_b_buf) + .map_err(ExchangeError::Storage)?; + storage + .write(a_location, &page_b_buf) + .map_err(ExchangeError::Storage)?; + storage + .write(b_location, &page_a_buf) + .map_err(ExchangeError::Storage)?; + + // Store the exchange progress + + last_state.update = Update::Exchanging(ExchangeProgress { + a, + b, + recovering, + page_index, + step, + }); + + state.write(&last_state).map_err(ExchangeError::State)?; + } + // TODO: Fit this into the while loop + if remaining_page_length > 0 { + let offset = full_pages * INTERNAL_PAGE_SIZE as u32; + let a_location = a.location + offset; + let b_location = b.location + offset; + + storage + .read(a_location, &mut page_a_buf[0..remaining_page_length]) + .map_err(ExchangeError::Storage)?; + storage + .read(b_location, &mut page_b_buf[0..remaining_page_length]) + .map_err(ExchangeError::Storage)?; + storage + .write(a_location, &page_a_buf[0..remaining_page_length]) + .map_err(ExchangeError::Storage)?; + storage + .write(b_location + offset, &page_b_buf[0..remaining_page_length]) + .map_err(ExchangeError::Storage)?; + } + + Ok(()) + } +} diff --git a/src/exchange/scratch.rs b/src/exchange/scratch.rs new file mode 100644 index 0000000..2d1f908 --- /dev/null +++ b/src/exchange/scratch.rs @@ -0,0 +1,154 @@ +use core::fmt::Debug; + +use embedded_storage::Storage; + +use crate::{ + exchange::Exchange, + hardware::Config, + log, + state::{ExchangeProgress, ExchangeStep, State, Update}, + Address, +}; + +pub struct Scratch; + +pub enum ExchangeError { + Storage(STORAGE), + State(STATE), + ScratchInsufficient, +} + +impl Debug for ExchangeError +where + STORAGE: Debug, + STATE: Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Storage(arg0) => f.debug_tuple("Storage").field(arg0).finish(), + Self::State(arg0) => f.debug_tuple("State").field(arg0).finish(), + Self::ScratchInsufficient => f.write_str("ScratchInsufficient"), + } + } +} + +impl Exchange for Scratch +where + STORAGE::Error: Debug, + STATE::Error: Debug, +{ + type Error = ExchangeError; + + fn exchange( + &mut self, + config: &Config, + storage: &mut STORAGE, + state: &mut STATE, + progress: ExchangeProgress, + ) -> Result<(), Self::Error> { + let ExchangeProgress { + a, + b, + page_index, + mut step, + .. + } = progress; + + assert_eq!(a.size, b.size); + assert_ne!(a.size, 0); + assert_ne!(b.size, 0); + + if config.scratch_bank.size < INTERNAL_PAGE_SIZE as u32 { + return Err(ExchangeError::ScratchInsufficient); + } + + let size = a.size; // Both are equal + + let full_pages = size / INTERNAL_PAGE_SIZE as Address; + let remaining_page_length = size as usize % INTERNAL_PAGE_SIZE; + + assert_eq!(remaining_page_length, 0); + + let mut ram_buf = [0_u8; INTERNAL_PAGE_SIZE]; + + let mut last_state = state.read().map_err(ExchangeError::State)?; + + // Set this in the exchanging part to know whether we are in a recovery process from a + // failed update or on the initial update + let recovering = matches!(last_state.update, Update::Revert(_)); + + let a_location = a.location; + let b_location = b.location; + + let scratch_bank_pages = config.scratch_bank.size / INTERNAL_PAGE_SIZE as u32; + + let mut first = true; + for page_index in page_index..full_pages { + let offset = page_index * INTERNAL_PAGE_SIZE as u32; + + let a_location = a_location + offset; + let b_location = b_location + offset; + + let scratch_index = page_index % scratch_bank_pages; + let scratch_offset = scratch_index * INTERNAL_PAGE_SIZE as u32; + let scratch_location = config.scratch_bank.location + scratch_offset; + + log::trace!( + "Exchange: Page {}, from a ({}) to b ({}) using scratch ({})", + page_index, + a_location, + b_location, + scratch_location + ); + + loop { + if first { + // Do not write the state to flash, as it is already recent. + first = false; + } else { + last_state.update = Update::Exchanging(ExchangeProgress { + a, + b, + recovering, + page_index, + step, + }); + state.write(&last_state).map_err(ExchangeError::State)?; + } + + match step { + ExchangeStep::AToScratch => { + storage + .read(a_location, &mut ram_buf) + .map_err(ExchangeError::Storage)?; + storage + .write(scratch_location, &ram_buf) + .map_err(ExchangeError::Storage)?; + step = ExchangeStep::BToA; + } + ExchangeStep::BToA => { + storage + .read(b_location, &mut ram_buf) + .map_err(ExchangeError::Storage)?; + storage + .write(a_location, &ram_buf) + .map_err(ExchangeError::Storage)?; + step = ExchangeStep::ScratchToB; + } + ExchangeStep::ScratchToB => { + storage + .read(scratch_location, &mut ram_buf) + .map_err(ExchangeError::Storage)?; + storage + .write(b_location, &ram_buf) + .map_err(ExchangeError::Storage)?; + step = ExchangeStep::AToScratch; + break; + } + } + } + } + + Ok(()) + } +} diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index bdef5ce..3b9cfdf 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -47,6 +47,8 @@ pub struct Config { pub update_bank: Bank, /// bank the bootloader is contained in, switching between banks pub bootloader_bank: Bank, + /// bank the pages are temporarily stored when using the `Scratch` exchange method + pub scratch_bank: Bank, // Initial Image is stored to this bank after first update, restore on failure // pub golden_bank: Bank, /// section of RAM of this device diff --git a/src/hardware/processor.rs b/src/hardware/processor.rs index b447c80..dcd54f3 100644 --- a/src/hardware/processor.rs +++ b/src/hardware/processor.rs @@ -29,15 +29,14 @@ pub mod cortex_m { fn do_jump(&mut self, address: super::Address) -> ! { unsafe { // Set Vector Table to new vector table (unsafe but okay here) - (*cortex_m::peripheral::SCB::ptr()).vtor.write(address); + (*cortex_m::peripheral::SCB::PTR).vtor.write(address); cortex_m::asm::bootload(address as *const u32); } } - fn setup(&mut self, config: &crate::hardware::Config) { + fn setup(&mut self, _config: &crate::hardware::Config) { // Nothing to do! } } } - diff --git a/src/lib.rs b/src/lib.rs index 7ca2a85..d11600c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,13 +11,17 @@ //!* Automatic Linker Script generation based on a Section/Parition Description in Rust Code mod boot; + /// Implementations for use in the bootloader pub use boot::MoonbootBoot; mod manager; + /// Implementations for use in the firmware pub use manager::MoonbootManager; +/// Various processes for exchanging pages +pub mod exchange; /// Common hardware abstractions and associated implementations pub mod hardware; /// Shared state management between firmware and bootloader @@ -43,6 +47,13 @@ pub(crate) use defmt as log; #[cfg(feature = "use-log")] pub(crate) use logger_crate as log; +pub trait Context { + type Storage: embedded_storage::Storage; + type State: state::State; + type Processor: hardware::processor::Processor; + type Exchange: exchange::Exchange; +} + #[cfg(not(any(feature = "use-log", feature = "use-defmt")))] pub(crate) mod log { macro_rules! info { diff --git a/src/manager/mod.rs b/src/manager/mod.rs index b6d514c..9f41206 100644 --- a/src/manager/mod.rs +++ b/src/manager/mod.rs @@ -1,57 +1,74 @@ use crate::{ hardware::{processor::Processor, Config}, state::{State, Update}, + Context, }; -use embedded_storage::{ReadStorage, Storage}; - use crate::log; /// Instantiate this in your application to enable mutation of the State specified in this and jump /// to the bootloader to apply any updates. -pub struct MoonbootManager< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, - const INTERNAL_PAGE_SIZE: usize, -> { +pub struct MoonbootManager { config: Config, - internal_memory: InternalMemory, - state: HardwareState, - processor: CPU, + storage: CONTEXT::Storage, + state: CONTEXT::State, + processor: CONTEXT::Processor, } -impl< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, - const INTERNAL_PAGE_SIZE: usize, - > MoonbootManager +#[cfg_attr(feature = "use-defmt", derive(Format))] +#[derive(Debug)] +pub struct InitError; + +#[cfg_attr(feature = "use-defmt", derive(Format))] +#[derive(Debug)] +pub enum MarkError { + UpdateQueuedButNotInstalled, + State(E), +} + +impl + MoonbootManager { pub fn new( config: Config, - internal_memory: InternalMemory, - state: HardwareState, - processor: CPU, - ) -> MoonbootManager { - Self { + storage: CONTEXT::Storage, + state: CONTEXT::State, + processor: CONTEXT::Processor, + ) -> Result { + if config.update_bank.size > config.boot_bank.size { + log::error!( + "Requested update bank {:?} is larger than boot bank {:?}", + config.update_bank, + config.boot_bank + ); + return Err(InitError); + } + + if config.update_bank.size == 0 || config.boot_bank.size == 0 { + log::error!("Requested banks are of zero size"); + return Err(InitError); + } + + Ok(Self { config, - internal_memory, + storage, state, processor, - } + }) } /// Destroy this instance of the boot manager and return access to the hardware peripheral - pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) { - (self.internal_memory, self.state, self.processor) + pub fn destroy(self) -> (CONTEXT::Storage, CONTEXT::State, CONTEXT::Processor) { + (self.storage, self.state, self.processor) } /// Run this immediately after booting your new image successfully to mark the boot as /// succesful. If you do not do this, any reset will cause the bootloader to restore to the /// previous firmware image. - pub fn mark_boot_successful(&mut self) -> Result<(), ()> { - let mut current_state = self.state.read(); + pub fn mark_boot_successful( + &mut self, + ) -> Result<(), MarkError<::Error>> { + let mut current_state = self.state.read().map_err(MarkError::State)?; log::info!( "Application running, marking boot as successful. Current state: {:?}", @@ -69,34 +86,24 @@ impl< } _ => { log::error!("There is an update queued, but it has not been installed yet. Did you skip the bootloader?"); - return Err(()); + return Err(MarkError::UpdateQueuedButNotInstalled); } }; log::trace!("New state: {:?}", current_state); - self.state.write(current_state) + self.state.write(¤t_state).map_err(MarkError::State) } // Upgrade firmware verifiying the given signature over the size of size. // Can only return an error or diverge (!, represented by Void while ! is not a type yet) - pub fn update(&mut self) -> Result { + pub fn update(&mut self) -> Result::Error> { // Apply the update stored in the update bank let bank = self.config.update_bank; - // TODO: Check size value! log::info!("Update requested on slot {:?}", bank); - if bank.size > self.config.boot_bank.size { - log::error!( - "Requested update bank {:?} is larger than boot bank {:?}", - bank, - self.config.boot_bank - ); - return Err(()); - } - - let mut current_state = self.state.read(); + let mut current_state = self.state.read()?; if current_state.update != Update::None { log::warn!( @@ -107,7 +114,7 @@ impl< current_state.update = Update::Request(bank); - self.state.write(current_state)?; + self.state.write(¤t_state)?; log::info!("Stored update request, jumping to bootloader! Geronimo!"); @@ -124,79 +131,3 @@ impl< self.processor.do_jump(bootloader_address) } } - -/// Easily get read access to the update bank -impl< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, - const INTERNAL_PAGE_SIZE: usize, - > core::convert::AsRef<[u8]> - for MoonbootManager -{ - #[inline] - fn as_ref(&self) -> &[u8] { - unsafe { - core::slice::from_raw_parts( - self.config.update_bank.location as *const u8, - self.config.update_bank.size as usize, - ) - } - } -} - -/// Read Access to the current update target slot -impl< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, - const INTERNAL_PAGE_SIZE: usize, - > ReadStorage for MoonbootManager -{ - type Error = (); // TODO - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - let bank = self.config.update_bank; // For now we always write updates to this bank. - if offset > bank.size || offset + bytes.len() as u32 > bank.size { - Err(()) // TODO: We want better error types! - } else { - // TODO! fix - let bank_start = bank.location; - log::info!("Writing at {:x}[{:x}]", bank_start, offset); - match bank.memory_unit { - crate::hardware::MemoryUnit::Internal => { - { self.internal_memory.read(bank_start + offset, bytes) }.map_err(|_| ()) - } - } - } - } - - fn capacity(&self) -> usize { - self.config.update_bank.size as usize - } -} - -/// Write Access to the current update target slot -impl< - InternalMemory: Storage, - HardwareState: State, - CPU: Processor, - const INTERNAL_PAGE_SIZE: usize, - > Storage for MoonbootManager -{ - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - let bank = self.config.update_bank; // For now we always write updates to this bank. - if offset > bank.size || offset + bytes.len() as u32 > bank.size { - Err(()) // TODO: We want better error types! - } else { - // TODO! fix - let bank_start = bank.location; - log::info!("Writing at {:x}[{:x}]", bank_start, offset); - match bank.memory_unit { - crate::hardware::MemoryUnit::Internal => { - { self.internal_memory.write(bank_start + offset, bytes) }.map_err(|_| ()) - } - } - } - } -} diff --git a/src/state.rs b/src/state/mod.rs similarity index 60% rename from src/state.rs rename to src/state/mod.rs index ae3cdae..24d6bfb 100644 --- a/src/state.rs +++ b/src/state/mod.rs @@ -1,10 +1,14 @@ +#[cfg(feature = "ram-state")] +pub mod ram; + use crate::hardware::Bank; -use crate::log; + +use core::fmt::Debug; use crc::{Crc, CRC_32_CKSUM}; #[cfg(feature = "defmt")] use defmt::Format; -#[cfg(feature = "ram-state")] +#[cfg(any(feature = "ram-state", feature = "scratch-state"))] use desse::{Desse, DesseSized}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -14,7 +18,10 @@ use serde::{Deserialize, Serialize}; // exchanged via software updates easily #[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] +#[cfg_attr( + any(feature = "ram-state", feature = "scratch-state"), + derive(Desse, DesseSized) +)] #[derive(Debug, PartialEq, Eq)] pub enum Update { // No update requested, just jump to the application @@ -33,7 +40,10 @@ pub enum Update { #[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] +#[cfg_attr( + any(feature = "ram-state", feature = "scratch-state"), + derive(Desse, DesseSized) +)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// Errors that can occur during update pub enum UpdateError { @@ -47,10 +57,30 @@ pub enum UpdateError { InvalidSignature, } +/// Upcoming operation of the exchange process for a given page index. +#[cfg_attr(feature = "use-defmt", derive(Format))] +#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] +#[cfg_attr( + any(feature = "ram-state", feature = "scratch-state"), + derive(Desse, DesseSized) +)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExchangeStep { + /// Copy page A to the scratch page. + AToScratch, + /// Copy page B to page A. + BToA, + /// Copy the scratch page to page B. + ScratchToB, +} + /// Store the progress of the current exchange operation #[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] +#[cfg_attr( + any(feature = "ram-state", feature = "scratch-state"), + derive(Desse, DesseSized) +)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExchangeProgress { /// Bank the update is coming from @@ -59,6 +89,8 @@ pub struct ExchangeProgress { pub(crate) b: Bank, /// Page the operation has last copied pub(crate) page_index: u32, + /// Upcoming operation of the exchange process for a given page index. + pub(crate) step: ExchangeStep, /// Whether this exchange resulted from a Request (false) or a Revert (true) pub(crate) recovering: bool, } @@ -66,7 +98,10 @@ pub struct ExchangeProgress { /// Struct used to store the state of the bootloader situation in NVM #[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] +#[cfg_attr( + any(feature = "ram-state", feature = "scratch-state"), + derive(Desse, DesseSized) +)] #[derive(Debug)] pub struct MoonbootState { /// If set Request, an Update is requested. This will exchange the two images, set the update @@ -81,10 +116,12 @@ pub struct MoonbootState { /// RAM. As long as you don't want to perform update download, power cycle the device, and then /// apply the update, storing it in volatile memory is fine. pub trait State { + type Error: Debug; + /// Read the shared state - fn read(&mut self) -> MoonbootState; + fn read(&mut self) -> Result; /// Write the new state to the shared state - fn write(&mut self, data: MoonbootState) -> Result<(), ()>; + fn write(&mut self, data: &MoonbootState) -> Result<(), Self::Error>; } /// Size of the serialized state @@ -96,66 +133,3 @@ const CRC: Crc = Crc::::new(&CRC_32_CKSUM); fn checksum(bytes: &[u8]) -> StateCrcType { CRC.checksum(bytes) } - -/// State stored in the RAM -/// TODO: Move to hardware folder together with state trait? -#[cfg(feature = "ram-state")] -pub mod ram { - use super::*; - - /// State read and written to RAM. This assumes the device is never powered off / the ram is never - /// reset! - pub struct RamState; - - extern "C" { - static mut _moonboot_state_crc_start: StateCrcType; - static mut _moonboot_state_data_start: [u8; STATE_SERIALIZED_MAX_SIZE]; - // TODO: Move these as normal variables to linker sections via #[link] macro? - } - - impl State for RamState { - fn read(&mut self) -> MoonbootState { - let crc = unsafe { _moonboot_state_crc_start }; - - log::info!( - "Reading data with len: {}, CRC: {}", - STATE_SERIALIZED_MAX_SIZE, - crc - ); - - let checksum = checksum(unsafe { &_moonboot_state_data_start }); - if crc == checksum { - let data = - MoonbootState::deserialize_from(unsafe { &_moonboot_state_data_start }); - log::trace!("CRC Match! {}: {:?}", crc, data); - return data; - } else { - log::trace!("CRC Mismatch! {} vs {}", crc, checksum); - } - - MoonbootState { - update: Update::None, - } - } - - fn write(&mut self, data: MoonbootState) -> Result<(), ()> { - log::trace!("Writing data {:?}", data); - - unsafe { _moonboot_state_data_start = data.serialize() }; - log::trace!("Written data: {:?}", unsafe { - &_moonboot_state_data_start - }); - - unsafe { - _moonboot_state_crc_start = checksum(&_moonboot_state_data_start); - } - log::info!( - "Written len: {}, checksum: {}", - STATE_SERIALIZED_MAX_SIZE, - unsafe { _moonboot_state_crc_start } - ); - - Ok(()) - } - } -} diff --git a/src/state/ram.rs b/src/state/ram.rs new file mode 100644 index 0000000..4601edf --- /dev/null +++ b/src/state/ram.rs @@ -0,0 +1,57 @@ +use crate::log; + +use super::*; + +/// State read and written to RAM. This assumes the device is never powered off / the ram is never +/// reset! +pub struct RamState; + +extern "C" { + static mut _moonboot_state_crc_start: StateCrcType; + static mut _moonboot_state_data_start: [u8; STATE_SERIALIZED_MAX_SIZE]; + // TODO: Move these as normal variables to linker sections via #[link] macro? +} + +impl State for RamState { + type Error = void::Void; + + fn read(&mut self) -> Result { + let crc = unsafe { _moonboot_state_crc_start }; + + log::info!( + "Reading data with len: {}, CRC: {}", + STATE_SERIALIZED_MAX_SIZE, + crc + ); + + let checksum = checksum(unsafe { &_moonboot_state_data_start }); + if crc == checksum { + let data = MoonbootState::deserialize_from(unsafe { &_moonboot_state_data_start }); + log::trace!("CRC Match! {}: {:?}", crc, data); + Ok(data) + } else { + log::trace!("CRC Mismatch! {} vs {}", crc, checksum); + Ok(MoonbootState { + update: Update::None, + }) + } + } + + fn write(&mut self, data: &MoonbootState) -> Result<(), Self::Error> { + log::trace!("Writing data {:?}", data); + + unsafe { _moonboot_state_data_start = data.serialize() }; + log::trace!("Written data: {:?}", unsafe { &_moonboot_state_data_start }); + + unsafe { + _moonboot_state_crc_start = checksum(&_moonboot_state_data_start); + } + log::info!( + "Written len: {}, checksum: {}", + STATE_SERIALIZED_MAX_SIZE, + unsafe { _moonboot_state_crc_start } + ); + + Ok(()) + } +}