use crate::hardware::Bank; use crate::log; use crc::{Crc, CRC_32_CKSUM}; #[cfg(feature = "defmt")] use defmt::Format; #[cfg(feature = "ram-state")] use desse::{Desse, DesseSized}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; // Decision making for the bootloader // TODO: Hash + Signature? Should be done on download I think! This way, the algorithms can be // 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))] #[derive(Debug, PartialEq, Eq)] pub enum Update { // No update requested, just jump to the application None, // Exchange the current boot image with the one from image specified as index, a signature to // verify against and the size of the firmware image to make the firmware signature // verification succeed Request(Bank), // Revert the current boot image to the from image specified as index Revert(Bank), // An Exchange Operation is in Progress or was interrupted Exchanging(ExchangeProgress), // An Error during the update has occured! Error(UpdateError), } #[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] // Errors that can occur during update pub enum UpdateError { // A wrong Image Index has been specified InvalidImageIndex, // Failed to exchange the new image with the old one ImageExchangeFailed, // Something f'ed up the internal state InvalidState, // The Signature provided does not match the PublicKey or Image. InvalidSignature, } // 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))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExchangeProgress { // Bank the update is coming from pub(crate) a: Bank, // Bank the update is going to pub(crate) b: Bank, // Page the operation has last copied pub(crate) page_index: u32, // Whether this exchange resulted from a Request (false) or a Revert (true) pub(crate) recovering: bool, } // 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))] #[derive(Debug)] pub struct MoonshineState { // If set Request, an Update is requested. This will exchange the two images, set the update // state to Revert and start the application. The application then has to set this state to // None and store it. If something went wrong and the boot attempt results in a restart of // the bootloader, the bootloader starts with this variable set to Revert and thus exchanges // the two images again, doing a downgrade because of a failed boot pub update: Update, } pub trait State { fn read(&mut self) -> MoonshineState; fn write(&mut self, data: MoonshineState) -> Result<(), ()>; } pub const STATE_SERIALIZED_MAX_SIZE: usize = MoonshineState::SIZE; pub type StateCrcType = u32; 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 _moonshine_state_crc_start: StateCrcType; static mut _moonshine_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) -> MoonshineState { let crc = unsafe { _moonshine_state_crc_start }; log::info!( "Reading data with len: {}, CRC: {}", STATE_SERIALIZED_MAX_SIZE, crc ); let checksum = checksum(unsafe { &_moonshine_state_data_start }); if crc == checksum { let data = MoonshineState::deserialize_from(unsafe { &_moonshine_state_data_start }); log::trace!("CRC Match! {}: {:?}", crc, data); return data; } else { log::trace!("CRC Mismatch! {} vs {}", crc, checksum); } MoonshineState { update: Update::None, } } fn write(&mut self, data: MoonshineState) -> Result<(), ()> { log::trace!("Writing data {:?}", data); unsafe { _moonshine_state_data_start = data.serialize() }; log::trace!("Written data: {:?}", unsafe { &_moonshine_state_data_start }); unsafe { _moonshine_state_crc_start = checksum(&_moonshine_state_data_start); } log::info!( "Written len: {}, checksum: {}", STATE_SERIALIZED_MAX_SIZE, unsafe { _moonshine_state_crc_start } ); Ok(()) } } }