diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index f641d5e1c..415d7960f 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml @@ -27,9 +27,10 @@ defmt = { version = "0.3", optional = true } digest = "0.10" log = { version = "0.4", optional = true } ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } embedded-storage = "0.3.0" -embedded-storage-async = { version = "0.4.0", optional = true} +embedded-storage-async = { version = "0.4.0", optional = true } salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } signature = { version = "1.6.4", default-features = false } @@ -39,6 +40,7 @@ env_logger = "0.9" rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" +critical-section = { version = "1.1.1", features = ["std"] } [dev-dependencies.ed25519-dalek] default_features = false @@ -48,7 +50,7 @@ features = ["rand", "std", "u32_backend"] ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ed25519-salty = ["dep:salty", "_verify"] -nightly = ["dep:embedded-storage-async"] +nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"] #Internal features _verify = [] diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index b959de2c4..a8c19197b 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -1,6 +1,11 @@ -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use core::cell::RefCell; -use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +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, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -30,63 +35,96 @@ where } } -/// Trait defining the flash handles used for active and DFU partition. -pub trait FlashConfig { - /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value. - const STATE_ERASE_VALUE: u8 = 0xFF; +/// 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. - type STATE: NorFlash; - /// Flash type used for the active partition. - type ACTIVE: NorFlash; - /// Flash type used for the dfu partition. - type DFU: NorFlash; - - /// Return flash instance used to write/read to/from active partition. - fn active(&mut self) -> &mut Self::ACTIVE; - /// Return flash instance used to write/read to/from dfu partition. - fn dfu(&mut self) -> &mut Self::DFU; - /// Return flash instance used to write/read to/from bootloader state. - fn state(&mut self) -> &mut Self::STATE; + pub state: STATE, } -trait FlashConfigEx { - fn page_size() -> u32; -} +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; + } -impl FlashConfigEx for T { - /// Get the page size which is the "unit of operation" within the bootloader. - fn page_size() -> u32 { - core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as 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 { - // Page with current state of bootloader. 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: Partition, - // Location of the partition which will be booted from - active: Partition, - // Location of the partition which will be swapped in when requested - dfu: Partition, +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 { - /// Create a new instance of a bootloader with the given partitions. +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(active: Partition, dfu: Partition, state: Partition) -> Self { - Self { active, dfu, state } - } - - /// Return the offset of the active partition into the active flash. - pub fn boot_address(&self) -> usize { - self.active.from as usize + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } } /// Perform necessary boot preparations like swapping images. @@ -175,195 +213,174 @@ impl BootLoader { /// | DFU | 3 | 3 | 2 | 1 | 3 | /// +-----------+--------------+--------+--------+--------+--------+ /// - pub fn prepare_boot(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { // Ensure we have enough progress pages to store copy progress - assert_eq!(0, P::page_size() % aligned_buf.len() as u32); - assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32); - assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32); - assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32); - assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32); - assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE); - assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); + 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); + + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); // Copy contents from partition N to active - let state = self.read_state(p, aligned_buf)?; + 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(p, aligned_buf)? { + if !self.is_swapped(aligned_buf)? { trace!("Swapping"); - self.swap(p, aligned_buf)?; + self.swap(aligned_buf)?; trace!("Swapping done"); } else { trace!("Reverting"); - self.revert(p, aligned_buf)?; + self.revert(aligned_buf)?; - let state_flash = p.state(); - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; // Invalidate progress - state_word.fill(!P::STATE_ERASE_VALUE); - self.state - .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.erase(0, self.state.capacity() as u32)?; // Set magic state_word.fill(BOOT_MAGIC); - self.state.write_blocking(state_flash, 0, state_word)?; + self.state.write(0, state_word)?; } } Ok(state) } - fn is_swapped(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { - let page_count = (self.active.size() / P::page_size()) as usize; - let progress = self.current_progress(p, aligned_buf)?; + 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, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let write_size = P::STATE::WRITE_SIZE as u32; - let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize; - let state_flash = config.state(); + 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_blocking(state_flash, write_size, state_word)?; - if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) { + 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_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?; + self.state.read((2 + index) as u32 * write_size, state_word)?; - if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) { + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { return Ok(index); } } Ok(max_index) } - fn update_progress( - &mut self, - progress_index: usize, - p: &mut P, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - state_word.fill(!P::STATE_ERASE_VALUE); - self.state.write_blocking( - p.state(), - (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32, - state_word, - )?; + 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( + fn copy_page_once_to_active( &mut self, progress_index: usize, from_offset: u32, to_offset: u32, - p: &mut P, aligned_buf: &mut [u8], ) -> Result<(), BootError> { - if self.current_progress(p, aligned_buf)? <= progress_index { - let page_size = P::page_size() as u32; + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; - self.active - .erase_blocking(p.active(), to_offset, to_offset + page_size)?; + 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_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?; - self.active - .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?; + 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, p, aligned_buf)?; + self.update_progress(progress_index, aligned_buf)?; } Ok(()) } - fn copy_page_once_to_dfu( + fn copy_page_once_to_dfu( &mut self, progress_index: usize, from_offset: u32, to_offset: u32, - p: &mut P, aligned_buf: &mut [u8], ) -> Result<(), BootError> { - if self.current_progress(p, aligned_buf)? <= progress_index { - let page_size = P::page_size() as u32; + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; - self.dfu - .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?; + 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_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?; - self.dfu - .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?; + 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, p, aligned_buf)?; + self.update_progress(progress_index, aligned_buf)?; } Ok(()) } - fn swap(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_size = P::page_size(); - let page_count = self.active.size() / page_size; + 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) * page_size; - let dfu_to_offset = (page_count - page_num) * page_size; + 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, p, aligned_buf)?; + 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) * page_size; - let dfu_from_offset = (page_count - 1 - page_num) * page_size; + 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, p, aligned_buf)?; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; } Ok(()) } - fn revert(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_size = P::page_size(); - let page_count = self.active.size() / page_size; + 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 * page_size; - let dfu_to_offset = page_num * page_size; - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; + 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 * page_size; - let dfu_from_offset = (page_num + 1) * page_size; - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; + 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, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - self.state.read_blocking(config.state(), 0, state_word)?; + 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) @@ -373,161 +390,32 @@ impl BootLoader { } } -fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) { - assert_eq!(active.size() % page_size, 0); - assert_eq!(dfu.size() % page_size, 0); - assert!(dfu.size() - active.size() >= page_size); - assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32); -} - -/// A flash wrapper implementing the Flash and embedded_storage traits. -pub struct BootFlash -where - F: NorFlash, -{ - flash: F, -} - -impl BootFlash -where - F: NorFlash, -{ - /// Create a new instance of a bootable flash - pub fn new(flash: F) -> Self { - Self { flash } - } -} - -impl ErrorType for BootFlash -where - F: NorFlash, -{ - type Error = F::Error; -} - -impl NorFlash for BootFlash -where - F: NorFlash, -{ - const WRITE_SIZE: usize = F::WRITE_SIZE; - const ERASE_SIZE: usize = F::ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - F::erase(&mut self.flash, from, to) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - F::write(&mut self.flash, offset, bytes) - } -} - -impl ReadNorFlash for BootFlash -where - F: NorFlash, -{ - const READ_SIZE: usize = F::READ_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - F::read(&mut self.flash, offset, bytes) - } - - fn capacity(&self) -> usize { - F::capacity(&self.flash) - } -} - -/// Convenience provider that uses a single flash for all partitions. -pub struct SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - flash: &'a mut F, -} - -impl<'a, F> SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - /// Create a provider for a single flash. - pub fn new(flash: &'a mut F) -> Self { - Self { flash } - } -} - -impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - type STATE = F; - type ACTIVE = F; - type DFU = F; - - fn active(&mut self) -> &mut Self::STATE { - self.flash - } - fn dfu(&mut self) -> &mut Self::ACTIVE { - self.flash - } - fn state(&mut self) -> &mut Self::DFU { - self.flash - } -} - -/// Convenience flash provider that uses separate flash instances for each partition. -pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - active: &'a mut ACTIVE, - state: &'a mut STATE, - dfu: &'a mut DFU, -} - -impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - /// Create a new flash provider with separate configuration for all three partitions. - pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { - Self { active, state, dfu } - } -} - -impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - type STATE = STATE; - type ACTIVE = ACTIVE; - type DFU = DFU; - - fn active(&mut self) -> &mut Self::ACTIVE { - self.active - } - fn dfu(&mut self) -> &mut Self::DFU { - self.dfu - } - fn state(&mut self) -> &mut Self::STATE { - self.state - } +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); + 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: Partition = Partition::new(4096, 4194304); - const DFU: Partition = Partition::new(4194304, 2 * 4194304); - const STATE: Partition = Partition::new(0, 4096); - assert_partitions(ACTIVE, DFU, STATE, 4096, 4); + 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/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index bdd03bff4..0b3f88313 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -1,20 +1,68 @@ use digest::Digest; -use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; +#[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 crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_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 { + dfu: DFU, + state: 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 FirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig) -> Self { + Self { + dfu: config.dfu, + state: config.state, + } + } -impl FirmwareUpdater { /// 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, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read(state_flash, 0, aligned).await?; + pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result { + self.state.read(0, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -37,19 +85,18 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// 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. - #[cfg(all(feature = "_verify", feature = "nightly"))] - pub async fn verify_and_mark_updated( + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( &mut self, - _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.size()); + assert_eq!(_aligned.len(), STATE::WRITE_SIZE); + assert!(_update_len <= self.dfu.capacity() as u32); #[cfg(feature = "ed25519-dalek")] { @@ -63,8 +110,7 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) - .await?; + self.hash::(_update_len, _aligned, &mut message).await?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -85,8 +131,7 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) - .await?; + self.hash::(_update_len, _aligned, &mut message).await?; let r = public_key.verify(&message, &signature); trace!( @@ -99,20 +144,19 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await + self.set_magic(_aligned, SWAP_MAGIC).await } /// Verify the update in DFU with any digest. - pub async fn hash( + pub async fn hash( &mut self, - dfu_flash: &mut F, 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(dfu_flash, offset, chunk_buf).await?; + 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]); } @@ -124,60 +168,44 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(all(feature = "nightly", not(feature = "_verify")))] - pub async fn mark_updated( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, SWAP_MAGIC, state_flash).await + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC).await } /// Mark firmware boot successful and stop rollback on reset. /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub async fn mark_booted( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, BOOT_MAGIC, state_flash).await + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC).await } - async fn set_magic( - &mut self, - aligned: &mut [u8], - magic: u8, - state_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - self.state.read(state_flash, 0, aligned).await?; + async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, aligned).await?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; - - // FIXME: Do not make this assumption. - const STATE_ERASE_VALUE: u8 = 0xFF; + self.state.read(STATE::WRITE_SIZE as u32, aligned).await?; if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { // The current progress validity marker is invalid } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; + self.state.write(STATE::WRITE_SIZE as u32, aligned).await?; } // Clear magic and progress - self.state.wipe(state_flash).await?; + self.state.erase(0, self.state.capacity() as u32).await?; // Set magic aligned.fill(magic); - self.state.write(state_flash, 0, aligned).await?; + self.state.write(0, aligned).await?; } Ok(()) } @@ -189,19 +217,12 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); + pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); - self.dfu - .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) - .await?; + self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; - self.dfu.write(dfu_flash, offset as u32, data).await?; + self.dfu.write(offset as u32, data).await?; Ok(()) } @@ -211,18 +232,18 @@ impl FirmwareUpdater { /// /// Using this instead of `write_firmware` allows for an optimized API in /// exchange for added complexity. - pub async fn prepare_update( - &mut self, - dfu_flash: &mut F, - ) -> Result { - self.dfu.wipe(dfu_flash).await?; + pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.dfu.erase(0, self.dfu.capacity() as u32).await?; - Ok(self.dfu) + Ok(&mut self.dfu) } } #[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}; @@ -231,20 +252,19 @@ mod tests { #[test] fn can_verify_sha1() { - const STATE: Partition = Partition::new(0, 4096); - const DFU: Partition = Partition::new(65536, 131072); - - let mut flash = MemFlash::<131072, 4096, 8>::default(); + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); 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(DFU, STATE); - block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); + 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::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + 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 index 50caaf08c..551150c4f 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -1,25 +1,70 @@ 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 crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_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 { + dfu: DFU, + state: 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) + }; -impl FirmwareUpdater { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - pub const fn new(dfu: Partition, state: Partition) -> Self { Self { dfu, state } } +} + +impl BlockingFirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig) -> Self { + Self { + dfu: config.dfu, + state: config.state, + } + } /// 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_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read_blocking(state_flash, 0, aligned)?; + pub fn get_state(&mut self, aligned: &mut [u8]) -> Result { + self.state.read(0, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -42,19 +87,18 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// 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. #[cfg(feature = "_verify")] - pub fn verify_and_mark_updated_blocking( + pub fn verify_and_mark_updated( &mut self, - _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.size()); + assert_eq!(_aligned.len(), STATE::WRITE_SIZE); + assert!(_update_len <= self.dfu.capacity() as u32); #[cfg(feature = "ed25519-dalek")] { @@ -68,7 +112,7 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; + self.hash::(_update_len, _aligned, &mut message)?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -89,7 +133,7 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; + self.hash::(_update_len, _aligned, &mut message)?; let r = public_key.verify(&message, &signature); trace!( @@ -102,20 +146,19 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) + self.set_magic(_aligned, SWAP_MAGIC) } /// Verify the update in DFU with any digest. - pub fn hash_blocking( + pub fn hash( &mut self, - dfu_flash: &mut F, 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_blocking(dfu_flash, offset, chunk_buf)?; + self.dfu.read(offset, chunk_buf)?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -127,60 +170,44 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. #[cfg(not(feature = "_verify"))] - pub fn mark_updated_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) + pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC) } /// Mark firmware boot successful and stop rollback on reset. /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub fn mark_booted_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC) } - fn set_magic_blocking( - &mut self, - aligned: &mut [u8], - magic: u8, - state_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - self.state.read_blocking(state_flash, 0, aligned)?; + fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, aligned)?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; - - // FIXME: Do not make this assumption. - const STATE_ERASE_VALUE: u8 = 0xFF; + self.state.read(STATE::WRITE_SIZE as u32, aligned)?; if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { // The current progress validity marker is invalid } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + self.state.write(STATE::WRITE_SIZE as u32, aligned)?; } // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.erase(0, self.state.capacity() as u32)?; // Set magic aligned.fill(magic); - self.state.write_blocking(state_flash, 0, aligned)?; + self.state.write(0, aligned)?; } Ok(()) } @@ -192,18 +219,12 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); + pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); - self.dfu - .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; + self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; - self.dfu.write_blocking(dfu_flash, offset as u32, data)?; + self.dfu.write(offset as u32, data)?; Ok(()) } @@ -211,11 +232,45 @@ impl FirmwareUpdater { /// Prepare for an incoming DFU update by erasing the entire DFU area and /// returning its `Partition`. /// - /// Using this instead of `write_firmware_blocking` allows for an optimized - /// API in exchange for added complexity. - pub fn prepare_update_blocking(&mut self, flash: &mut F) -> Result { - self.dfu.wipe_blocking(flash)?; + /// 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.dfu.erase(0, self.dfu.capacity() as u32)?; - Ok(self.dfu) + Ok(&mut self.dfu) + } +} + +#[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 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 }); + 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 index e09f5eb6c..a37984a3a 100644 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -2,9 +2,22 @@ mod asynch; mod blocking; +#[cfg(feature = "nightly")] +pub use asynch::FirmwareUpdater; +pub use blocking::BlockingFirmwareUpdater; use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; -use crate::Partition; +/// 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)] @@ -33,39 +46,3 @@ where FirmwareUpdaterError::Flash(error.kind()) } } - -/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct FirmwareUpdater { - state: Partition, - dfu: Partition, -} - -#[cfg(target_os = "none")] -impl Default for FirmwareUpdater { - fn default() -> 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 { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - FirmwareUpdater::new(dfu, state) - } -} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4521fecb0..45a87bd0e 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -7,12 +7,18 @@ mod fmt; mod boot_loader; mod digest_adapters; mod firmware_updater; +#[cfg(test)] mod mem_flash; -mod partition; +#[cfg(test)] +mod test_flash; -pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; -pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; -pub use partition::Partition; +// 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}; +#[cfg(feature = "nightly")] +pub use firmware_updater::FirmwareUpdater; +pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; @@ -45,10 +51,18 @@ impl AsMut<[u8]> for AlignedBuffer { #[cfg(test)] mod tests { + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + #[cfg(feature = "nightly")] + 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; + #[cfg(feature = "nightly")] + use crate::test_flash::AsyncTestFlash; + use crate::test_flash::BlockingTestFlash; /* #[test] @@ -67,147 +81,173 @@ mod tests { #[test] fn test_boot_state() { - const STATE: Partition = Partition::new(0, 4096); - const ACTIVE: Partition = Partition::new(4096, 61440); - const DFU: Partition = Partition::new(61440, 122880); + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<57344, 4096, 4>::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); - let mut flash = MemFlash::<131072, 4096, 4>::default(); - flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); - let mut flash = SingleFlashConfig::new(&mut flash); + flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); + 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 flash, &mut page).unwrap()); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); } #[test] #[cfg(all(feature = "nightly", not(feature = "_verify")))] fn test_swap_state() { - const STATE: Partition = Partition::new(0, 4096); - const ACTIVE: Partition = Partition::new(4096, 61440); - const DFU: Partition = Partition::new(61440, 122880); - let mut flash = MemFlash::<131072, 4096, 4>::random(); + 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(), + }); - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - flash.program(ACTIVE.from, &original).unwrap(); + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); - let mut updater = FirmwareUpdater::new(DFU, STATE); - block_on(updater.write_firmware(0, &update, &mut flash)).unwrap(); - block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).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; 1024]; - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - flash.assert_eq(ACTIVE.from, &update); + 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.assert_eq(DFU.from + 4096, &original); + 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 SingleFlashConfig::new(&mut flash), &mut page) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - flash.assert_eq(ACTIVE.from, &original); - // Last page is untouched - flash.assert_eq(DFU.from, &update); + 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 - block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap(); - assert_eq!( - State::Boot, - bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) - .unwrap() - ); + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.mark_booted(&mut aligned)).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(all(feature = "nightly", not(feature = "_verify")))] - fn test_separate_flash_active_page_biggest() { - const STATE: Partition = Partition::new(2048, 4096); - const ACTIVE: Partition = Partition::new(4096, 16384); - const DFU: Partition = Partition::new(0, 16384); + 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(), + }); - let mut active = MemFlash::<16384, 4096, 8>::random(); - let mut dfu = MemFlash::<16384, 2048, 8>::random(); - let mut state = MemFlash::<4096, 128, 4>::random(); + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - active.program(ACTIVE.from, &original).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - let mut updater = FirmwareUpdater::new(DFU, STATE); + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); - block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap(); - block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); - - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page) - .unwrap() - ); - - active.assert_eq(ACTIVE.from, &update); + 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 - dfu.assert_eq(DFU.from + 4096, &original); + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); } #[test] #[cfg(all(feature = "nightly", not(feature = "_verify")))] - fn test_separate_flash_dfu_page_biggest() { - const STATE: Partition = Partition::new(2048, 4096); - const ACTIVE: Partition = Partition::new(4096, 16384); - const DFU: Partition = Partition::new(0, 16384); + 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]; - let mut active = MemFlash::<16384, 2048, 4>::random(); - let mut dfu = MemFlash::<16384, 4096, 8>::random(); - let mut state = MemFlash::<4096, 128, 4>::random(); - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - active.program(ACTIVE.from, &original).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - let mut updater = FirmwareUpdater::new(DFU, STATE); - - block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap(); - block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); - - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); + 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 MultiFlashConfig::new(&mut active, &mut state, &mut dfu,), - &mut page - ) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - active.assert_eq(ACTIVE.from, &update); + 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 - dfu.assert_eq(DFU.from + 4096, &original); + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); } #[test] @@ -233,25 +273,28 @@ mod tests { let public_key: PublicKey = keypair.public; // Setup flash - - const STATE: Partition = Partition::new(0, 4096); - const DFU: Partition = Partition::new(4096, 8192); - let mut flash = MemFlash::<8192, 4096, 4>::default(); + 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); - DFU.write_blocking(&mut flash, 0, &write_buf).unwrap(); + flash.dfu().write(0, &write_buf).unwrap(); // On with the test - - let mut updater = FirmwareUpdater::new(DFU, STATE); + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); let mut aligned = [0; 4]; assert!(block_on(updater.verify_and_mark_updated( - &mut flash, &public_key.to_bytes(), &signature.to_bytes(), firmware_len as u32, diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index 3ba84a92c..2728e9720 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -34,6 +34,52 @@ impl MemFla } } + 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); @@ -44,12 +90,6 @@ impl MemFla Ok(()) } - - pub fn assert_eq(&self, offset: u32, expectation: &[u8]) { - for i in 0..expectation.len() { - assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i); - } - } } impl Default @@ -78,9 +118,7 @@ impl ReadNo const READ_SIZE: usize = 1; fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - let len = bytes.len(); - bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); - Ok(()) + self.read(offset, bytes) } fn capacity(&self) -> usize { @@ -94,44 +132,12 @@ impl NorFla const WRITE_SIZE: usize = WRITE_SIZE; const ERASE_SIZE: usize = ERASE_SIZE; - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - 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(()) + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) } - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - 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<(), Self::Error> { + self.erase(from, to) } } @@ -142,11 +148,11 @@ impl AsyncR const READ_SIZE: usize = 1; async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - ::read(self, offset, bytes) + self.read(offset, bytes) } fn capacity(&self) -> usize { - ::capacity(self) + SIZE } } @@ -157,11 +163,11 @@ impl AsyncN const WRITE_SIZE: usize = WRITE_SIZE; const ERASE_SIZE: usize = ERASE_SIZE; - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - ::erase(self, from, to) + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) } - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - ::write(self, 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/partition.rs b/embassy-boot/boot/src/partition.rs deleted file mode 100644 index 7b56a8240..000000000 --- a/embassy-boot/boot/src/partition.rs +++ /dev/null @@ -1,144 +0,0 @@ -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; -#[cfg(feature = "nightly")] -use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; - -/// A region in flash used by the bootloader. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Partition { - /// The offset into the flash where the partition starts. - pub from: u32, - /// The offset into the flash where the partition ends. - pub to: u32, -} - -impl Partition { - /// Create a new partition with the provided range - pub const fn new(from: u32, to: u32) -> Self { - Self { from, to } - } - - /// Return the size of the partition - pub const fn size(&self) -> u32 { - self.to - self.from - } - - /// Read from the partition on the provided flash - #[cfg(feature = "nightly")] - pub async fn read( - &self, - flash: &mut F, - offset: u32, - bytes: &mut [u8], - ) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.read(offset, bytes).await - } - - /// Write to the partition on the provided flash - #[cfg(feature = "nightly")] - pub async fn write(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.write(offset, bytes).await?; - trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); - Ok(()) - } - - /// Erase part of the partition on the provided flash - #[cfg(feature = "nightly")] - pub async fn erase(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { - let from = self.from as u32 + from; - let to = self.from as u32 + to; - flash.erase(from, to).await?; - trace!("Erased from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } - - /// Erase the entire partition - #[cfg(feature = "nightly")] - pub(crate) async fn wipe(&self, flash: &mut F) -> Result<(), F::Error> { - let from = self.from as u32; - let to = self.to as u32; - flash.erase(from, to).await?; - trace!("Wiped from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } - - /// Read from the partition on the provided flash - pub fn read_blocking(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.read(offset, bytes) - } - - /// Write to the partition on the provided flash - pub fn write_blocking(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.write(offset, bytes)?; - trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); - Ok(()) - } - - /// Erase part of the partition on the provided flash - pub fn erase_blocking(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { - let from = self.from as u32 + from; - let to = self.from as u32 + to; - flash.erase(from, to)?; - trace!("Erased from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } - - /// Erase the entire partition - pub(crate) fn wipe_blocking(&self, flash: &mut F) -> Result<(), F::Error> { - let from = self.from as u32; - let to = self.to as u32; - flash.erase(from, to)?; - trace!("Wiped from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::mem_flash::MemFlash; - use crate::Partition; - - #[test] - fn can_erase() { - let mut flash = MemFlash::<1024, 64, 4>::new(0x00); - let partition = Partition::new(256, 512); - - partition.erase_blocking(&mut flash, 64, 192).unwrap(); - - for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) { - assert_eq!(0x00, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) { - assert_eq!(0xFF, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) { - assert_eq!(0x00, byte, "Index {}", index); - } - } - - #[test] - fn can_wipe() { - let mut flash = MemFlash::<1024, 64, 4>::new(0x00); - let partition = Partition::new(256, 512); - - partition.wipe_blocking(&mut flash).unwrap(); - - for (index, byte) in flash.mem.iter().copied().enumerate().take(256) { - assert_eq!(0x00, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) { - assert_eq!(0xFF, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) { - assert_eq!(0x00, byte, "Index {}", index); - } - } -} diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs new file mode 100644 index 000000000..3ac9e71ab --- /dev/null +++ b/embassy-boot/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/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs new file mode 100644 index 000000000..ba33c9208 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/blocking.rs @@ -0,0 +1,69 @@ +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) + } +} + +#[cfg(feature = "nightly")] +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 new file mode 100644 index 000000000..a0672322e --- /dev/null +++ b/embassy-boot/boot/src/test_flash/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +#[cfg(feature = "nightly")] +pub(crate) use asynch::AsyncTestFlash; +pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index 710798bdb..bb702073c 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs @@ -3,74 +3,37 @@ #![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig}; use embassy_nrf::nvmc::{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 { - boot: embassy_boot::BootLoader, +pub struct BootLoader { + boot: embassy_boot::BootLoader, aligned_buf: AlignedBuffer, } -#[cfg(target_os = "none")] -impl Default for BootLoader { - /// Create a new bootloader instance using parameters from linker script - fn default() -> 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 { - Partition::new( - &__bootloader_active_start as *const u32 as u32, - &__bootloader_active_end as *const u32 as u32, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } -} - -impl BootLoader { +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } /// Inspect the bootloader state and perform actions required before booting, such as swapping /// firmware. - pub fn prepare(&mut self, flash: &mut F) -> usize { - match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) { - Ok(_) => self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + pub fn prepare(&mut self) { + self.boot + .prepare_boot(&mut self.aligned_buf.0) + .expect("Boot prepare error"); } /// Boots the application without softdevice mechanisms. @@ -79,10 +42,12 @@ impl BootLoader { /// /// 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(&mut self, start: usize) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + let mut p = cortex_m::Peripherals::steal(); p.SCB.invalidate_icache(); - p.SCB.vtor.write(start as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } @@ -92,7 +57,7 @@ impl BootLoader { /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(feature = "softdevice")] - pub unsafe fn load(&mut self, _app: usize) -> ! { + pub unsafe fn load(&mut self, _app: u32) -> ! { use nrf_softdevice_mbr as mbr; const NRF_SUCCESS: u32 = 0; diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs index fb9bc3242..25329f9e9 100644 --- a/embassy-boot/rp/src/lib.rs +++ b/embassy-boot/rp/src/lib.rs @@ -3,7 +3,9 @@ #![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; use embassy_rp::flash::{Flash, ERASE_SIZE}; use embassy_rp::peripherals::{FLASH, WATCHDOG}; use embassy_rp::watchdog::Watchdog; @@ -11,27 +13,28 @@ use embassy_time::Duration; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for RP2040 devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, +pub struct BootLoader { + boot: embassy_boot::BootLoader, aligned_buf: AlignedBuffer, } -impl BootLoader { +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } /// Inspect the bootloader state and perform actions required before booting, such as swapping /// firmware. - pub fn prepare(&mut self, flash: &mut F) -> usize { - match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) { - Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + pub fn prepare(&mut self) { + self.boot + .prepare_boot(self.aligned_buf.as_mut()) + .expect("Boot prepare error"); } /// Boots the application. @@ -39,58 +42,20 @@ impl BootLoader { /// # Safety /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(&mut self, start: usize) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + 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 as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } } -#[cfg(target_os = "none")] -impl Default for BootLoader { - /// Create a new bootloader instance using parameters from linker script - fn default() -> 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 { - Partition::new( - &__bootloader_active_start as *const u32 as u32, - &__bootloader_active_end as *const u32 as u32, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } -} - /// A flash implementation that will feed a watchdog when touching flash. pub struct WatchdogFlash<'d, const SIZE: usize> { flash: Flash<'d, FLASH, SIZE>, diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index ccf136c74..069de0d1a 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -3,30 +3,34 @@ #![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; +use embedded_storage::nor_flash::NorFlash; /// A bootloader for STM32 devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, +pub struct BootLoader { + boot: embassy_boot::BootLoader, aligned_buf: AlignedBuffer, } -impl BootLoader { +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } /// Inspect the bootloader state and perform actions required before booting, such as swapping /// firmware. - pub fn prepare(&mut self, flash: &mut F) -> usize { - match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) { - Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + pub fn prepare(&mut self) { + self.boot + .prepare_boot(self.aligned_buf.as_mut()) + .expect("Boot prepare error"); } /// Boots the application. @@ -34,54 +38,16 @@ impl BootLoader { /// # Safety /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(&mut self, start: usize) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + 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 as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } } - -#[cfg(target_os = "none")] -impl Default for BootLoader { - /// Create a new bootloader instance using parameters from linker script - fn default() -> 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 { - Partition::new( - &__bootloader_active_start as *const u32 as u32, - &__bootloader_active_end as *const u32 as u32, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } -} diff --git a/embassy-embedded-hal/src/flash/partition/asynch.rs b/embassy-embedded-hal/src/flash/partition/asynch.rs index 141e0d9fc..5920436dd 100644 --- a/embassy-embedded-hal/src/flash/partition/asynch.rs +++ b/embassy-embedded-hal/src/flash/partition/asynch.rs @@ -30,6 +30,16 @@ impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> { } Self { flash, offset, size } } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } } impl ErrorType for Partition<'_, M, T> { diff --git a/embassy-embedded-hal/src/flash/partition/blocking.rs b/embassy-embedded-hal/src/flash/partition/blocking.rs index dc52e292a..2ddbe3de0 100644 --- a/embassy-embedded-hal/src/flash/partition/blocking.rs +++ b/embassy-embedded-hal/src/flash/partition/blocking.rs @@ -31,6 +31,16 @@ impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> { } Self { flash, offset, size } } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } } impl ErrorType for BlockingPartition<'_, M, T> { diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 090a05b23..06c237781 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -3,12 +3,13 @@ #![macro_use] #![feature(type_alias_impl_trait)] -use embassy_boot_nrf::FirmwareUpdater; +use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::nvmc::Nvmc; use embassy_nrf::wdt::{self, Watchdog}; +use embassy_sync::mutex::Mutex; use panic_reset as _; static APP_B: &[u8] = include_bytes!("../../b.bin"); @@ -45,9 +46,10 @@ async fn main(_spawner: Spawner) { }; let nvmc = Nvmc::new(p.NVMC); - let mut nvmc = BlockingAsync::new(nvmc); + let nvmc = Mutex::new(BlockingAsync::new(nvmc)); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc); + let mut updater = FirmwareUpdater::new(config); loop { led.set_low(); button.wait_for_any_edge().await; @@ -56,11 +58,11 @@ async fn main(_spawner: Spawner) { for chunk in APP_B.chunks(4096) { let mut buf: [u8; 4096] = [0; 4096]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = [0; 4]; - updater.mark_updated(&mut nvmc, &mut magic).await.unwrap(); + updater.mark_updated(&mut magic).await.unwrap(); led.set_high(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 64c2b8925..4a2c5dd8f 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -20,6 +20,7 @@ embedded-hal = { version = "0.2.6" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" +embedded-storage = "0.3.0" [features] default = ["panic-reset"] diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs index 47f1d16d8..69850069b 100644 --- a/examples/boot/application/rp/src/bin/a.rs +++ b/examples/boot/application/rp/src/bin/a.rs @@ -2,13 +2,17 @@ #![no_main] #![feature(type_alias_impl_trait)] +use core::cell::RefCell; + use defmt_rtt as _; use embassy_boot_rp::*; use embassy_executor::Spawner; use embassy_rp::flash::Flash; use embassy_rp::gpio::{Level, Output}; use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; use embassy_time::{Duration, Timer}; +use embedded_storage::nor_flash::NorFlash; #[cfg(feature = "panic-probe")] use panic_probe as _; #[cfg(feature = "panic-reset")] @@ -26,9 +30,11 @@ async fn main(_s: Spawner) { let mut watchdog = Watchdog::new(p.WATCHDOG); watchdog.start(Duration::from_secs(8)); - let mut flash: Flash<_, FLASH_SIZE> = Flash::new_blocking(p.FLASH); + let flash: Flash<_, FLASH_SIZE> = Flash::new(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); Timer::after(Duration::from_secs(5)).await; watchdog.feed(); @@ -36,22 +42,20 @@ async fn main(_s: Spawner) { let mut offset = 0; let mut buf: AlignedBuffer<4096> = AlignedBuffer([0; 4096]); defmt::info!("preparing update"); - let mut writer = updater - .prepare_update_blocking(&mut flash) + let writer = updater + .prepare_update() .map_err(|e| defmt::warn!("E: {:?}", defmt::Debug2Format(&e))) .unwrap(); defmt::info!("writer created, starting write"); for chunk in APP_B.chunks(4096) { buf.0[..chunk.len()].copy_from_slice(chunk); defmt::info!("writing block at offset {}", offset); - writer - .write_block_blocking(offset, &buf.0[..], &mut flash, 256) - .unwrap(); - offset += chunk.len(); + writer.write(offset, &buf.0[..]).unwrap(); + offset += chunk.len() as u32; } watchdog.feed(); defmt::info!("firmware written, marking update"); - updater.mark_updated_blocking(&mut flash, &mut buf.0[..1]).unwrap(); + updater.mark_updated(&mut buf.0[..1]).unwrap(); Timer::after(Duration::from_secs(2)).await; led.set_low(); defmt::info!("update marked, resetting"); diff --git a/examples/boot/application/stm32f3/src/bin/a.rs b/examples/boot/application/stm32f3/src/bin/a.rs index 5db1dbb57..c94676f09 100644 --- a/examples/boot/application/stm32f3/src/bin/a.rs +++ b/examples/boot/application/stm32f3/src/bin/a.rs @@ -4,12 +4,13 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use panic_reset as _; static APP_B: &[u8] = include_bytes!("../../b.bin"); @@ -18,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PC13, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,17 +27,18 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PA5, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32f7/src/bin/a.rs b/examples/boot/application/stm32f7/src/bin/a.rs index 5d586445c..fc2702c91 100644 --- a/examples/boot/application/stm32f7/src/bin/a.rs +++ b/examples/boot/application/stm32f7/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; @@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut flash = Flash::new_blocking(p.FLASH); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); let button = Input::new(p.PC13, Pull::Down); let mut button = ExtiInput::new(button, p.EXTI13); @@ -24,20 +25,19 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB7, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); - let mut writer = updater.prepare_update_blocking(&mut flash).unwrap(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); + let mut writer = updater.prepare_update().unwrap(); button.wait_for_rising_edge().await; let mut offset = 0; let mut buf = AlignedBuffer([0; 4096]); for chunk in APP_B.chunks(4096) { buf.as_mut()[..chunk.len()].copy_from_slice(chunk); - writer - .write_block_blocking(offset, buf.as_ref(), &mut flash, chunk.len()) - .unwrap(); + writer.write(offset, buf.as_ref()).unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap(); + updater.mark_updated(magic.as_mut()).unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32h7/src/bin/a.rs b/examples/boot/application/stm32h7/src/bin/a.rs index 202220223..1a54464d0 100644 --- a/examples/boot/application/stm32h7/src/bin/a.rs +++ b/examples/boot/application/stm32h7/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; @@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut flash = Flash::new_blocking(p.FLASH); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); let button = Input::new(p.PC13, Pull::Down); let mut button = ExtiInput::new(button, p.EXTI13); @@ -24,21 +25,19 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); - - let mut writer = updater.prepare_update_blocking(&mut flash).unwrap(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); + let mut writer = updater.prepare_update().unwrap(); button.wait_for_rising_edge().await; let mut offset = 0; let mut buf = AlignedBuffer([0; 4096]); for chunk in APP_B.chunks(4096) { buf.as_mut()[..chunk.len()].copy_from_slice(chunk); - writer - .write_block_blocking(offset, buf.as_ref(), &mut flash, 4096) - .unwrap(); + writer.write(offset, buf.as_ref()).unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap(); + updater.mark_updated(magic.as_mut()).unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32l1/src/bin/a.rs b/examples/boot/application/stm32l1/src/bin/a.rs index 4033ac590..00ddda636 100644 --- a/examples/boot/application/stm32l1/src/bin/a.rs +++ b/examples/boot/application/stm32l1/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; @@ -19,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PB2, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI2); @@ -28,18 +28,19 @@ async fn main(_spawner: Spawner) { led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; for chunk in APP_B.chunks(128) { let mut buf: [u8; 128] = [0; 128]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 128).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); Timer::after(Duration::from_secs(1)).await; cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/application/stm32l4/src/bin/a.rs b/examples/boot/application/stm32l4/src/bin/a.rs index 141d82afd..54579e4ac 100644 --- a/examples/boot/application/stm32l4/src/bin/a.rs +++ b/examples/boot/application/stm32l4/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; @@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PC13, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,13 +26,14 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index 5f48dbe51..0c6fa05f9 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let mut flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PA0, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI0); @@ -26,7 +26,8 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB9, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; //defmt::info!("Starting update"); let mut offset = 0; @@ -34,11 +35,11 @@ async fn main(_spawner: Spawner) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); // defmt::info!("Writing chunk at 0x{:x}", offset); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); //defmt::info!("Marked as updated"); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index 8c2fb4c5f..40656f359 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -12,6 +12,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", features = ["nightly"] } embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } cfg-if = "1.0.0" diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index 8818a23b8..72c95c02a 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -1,12 +1,15 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_nrf::*; use embassy_nrf::nvmc::Nvmc; use embassy_nrf::wdt; +use embassy_sync::blocking_mutex::Mutex; #[entry] fn main() -> ! { @@ -20,19 +23,21 @@ fn main() -> ! { } */ - let mut bl = BootLoader::default(); - let mut wdt_config = wdt::Config::default(); wdt_config.timeout_ticks = 32768 * 5; // timeout seconds wdt_config.run_during_sleep = true; wdt_config.run_during_debug_halt = false; - let start = bl.prepare(&mut SingleFlashConfig::new(&mut BootFlash::new(WatchdogFlash::start( - Nvmc::new(p.NVMC), - p.WDT, - wdt_config, - )))); - unsafe { bl.load(start) } + let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(active_offset) } } #[no_mangle] diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index bf9226993..8d60f18be 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -11,6 +11,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-rp = { path = "../../../../embassy-rp", features = ["nightly"] } embassy-boot-rp = { path = "../../../../embassy-boot/rp" } +embassy-sync = { path = "../../../../embassy-sync" } embassy-time = { path = "../../../../embassy-time", features = ["nightly"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } diff --git a/examples/boot/bootloader/rp/src/main.rs b/examples/boot/bootloader/rp/src/main.rs index 8129591fa..6a81db804 100644 --- a/examples/boot/bootloader/rp/src/main.rs +++ b/examples/boot/bootloader/rp/src/main.rs @@ -1,10 +1,13 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_rp::*; +use embassy_sync::blocking_mutex::Mutex; use embassy_time::Duration; const FLASH_SIZE: usize = 2 * 1024 * 1024; @@ -21,13 +24,16 @@ fn main() -> ! { } */ - let mut bl: BootLoader = BootLoader::default(); let flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); - let mut flash = BootFlash::new(flash); - let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash)); - core::mem::drop(flash); + let flash = Mutex::new(RefCell::new(flash)); - unsafe { bl.load(start) } + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } } #[no_mangle] diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index fbc80b34c..6436f2fee 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -12,6 +12,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = ["nightly"] } embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" embedded-storage-async = "0.4.0" diff --git a/examples/boot/bootloader/stm32/src/main.rs b/examples/boot/bootloader/stm32/src/main.rs index f81fdbc5f..262eed200 100644 --- a/examples/boot/bootloader/stm32/src/main.rs +++ b/examples/boot/bootloader/stm32/src/main.rs @@ -1,11 +1,14 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_stm32::*; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, BANK1_REGION}; +use embassy_sync::blocking_mutex::Mutex; #[entry] fn main() -> ! { @@ -19,12 +22,16 @@ fn main() -> ! { } */ - let mut bl: BootLoader<2048> = BootLoader::default(); let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); - let mut flash = BootFlash::new(layout.bank1_region); - let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash)); - core::mem::drop(flash); - unsafe { bl.load(start) } + let flash = Mutex::new(RefCell::new(layout.bank1_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _, 2048> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(BANK1_REGION.base + active_offset) } } #[no_mangle]