mirror of
				https://github.com/jhbruhn/moonboot.git
				synced 2025-10-30 10:56:01 +00:00 
			
		
		
		
	Use Context trait
This commit is contained in:
		
							parent
							
								
									1cd3c4f5be
								
							
						
					
					
						commit
						3d7a4b7cfb
					
				
					 6 changed files with 160 additions and 137 deletions
				
			
		|  | @ -1,11 +1,10 @@ | |||
| use crate::{ | ||||
|     hardware::processor::Processor, | ||||
|     hardware::{Bank, Config}, | ||||
|     state::{Exchange, ExchangeError, ExchangeProgress, ExchangeStep, State, Update, UpdateError}, | ||||
|     state::{Exchange, ExchangeProgress, ExchangeStep, State, Update, UpdateError}, | ||||
|     Context, | ||||
| }; | ||||
| 
 | ||||
| use embedded_storage::Storage; | ||||
| 
 | ||||
| use crate::log; | ||||
| 
 | ||||
| #[cfg(feature = "defmt")] | ||||
|  | @ -13,36 +12,23 @@ use defmt::Format; | |||
| 
 | ||||
| /// 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< | ||||
|     STORAGE: Storage, | ||||
|     STATE: State, | ||||
|     PROCESSOR: Processor, // TODO: Wrap these into a context struct like rubble?
 | ||||
|     EXCHANGE: Exchange, | ||||
|     const INTERNAL_PAGE_SIZE: usize, | ||||
| > { | ||||
| pub struct MoonbootBoot<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> { | ||||
|     config: Config, | ||||
|     storage: STORAGE, | ||||
|     state: STATE, | ||||
|     processor: PROCESSOR, | ||||
|     exchange: EXCHANGE, | ||||
|     storage: CONTEXT::Storage, | ||||
|     state: CONTEXT::State, | ||||
|     processor: CONTEXT::Processor, | ||||
|     exchange: CONTEXT::Exchange, | ||||
| } | ||||
| 
 | ||||
| impl< | ||||
|         STORAGE: Storage, | ||||
|         STATE: State, | ||||
|         PROCESSOR: Processor, | ||||
|         EXCHANGE: Exchange, | ||||
|         const INTERNAL_PAGE_SIZE: usize, | ||||
|     > MoonbootBoot<STORAGE, STATE, PROCESSOR, EXCHANGE, INTERNAL_PAGE_SIZE> | ||||
| { | ||||
| impl<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> MoonbootBoot<CONTEXT, INTERNAL_PAGE_SIZE> { | ||||
|     /// create a new instance of the bootloader
 | ||||
|     pub fn new( | ||||
|         config: Config, | ||||
|         storage: STORAGE, | ||||
|         state: STATE, | ||||
|         processor: PROCESSOR, | ||||
|         exchange: EXCHANGE, | ||||
|     ) -> MoonbootBoot<STORAGE, STATE, PROCESSOR, EXCHANGE, INTERNAL_PAGE_SIZE> { | ||||
|         storage: CONTEXT::Storage, | ||||
|         state: CONTEXT::State, | ||||
|         processor: CONTEXT::Processor, | ||||
|         exchange: CONTEXT::Exchange, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             config, | ||||
|             storage, | ||||
|  | @ -53,12 +39,12 @@ impl< | |||
|     } | ||||
| 
 | ||||
|     /// Destroy this instance of the bootloader and return access to the hardware peripheral
 | ||||
|     pub fn destroy(self) -> (STORAGE, STATE, 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<void::Void, STATE::Error> { | ||||
|     pub fn boot(&mut self) -> Result<void::Void, <CONTEXT::State as State>::Error> { | ||||
|         // TODO: consider error handling
 | ||||
|         log::info!("Booting with moonboot!"); | ||||
| 
 | ||||
|  | @ -116,19 +102,20 @@ impl< | |||
| 
 | ||||
|     // Handle a case of power interruption or similar, which lead to a exchange_banks being
 | ||||
|     // interrupted.
 | ||||
|     fn handle_exchanging(&mut self, progress: ExchangeProgress) -> Result<Update, STATE::Error> { | ||||
|     fn handle_exchanging( | ||||
|         &mut self, | ||||
|         progress: ExchangeProgress, | ||||
|     ) -> Result<Update, <CONTEXT::State as State>::Error> { | ||||
|         log::error!( | ||||
|             "Firmware Update was interrupted! Trying to recover with exchange operation: {:?}", | ||||
|             progress | ||||
|         ); | ||||
| 
 | ||||
|         let exchange_result = self | ||||
|             .exchange | ||||
|             .exchange::<STORAGE, STATE, INTERNAL_PAGE_SIZE>( | ||||
|                 &mut self.storage, | ||||
|                 &mut self.state, | ||||
|                 progress, | ||||
|             ); | ||||
|         let exchange_result = self.exchange.exchange::<INTERNAL_PAGE_SIZE>( | ||||
|             &mut self.storage, | ||||
|             &mut self.state, | ||||
|             progress, | ||||
|         ); | ||||
| 
 | ||||
|         Ok(if exchange_result.is_ok() { | ||||
|             let state = self.state.read()?.update; | ||||
|  | @ -168,7 +155,17 @@ impl< | |||
|             ); | ||||
| 
 | ||||
|             // Try to exchange the firmware images
 | ||||
|             let exchange_result = self.exchange_banks(new, old); | ||||
|             let exchange_result = self.exchange.exchange::<INTERNAL_PAGE_SIZE>( | ||||
|                 &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
 | ||||
|  | @ -193,25 +190,6 @@ impl< | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn exchange_banks( | ||||
|         &mut self, | ||||
|         a: Bank, | ||||
|         b: Bank, | ||||
|     ) -> Result<(), ExchangeError<STORAGE::Error, STATE::Error, EXCHANGE::OtherError>> { | ||||
|         self.exchange | ||||
|             .exchange::<STORAGE, STATE, INTERNAL_PAGE_SIZE>( | ||||
|                 &mut self.storage, | ||||
|                 &mut self.state, | ||||
|                 ExchangeProgress { | ||||
|                     a, | ||||
|                     b, | ||||
|                     page_index: 0, | ||||
|                     recovering: false, | ||||
|                     step: ExchangeStep::AToScratch, | ||||
|                 }, | ||||
|             ) | ||||
|     } | ||||
| 
 | ||||
|     // Jump to the firmware image marked as bootable
 | ||||
|     fn jump_to_firmware(&mut self) -> ! { | ||||
|         let app_exec_image = self.config.boot_bank; | ||||
|  |  | |||
|  | @ -45,6 +45,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: state::Exchange<Self::Storage, Self::State>; | ||||
| } | ||||
| 
 | ||||
| #[cfg(not(any(feature = "use-log", feature = "use-defmt")))] | ||||
| pub(crate) mod log { | ||||
|     macro_rules! info { | ||||
|  |  | |||
|  | @ -1,24 +1,18 @@ | |||
| use crate::{ | ||||
|     hardware::{processor::Processor, Config}, | ||||
|     state::{State, Update}, | ||||
|     Context, | ||||
| }; | ||||
| 
 | ||||
| use embedded_storage::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< | ||||
|     STORAGE: Storage, | ||||
|     STATE: State, | ||||
|     PROCESSOR: Processor, | ||||
|     const INTERNAL_PAGE_SIZE: usize, | ||||
| > { | ||||
| pub struct MoonbootManager<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> { | ||||
|     config: Config, | ||||
|     storage: STORAGE, | ||||
|     state: STATE, | ||||
|     processor: PROCESSOR, | ||||
|     storage: CONTEXT::Storage, | ||||
|     state: CONTEXT::State, | ||||
|     processor: CONTEXT::Processor, | ||||
| } | ||||
| 
 | ||||
| pub struct InitError; | ||||
|  | @ -28,24 +22,29 @@ pub enum MarkError<E> { | |||
|     State(E), | ||||
| } | ||||
| 
 | ||||
| impl<STORAGE: Storage, STATE: State, PROCESSOR: Processor, const INTERNAL_PAGE_SIZE: usize> | ||||
|     MoonbootManager<STORAGE, STATE, PROCESSOR, INTERNAL_PAGE_SIZE> | ||||
| impl<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> | ||||
|     MoonbootManager<CONTEXT, INTERNAL_PAGE_SIZE> | ||||
| { | ||||
|     pub fn new( | ||||
|         config: Config, | ||||
|         storage: STORAGE, | ||||
|         state: STATE, | ||||
|         processor: PROCESSOR, | ||||
|     ) -> Result<MoonbootManager<STORAGE, STATE, PROCESSOR, INTERNAL_PAGE_SIZE>, InitError> { | ||||
|         storage: CONTEXT::Storage, | ||||
|         state: CONTEXT::State, | ||||
|         processor: CONTEXT::Processor, | ||||
|     ) -> Result<Self, InitError> { | ||||
|         if config.update_bank.size > config.boot_bank.size { | ||||
|             log::error!( | ||||
|                 "Requested update bank {:?} is larger than boot bank {:?}", | ||||
|                 bank, | ||||
|                 self.config.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, | ||||
|             storage, | ||||
|  | @ -55,14 +54,16 @@ impl<STORAGE: Storage, STATE: State, PROCESSOR: Processor, const INTERNAL_PAGE_S | |||
|     } | ||||
| 
 | ||||
|     /// Destroy this instance of the boot manager and return access to the hardware peripheral
 | ||||
|     pub fn destroy(self) -> (STORAGE, STATE, 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<(), MarkError<STATE::Error>> { | ||||
|     pub fn mark_boot_successful( | ||||
|         &mut self, | ||||
|     ) -> Result<(), MarkError<<CONTEXT::State as State>::Error>> { | ||||
|         let mut current_state = self.state.read().map_err(MarkError::State)?; | ||||
| 
 | ||||
|         log::info!( | ||||
|  | @ -92,7 +93,7 @@ impl<STORAGE: Storage, STATE: State, PROCESSOR: Processor, const INTERNAL_PAGE_S | |||
| 
 | ||||
|     // 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<void::Void, STATE::Error> { | ||||
|     pub fn update(&mut self) -> Result<void::Void, <CONTEXT::State as State>::Error> { | ||||
|         // Apply the update stored in the update bank
 | ||||
|         let bank = self.config.update_bank; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,28 +4,11 @@ pub mod ram; | |||
| #[cfg(feature = "scratch-state")] | ||||
| pub mod scratch; | ||||
| 
 | ||||
| use embedded_storage::Storage; | ||||
| 
 | ||||
| pub enum ExchangeError<STORAGE, STATE, OTHER> { | ||||
|     Storage(STORAGE), | ||||
|     State(STATE), | ||||
|     Other(OTHER), | ||||
| } | ||||
| 
 | ||||
| /// Abstraction for the exchange operation of the current state.
 | ||||
| pub trait Exchange { | ||||
|     type OtherError; | ||||
| 
 | ||||
|     fn exchange<STORAGE: Storage, STATE: State, const INTERNAL_PAGE_SIZE: usize>( | ||||
|         &mut self, | ||||
|         internal_memory: &mut STORAGE, | ||||
|         state: &mut STATE, | ||||
|         progress: ExchangeProgress, | ||||
|     ) -> Result<(), ExchangeError<STORAGE::Error, STATE::Error, Self::OtherError>>; | ||||
| } | ||||
| 
 | ||||
| use crate::hardware::Bank; | ||||
| 
 | ||||
| use core::fmt::Debug; | ||||
| use embedded_storage::Storage; | ||||
| 
 | ||||
| use crc::{Crc, CRC_32_CKSUM}; | ||||
| #[cfg(feature = "defmt")] | ||||
| use defmt::Format; | ||||
|  | @ -34,6 +17,18 @@ use desse::{Desse, DesseSized}; | |||
| #[cfg(feature = "serde")] | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| /// Abstraction for the exchange operation of the current state.
 | ||||
| pub trait Exchange<STORAGE: Storage, STATE: State> { | ||||
|     type Error: Debug; | ||||
| 
 | ||||
|     fn exchange<const INTERNAL_PAGE_SIZE: usize>( | ||||
|         &mut self, | ||||
|         storage: &mut STORAGE, | ||||
|         state: &mut STATE, | ||||
|         progress: ExchangeProgress, | ||||
|     ) -> Result<(), Self::Error>; | ||||
| } | ||||
| 
 | ||||
| /// Decision making states for the bootloader
 | ||||
| // TODO: Hash + Signature? Should be done on download I think! This way, the algorithms can be
 | ||||
| // exchanged via software updates easily
 | ||||
|  |  | |||
|  | @ -58,15 +58,37 @@ impl State for RamState { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Exchange for RamState { | ||||
|     type OtherError = void::Void; | ||||
| pub enum ExchangeError<STORAGE, STATE> { | ||||
|     Storage(STORAGE), | ||||
|     State(STATE), | ||||
| } | ||||
| 
 | ||||
|     fn exchange<STORAGE: Storage, STATE: State, const INTERNAL_PAGE_SIZE: usize>( | ||||
| impl<STORAGE, STATE> Debug for ExchangeError<STORAGE, STATE> | ||||
| 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(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<STORAGE: Storage, STATE: State> Exchange<STORAGE, STATE> for RamState | ||||
| where | ||||
|     STORAGE::Error: Debug, | ||||
|     STATE::Error: Debug, | ||||
| { | ||||
|     type Error = ExchangeError<STORAGE::Error, STATE::Error>; | ||||
| 
 | ||||
|     fn exchange<const INTERNAL_PAGE_SIZE: usize>( | ||||
|         &mut self, | ||||
|         storage: &mut STORAGE, | ||||
|         state: &mut STATE, | ||||
|         progress: ExchangeProgress, | ||||
|     ) -> Result<(), ExchangeError<STORAGE::Error, STATE::Error, Self::OtherError>> { | ||||
|     ) -> Result<(), Self::Error> { | ||||
|         let ExchangeProgress { | ||||
|             a, | ||||
|             b, | ||||
|  | @ -75,6 +97,10 @@ impl Exchange for RamState { | |||
|             .. | ||||
|         } = 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; | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| use core::ops::Range; | ||||
| use core::{fmt::Debug, ops::Range}; | ||||
| 
 | ||||
| use embedded_storage::Storage; | ||||
| 
 | ||||
| use crate::{ | ||||
|     log, | ||||
|     state::{ExchangeProgress, ExchangeStep, State, Update}, | ||||
|     swap::{MemoryError, Swap}, | ||||
|     state::{Exchange, ExchangeProgress, ExchangeStep, State, Update}, | ||||
|     Address, | ||||
| }; | ||||
| 
 | ||||
|  | @ -13,29 +12,48 @@ pub struct Scratch<'a> { | |||
|     pub pages: &'a [Range<Address>], | ||||
| } | ||||
| 
 | ||||
| impl<'a> Swap for Scratch<'a> { | ||||
|     fn exchange<InternalMemory: Storage, HardwareState: State, const INTERNAL_PAGE_SIZE: usize>( | ||||
| pub enum ExchangeError<STORAGE, STATE> { | ||||
|     Storage(STORAGE), | ||||
|     State(STATE), | ||||
| } | ||||
| 
 | ||||
| impl<STORAGE, STATE> Debug for ExchangeError<STORAGE, STATE> | ||||
| 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(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a, STORAGE: Storage, STATE: State> Exchange<STORAGE, STATE> for Scratch<'a> | ||||
| where | ||||
|     STORAGE::Error: Debug, | ||||
|     STATE::Error: Debug, | ||||
| { | ||||
|     type Error = ExchangeError<STORAGE::Error, STATE::Error>; | ||||
| 
 | ||||
|     fn exchange<const INTERNAL_PAGE_SIZE: usize>( | ||||
|         &mut self, | ||||
|         internal_memory: &mut InternalMemory, | ||||
|         state: &mut HardwareState, | ||||
|         exchange: ExchangeProgress, | ||||
|     ) -> Result<(), MemoryError> { | ||||
|         storage: &mut STORAGE, | ||||
|         state: &mut STATE, | ||||
|         progress: ExchangeProgress, | ||||
|     ) -> Result<(), Self::Error> { | ||||
|         let ExchangeProgress { | ||||
|             a, | ||||
|             b, | ||||
|             page_index, | ||||
|             mut step, | ||||
|             .. | ||||
|         } = exchange; | ||||
|         } = progress; | ||||
| 
 | ||||
|         // 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); | ||||
|         } | ||||
|         assert_eq!(a.size, b.size); | ||||
|         assert_ne!(a.size, 0); | ||||
|         assert_ne!(b.size, 0); | ||||
| 
 | ||||
|         let size = a.size; // Both are equal
 | ||||
| 
 | ||||
|  | @ -46,7 +64,7 @@ impl<'a> Swap for Scratch<'a> { | |||
| 
 | ||||
|         let mut ram_buf = [0_u8; INTERNAL_PAGE_SIZE]; | ||||
| 
 | ||||
|         let mut last_state = state.read(); | ||||
|         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
 | ||||
|  | @ -85,37 +103,35 @@ impl<'a> Swap for Scratch<'a> { | |||
|                         page_index, | ||||
|                         step, | ||||
|                     }); | ||||
|                     state | ||||
|                         .write(&last_state) | ||||
|                         .map_err(|_| MemoryError::WriteFailure)?; | ||||
|                     state.write(&last_state).map_err(ExchangeError::State)?; | ||||
|                 } | ||||
| 
 | ||||
|                 match step { | ||||
|                     ExchangeStep::AToScratch => { | ||||
|                         internal_memory | ||||
|                         storage | ||||
|                             .read(a_location, &mut ram_buf) | ||||
|                             .map_err(|_| MemoryError::ReadFailure)?; | ||||
|                         internal_memory | ||||
|                             .map_err(ExchangeError::Storage)?; | ||||
|                         storage | ||||
|                             .write(scratch_location, &ram_buf) | ||||
|                             .map_err(|_| MemoryError::WriteFailure)?; | ||||
|                             .map_err(ExchangeError::Storage)?; | ||||
|                         step = ExchangeStep::BToA; | ||||
|                     } | ||||
|                     ExchangeStep::BToA => { | ||||
|                         internal_memory | ||||
|                         storage | ||||
|                             .read(b_location, &mut ram_buf) | ||||
|                             .map_err(|_| MemoryError::ReadFailure)?; | ||||
|                         internal_memory | ||||
|                             .map_err(ExchangeError::Storage)?; | ||||
|                         storage | ||||
|                             .write(a_location, &ram_buf) | ||||
|                             .map_err(|_| MemoryError::WriteFailure)?; | ||||
|                             .map_err(ExchangeError::Storage)?; | ||||
|                         step = ExchangeStep::ScratchToB; | ||||
|                     } | ||||
|                     ExchangeStep::ScratchToB => { | ||||
|                         internal_memory | ||||
|                         storage | ||||
|                             .read(scratch_location, &mut ram_buf) | ||||
|                             .map_err(|_| MemoryError::ReadFailure)?; | ||||
|                         internal_memory | ||||
|                             .map_err(ExchangeError::Storage)?; | ||||
|                         storage | ||||
|                             .write(b_location, &ram_buf) | ||||
|                             .map_err(|_| MemoryError::WriteFailure)?; | ||||
|                             .map_err(ExchangeError::Storage)?; | ||||
|                         step = ExchangeStep::AToScratch; | ||||
|                         break; | ||||
|                     } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue