From 3ca73144765411994759194a2279b567f4508be5 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 30 Aug 2022 13:07:35 +0200 Subject: Remove generic const expressions from embassy-boot * Remove the need for generic const expressions and use buffers provided in the flash config. * Extend embedded-storage traits to simplify generics. * Document all public APIs * Add toplevel README * Expose AlignedBuffer type for convenience. * Update examples --- embassy-boot/boot/src/lib.rs | 544 +++++++++++++++++++++++++------------------ 1 file changed, 312 insertions(+), 232 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 51e1056cf..e8ebe628d 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,53 +1,54 @@ #![feature(type_alias_impl_trait)] #![feature(generic_associated_types)] -#![feature(generic_const_exprs)] -#![allow(incomplete_features)] #![no_std] -///! embassy-boot is a bootloader and firmware updater for embedded devices with flash -///! storage implemented using embedded-storage -///! -///! The bootloader works in conjunction with the firmware application, and only has the -///! ability to manage two flash banks with an active and a updatable part. It implements -///! a swap algorithm that is power-failure safe, and allows reverting to the previous -///! version of the firmware, should the application crash and fail to mark itself as booted. -///! -///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf, -///! which defines the limits and flash type for that particular platform. -///! +#![warn(missing_docs)] +#![doc = include_str!("../../README.md")] mod fmt; -use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use embedded_storage_async::nor_flash::AsyncNorFlash; const BOOT_MAGIC: u8 = 0xD0; const SWAP_MAGIC: u8 = 0xF0; +/// A region in flash used by the bootloader. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Partition { + /// Start of the flash region. pub from: usize, + /// End of the flash region. pub to: usize, } impl Partition { + /// Create a new partition with the provided range pub const fn new(from: usize, to: usize) -> Self { Self { from, to } } + + /// Return the length of the partition pub const fn len(&self) -> usize { self.to - self.from } } -#[derive(PartialEq, Debug)] +/// The state of the bootloader after running prepare. +#[derive(PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum State { + /// Bootloader is ready to boot the active partition. Boot, + /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, } -#[derive(PartialEq, Debug)] +/// Errors returned by bootloader +#[derive(PartialEq, Eq, Debug)] pub enum BootError { + /// Error from flash. Flash(NorFlashErrorKind), + /// Invalid bootloader magic BadMagic, } @@ -60,19 +61,39 @@ where } } -pub trait FlashConfig { - const BLOCK_SIZE: usize; - const ERASE_VALUE: u8; - type FLASH: NorFlash + ReadNorFlash; +/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. +#[repr(align(32))] +pub struct AlignedBuffer(pub [u8; N]); + +impl AsRef<[u8]> for AlignedBuffer { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for AlignedBuffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} - fn flash(&mut self) -> &mut Self::FLASH; +/// Extension of the embedded-storage flash type information with block size and erase value. +pub trait Flash: NorFlash + ReadNorFlash { + /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase + /// size of the flash, but for external QSPI flash modules, this can be lower. + const BLOCK_SIZE: usize; + /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value. + const ERASE_VALUE: u8 = 0xFF; } /// Trait defining the flash handles used for active and DFU partition -pub trait FlashProvider { - type STATE: FlashConfig; - type ACTIVE: FlashConfig; - type DFU: FlashConfig; +pub trait FlashConfig { + /// Flash type used for the state partition. + type STATE: Flash; + /// Flash type used for the active partition. + type ACTIVE: Flash; + /// Flash type used for the dfu partition. + type DFU: Flash; /// Return flash instance used to write/read to/from active partition. fn active(&mut self) -> &mut Self::ACTIVE; @@ -84,9 +105,7 @@ pub trait FlashProvider { /// BootLoader works with any flash implementing embedded_storage and can also work with /// different page sizes and flash write sizes. -/// -/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes. -pub struct BootLoader { +pub struct BootLoader { // Page with current state of bootloader. The state partition has the following format: // | Range | Description | // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | @@ -98,15 +117,16 @@ pub struct BootLoader { dfu: Partition, } -impl BootLoader { +impl BootLoader { + /// Create a new instance of a bootloader with the given 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 { - assert_eq!(active.len() % PAGE_SIZE, 0); - assert_eq!(dfu.len() % PAGE_SIZE, 0); - // DFU partition must have an extra page - assert!(dfu.len() - active.len() >= PAGE_SIZE); Self { active, dfu, state } } + /// Return the boot address for the active partition. pub fn boot_address(&self) -> usize { self.active.from } @@ -194,44 +214,43 @@ impl BootLoader { /// | DFU | 3 | 3 | 2 | 1 | 3 | /// +-----------+--------------+--------+--------+--------+--------+ /// - pub fn prepare_boot(&mut self, p: &mut P) -> Result - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - [(); <

::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, - { + pub fn prepare_boot( + &mut self, + p: &mut P, + magic: &mut [u8], + page: &mut [u8], + ) -> Result { // Ensure we have enough progress pages to store copy progress - assert!( - self.active.len() / PAGE_SIZE - <= (self.state.len() - <

::STATE as FlashConfig>::FLASH::WRITE_SIZE) - / <

::STATE as FlashConfig>::FLASH::WRITE_SIZE - ); + assert_eq!(self.active.len() % page.len(), 0); + assert_eq!(self.dfu.len() % page.len(), 0); + assert!(self.dfu.len() - self.active.len() >= page.len()); + assert!(self.active.len() / page.len() <= (self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE); + assert_eq!(magic.len(), P::STATE::WRITE_SIZE); // Copy contents from partition N to active - let state = self.read_state(p.state())?; + let state = self.read_state(p, magic)?; match 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.state())? { + if !self.is_swapped(p, magic, page)? { trace!("Swapping"); - self.swap(p)?; + self.swap(p, magic, page)?; trace!("Swapping done"); } else { trace!("Reverting"); - self.revert(p)?; + self.revert(p, magic, page)?; // Overwrite magic and reset progress - let fstate = p.state().flash(); - let aligned = Aligned( - [!P::STATE::ERASE_VALUE; <

::STATE as FlashConfig>::FLASH::WRITE_SIZE], - ); - fstate.write(self.state.from as u32, &aligned.0)?; + let fstate = p.state(); + magic.fill(!P::STATE::ERASE_VALUE); + fstate.write(self.state.from as u32, magic)?; fstate.erase(self.state.from as u32, self.state.to as u32)?; - let aligned = - Aligned([BOOT_MAGIC; <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]); - fstate.write(self.state.from as u32, &aligned.0)?; + + magic.fill(BOOT_MAGIC); + fstate.write(self.state.from as u32, magic)?; } } _ => {} @@ -239,166 +258,152 @@ impl BootLoader { Ok(state) } - fn is_swapped(&mut self, p: &mut P) -> Result - where - [(); P::FLASH::WRITE_SIZE]:, - { - let page_count = self.active.len() / P::FLASH::ERASE_SIZE; - let progress = self.current_progress(p)?; + fn is_swapped(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result { + let page_size = page.len(); + let page_count = self.active.len() / page_size; + let progress = self.current_progress(p, magic)?; Ok(progress >= page_count * 2) } - fn current_progress(&mut self, p: &mut P) -> Result - where - [(); P::FLASH::WRITE_SIZE]:, - { - let write_size = P::FLASH::WRITE_SIZE; + fn current_progress(&mut self, config: &mut P, aligned: &mut [u8]) -> Result { + let write_size = aligned.len(); let max_index = ((self.state.len() - write_size) / write_size) - 1; - let flash = p.flash(); - let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); + aligned.fill(!P::STATE::ERASE_VALUE); + + let flash = config.state(); for i in 0..max_index { - flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?; - if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] { + flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?; + + if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { return Ok(i); } } Ok(max_index) } - fn update_progress(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> - where - [(); P::FLASH::WRITE_SIZE]:, - { - let flash = p.flash(); - let write_size = P::FLASH::WRITE_SIZE; + fn update_progress(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { + let flash = p.state(); + let write_size = magic.len(); let w = self.state.from + write_size + idx * write_size; - let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); - flash.write(w as u32, &aligned.0)?; + + let aligned = magic; + aligned.fill(!P::STATE::ERASE_VALUE); + flash.write(w as u32, aligned)?; Ok(()) } - fn active_addr(&self, n: usize) -> usize { - self.active.from + n * PAGE_SIZE + fn active_addr(&self, n: usize, page_size: usize) -> usize { + self.active.from + n * page_size } - fn dfu_addr(&self, n: usize) -> usize { - self.dfu.from + n * PAGE_SIZE + fn dfu_addr(&self, n: usize, page_size: usize) -> usize { + self.dfu.from + n * page_size } - fn copy_page_once_to_active( + fn copy_page_once_to_active( &mut self, idx: usize, from_page: usize, to_page: usize, p: &mut P, - ) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; - if self.current_progress(p.state())? <= idx { + magic: &mut [u8], + page: &mut [u8], + ) -> Result<(), BootError> { + let buf = page; + if self.current_progress(p, magic)? <= idx { let mut offset = from_page; for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { - p.dfu().flash().read(offset as u32, chunk)?; + p.dfu().read(offset as u32, chunk)?; offset += chunk.len(); } - p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; + p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?; let mut offset = to_page; for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { - p.active().flash().write(offset as u32, &chunk)?; + p.active().write(offset as u32, chunk)?; offset += chunk.len(); } - self.update_progress(idx, p.state())?; + self.update_progress(idx, p, magic)?; } Ok(()) } - fn copy_page_once_to_dfu( + fn copy_page_once_to_dfu( &mut self, idx: usize, from_page: usize, to_page: usize, p: &mut P, - ) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; - if self.current_progress(p.state())? <= idx { + magic: &mut [u8], + page: &mut [u8], + ) -> Result<(), BootError> { + let buf = page; + if self.current_progress(p, magic)? <= idx { let mut offset = from_page; for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { - p.active().flash().read(offset as u32, chunk)?; + p.active().read(offset as u32, chunk)?; offset += chunk.len(); } - p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; + p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?; let mut offset = to_page; for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { - p.dfu().flash().write(offset as u32, chunk)?; + p.dfu().write(offset as u32, chunk)?; offset += chunk.len(); } - self.update_progress(idx, p.state())?; + self.update_progress(idx, p, magic)?; } Ok(()) } - fn swap(&mut self, p: &mut P) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let page_count = self.active.len() / PAGE_SIZE; + fn swap(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { + let page_size = page.len(); + let page_count = self.active.len() / page_size; trace!("Page count: {}", page_count); - for page in 0..page_count { - trace!("COPY PAGE {}", page); + for page_num in 0..page_count { + trace!("COPY PAGE {}", page_num); // Copy active page to the 'next' DFU page. - let active_page = self.active_addr(page_count - 1 - page); - let dfu_page = self.dfu_addr(page_count - page); + let active_page = self.active_addr(page_count - 1 - page_num, page_size); + let dfu_page = self.dfu_addr(page_count - page_num, page_size); //trace!("Copy active {} to dfu {}", active_page, dfu_page); - self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; + self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?; // Copy DFU page to the active page - let active_page = self.active_addr(page_count - 1 - page); - let dfu_page = self.dfu_addr(page_count - 1 - page); + let active_page = self.active_addr(page_count - 1 - page_num, page_size); + let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size); //trace!("Copy dfy {} to active {}", dfu_page, active_page); - self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; + self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; } Ok(()) } - fn revert(&mut self, p: &mut P) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let page_count = self.active.len() / PAGE_SIZE; - for page in 0..page_count { + fn revert(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { + let page_size = page.len(); + let page_count = self.active.len() / page_size; + for page_num in 0..page_count { // Copy the bad active page to the DFU page - let active_page = self.active_addr(page); - let dfu_page = self.dfu_addr(page); - self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?; + let active_page = self.active_addr(page_num, page_size); + let dfu_page = self.dfu_addr(page_num, page_size); + self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?; // Copy the DFU page back to the active page - let active_page = self.active_addr(page); - let dfu_page = self.dfu_addr(page + 1); - self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?; + let active_page = self.active_addr(page_num, page_size); + let dfu_page = self.dfu_addr(page_num + 1, page_size); + self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; } Ok(()) } - fn read_state(&mut self, p: &mut P) -> Result - where - [(); P::FLASH::WRITE_SIZE]:, - { - let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE]; - let flash = p.flash(); - flash.read(self.state.from as u32, &mut magic)?; + fn read_state(&mut self, config: &mut P, magic: &mut [u8]) -> Result { + let flash = config.state(); + flash.read(self.state.from as u32, magic)?; - if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] { + if !magic.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) } else { Ok(State::Boot) @@ -406,108 +411,149 @@ impl BootLoader { } } -/// Convenience provider that uses a single flash for everything -pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF> +/// Convenience provider that uses a single flash for all partitions. +pub struct SingleFlashConfig<'a, F> where - F: NorFlash + ReadNorFlash, + F: Flash, { - config: SingleFlashConfig<'a, F, ERASE_VALUE>, + flash: &'a mut F, } -impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE> +impl<'a, F> SingleFlashConfig<'a, F> where - F: NorFlash + ReadNorFlash, + F: Flash, { + /// Create a provider for a single flash. pub fn new(flash: &'a mut F) -> Self { - Self { - config: SingleFlashConfig { flash }, - } + Self { flash } + } +} + +impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> +where + F: Flash, +{ + 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 } } -pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF> +/// A flash wrapper implementing the Flash and embedded_storage traits. +pub struct BootFlash<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF> where F: NorFlash + ReadNorFlash, { flash: &'a mut F, } -impl<'a, F> FlashProvider for SingleFlashProvider<'a, F> +impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> where F: NorFlash + ReadNorFlash, { - type STATE = SingleFlashConfig<'a, F>; - type ACTIVE = SingleFlashConfig<'a, F>; - type DFU = SingleFlashConfig<'a, F>; - - fn active(&mut self) -> &mut Self::STATE { - &mut self.config - } - fn dfu(&mut self) -> &mut Self::ACTIVE { - &mut self.config - } - fn state(&mut self) -> &mut Self::DFU { - &mut self.config + /// Create a new instance of a bootable flash + pub fn new(flash: &'a mut F) -> Self { + Self { flash } } } -impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE> +impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> where F: NorFlash + ReadNorFlash, { - const BLOCK_SIZE: usize = F::ERASE_SIZE; + const BLOCK_SIZE: usize = BLOCK_SIZE; const ERASE_VALUE: u8 = ERASE_VALUE; - type FLASH = F; - fn flash(&mut self) -> &mut F { - self.flash +} + +impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +where + F: ReadNorFlash + NorFlash, +{ + type Error = F::Error; +} + +impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +where + F: ReadNorFlash + 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(self.flash, from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + F::write(self.flash, offset, bytes) } } -/// Convenience provider that uses a single flash for everything -pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU> +impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> where - ACTIVE: NorFlash + ReadNorFlash, - STATE: NorFlash + ReadNorFlash, - DFU: NorFlash + ReadNorFlash, + F: ReadNorFlash + NorFlash, { - active: SingleFlashConfig<'a, ACTIVE>, - state: SingleFlashConfig<'a, STATE>, - dfu: SingleFlashConfig<'a, DFU>, + const READ_SIZE: usize = F::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + F::read(self.flash, offset, bytes) + } + + fn capacity(&self) -> usize { + F::capacity(self.flash) + } } -impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU> +/// Convenience flash provider that uses separate flash instances for each partition. +pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> where - ACTIVE: NorFlash + ReadNorFlash, - STATE: NorFlash + ReadNorFlash, - DFU: NorFlash + ReadNorFlash, + ACTIVE: Flash, + STATE: Flash, + DFU: Flash, { + active: &'a mut ACTIVE, + state: &'a mut STATE, + dfu: &'a mut DFU, +} + +impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> +where + ACTIVE: Flash, + STATE: Flash, + DFU: Flash, +{ + /// 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: SingleFlashConfig { flash: active }, - state: SingleFlashConfig { flash: state }, - dfu: SingleFlashConfig { flash: dfu }, - } + Self { active, state, dfu } } } -impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU> +impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> where - ACTIVE: NorFlash + ReadNorFlash, - STATE: NorFlash + ReadNorFlash, - DFU: NorFlash + ReadNorFlash, + ACTIVE: Flash, + STATE: Flash, + DFU: Flash, { - type STATE = SingleFlashConfig<'a, STATE>; - type ACTIVE = SingleFlashConfig<'a, ACTIVE>; - type DFU = SingleFlashConfig<'a, DFU>; + type STATE = STATE; + type ACTIVE = ACTIVE; + type DFU = DFU; fn active(&mut self) -> &mut Self::ACTIVE { - &mut self.active + self.active } fn dfu(&mut self) -> &mut Self::DFU { - &mut self.dfu + self.dfu } fn state(&mut self) -> &mut Self::STATE { - &mut self.state + self.state } } @@ -518,10 +564,6 @@ pub struct FirmwareUpdater { dfu: Partition, } -// NOTE: Aligned to the largest write size supported by flash -#[repr(align(32))] -pub struct Aligned([u8; N]); - impl Default for FirmwareUpdater { fn default() -> Self { extern "C" { @@ -551,6 +593,7 @@ impl Default for FirmwareUpdater { } 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 } } @@ -560,23 +603,24 @@ impl FirmwareUpdater { self.dfu.len() } - /// Instruct bootloader that DFU should commence at next boot. - /// Must be provided with an aligned buffer to use for reading and writing magic; - pub async fn update(&mut self, flash: &mut F) -> Result<(), F::Error> - where - [(); F::WRITE_SIZE]:, - { - let mut aligned = Aligned([0; { F::WRITE_SIZE }]); - self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await + /// Mark to trigger firmware swap on next boot. + /// + /// # 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_updated(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC, flash).await } - /// Mark firmware boot successfully - pub async fn mark_booted(&mut self, flash: &mut F) -> Result<(), F::Error> - where - [(); F::WRITE_SIZE]:, - { - let mut aligned = Aligned([0; { F::WRITE_SIZE }]); - self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).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, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC, flash).await } async fn set_magic( @@ -587,7 +631,7 @@ impl FirmwareUpdater { ) -> Result<(), F::Error> { flash.read(self.state.from as u32, aligned).await?; - if aligned.iter().find(|&&b| b != magic).is_some() { + if aligned.iter().any(|&b| b != magic) { aligned.fill(0); flash.write(self.state.from as u32, aligned).await?; @@ -599,7 +643,13 @@ impl FirmwareUpdater { Ok(()) } - // Write to a region of the DFU page + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. pub async fn write_firmware( &mut self, offset: usize, @@ -668,7 +718,7 @@ mod tests { #[test] fn test_bad_magic() { let mut flash = MemFlash([0xff; 131072]); - let mut flash = SingleFlashProvider::new(&mut flash); + let mut flash = SingleFlashConfig::new(&mut flash); let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); @@ -687,11 +737,16 @@ mod tests { let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); - let mut flash = SingleFlashProvider::new(&mut flash); + let mut flash = SingleFlashConfig::new(&mut flash); - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); + let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); - assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); + let mut magic = [0; 4]; + let mut page = [0; 4096]; + assert_eq!( + State::Boot, + bootloader.prepare_boot(&mut flash, &mut magic, &mut page).unwrap() + ); } #[test] @@ -703,24 +758,27 @@ mod tests { let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + let mut aligned = [0; 4]; for i in ACTIVE.from..ACTIVE.to { flash.0[i] = original[i - ACTIVE.from]; } - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); + let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut updater = FirmwareUpdater::new(DFU, STATE); let mut offset = 0; for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); + block_on(updater.write_firmware(offset, chunk, &mut flash, 4096)).unwrap(); offset += chunk.len(); } - block_on(updater.update(&mut flash)).unwrap(); + block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); + let mut magic = [0; 4]; + let mut page = [0; 4096]; assert_eq!( State::Swap, bootloader - .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) + .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) .unwrap() ); @@ -737,7 +795,7 @@ mod tests { assert_eq!( State::Swap, bootloader - .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) + .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) .unwrap() ); @@ -751,11 +809,11 @@ mod tests { } // Mark as booted - block_on(updater.mark_booted(&mut flash)).unwrap(); + block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap(); assert_eq!( State::Boot, bootloader - .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) + .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) .unwrap() ); } @@ -769,6 +827,7 @@ mod tests { let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); + let mut aligned = [0; 4]; let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; @@ -781,16 +840,23 @@ mod tests { let mut offset = 0; for chunk in update.chunks(2048) { - block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); + block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap(); offset += chunk.len(); } - block_on(updater.update(&mut state)).unwrap(); + block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); + + let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); + let mut magic = [0; 4]; + let mut page = [0; 4096]; - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); assert_eq!( State::Swap, bootloader - .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) + .prepare_boot( + &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), + &mut magic, + &mut page + ) .unwrap() ); @@ -810,6 +876,7 @@ mod tests { const ACTIVE: Partition = Partition::new(4096, 16384); const DFU: Partition = Partition::new(0, 16384); + let mut aligned = [0; 4]; let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); @@ -825,16 +892,22 @@ mod tests { let mut offset = 0; for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); + block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap(); offset += chunk.len(); } - block_on(updater.update(&mut state)).unwrap(); + block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); + let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); + let mut magic = [0; 4]; + let mut page = [0; 4096]; assert_eq!( State::Swap, bootloader - .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) + .prepare_boot( + &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,), + &mut magic, + &mut page + ) .unwrap() ); @@ -899,6 +972,13 @@ mod tests { } } + impl super::Flash + for MemFlash + { + const BLOCK_SIZE: usize = ERASE_SIZE; + const ERASE_VALUE: u8 = 0xFF; + } + impl AsyncReadNorFlash for MemFlash { -- cgit From 3aa0c13ba5cbef09b825619f22221d028532732d Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 2 Sep 2022 08:42:42 +0200 Subject: Fix a few clippy warnings --- embassy-boot/boot/src/lib.rs | 48 +++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 25 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index e8ebe628d..4a2b112a9 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -28,6 +28,7 @@ impl Partition { } /// Return the length of the partition + #[allow(clippy::len_without_is_empty)] pub const fn len(&self) -> usize { self.to - self.from } @@ -229,31 +230,28 @@ impl BootLoader { // Copy contents from partition N to active let state = self.read_state(p, magic)?; - match 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, magic, page)? { - trace!("Swapping"); - self.swap(p, magic, page)?; - trace!("Swapping done"); - } else { - trace!("Reverting"); - self.revert(p, magic, page)?; - - // Overwrite magic and reset progress - let fstate = p.state(); - magic.fill(!P::STATE::ERASE_VALUE); - fstate.write(self.state.from as u32, magic)?; - fstate.erase(self.state.from as u32, self.state.to as u32)?; - - magic.fill(BOOT_MAGIC); - fstate.write(self.state.from as u32, magic)?; - } + 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, magic, page)? { + trace!("Swapping"); + self.swap(p, magic, page)?; + trace!("Swapping done"); + } else { + trace!("Reverting"); + self.revert(p, magic, page)?; + + // Overwrite magic and reset progress + let fstate = p.state(); + magic.fill(!P::STATE::ERASE_VALUE); + fstate.write(self.state.from as u32, magic)?; + fstate.erase(self.state.from as u32, self.state.to as u32)?; + + magic.fill(BOOT_MAGIC); + fstate.write(self.state.from as u32, magic)?; } - _ => {} } Ok(state) } @@ -1005,7 +1003,7 @@ mod tests { const ERASE_SIZE: usize = ERASE_SIZE; type EraseFuture<'a> = impl Future> + 'a; - fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { + fn erase(&mut self, from: u32, to: u32) -> Self::EraseFuture<'_> { async move { let from = from as usize; let to = to as usize; -- cgit From d0fe654c82b548d65f49213ad50fc2edc5b3d71e Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 20 Sep 2022 09:42:40 +0200 Subject: Remove BootFlash borrow Compiler will infer a different lifetime for BootFlash than for the borrowed flash, which makes it require more type annotations than if it was just owning the type. Since it doesn't really matter if it owns or borrows in practical use, change it to own so that it simplifies usage. --- embassy-boot/boot/src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4a2b112a9..015dd58db 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -447,24 +447,24 @@ where } /// A flash wrapper implementing the Flash and embedded_storage traits. -pub struct BootFlash<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF> +pub struct BootFlash where F: NorFlash + ReadNorFlash, { - flash: &'a mut F, + flash: F, } -impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +impl BootFlash where F: NorFlash + ReadNorFlash, { /// Create a new instance of a bootable flash - pub fn new(flash: &'a mut F) -> Self { + pub fn new(flash: F) -> Self { Self { flash } } } -impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +impl Flash for BootFlash where F: NorFlash + ReadNorFlash, { @@ -472,14 +472,14 @@ where const ERASE_VALUE: u8 = ERASE_VALUE; } -impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +impl ErrorType for BootFlash where F: ReadNorFlash + NorFlash, { type Error = F::Error; } -impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +impl NorFlash for BootFlash where F: ReadNorFlash + NorFlash, { @@ -487,26 +487,26 @@ where const ERASE_SIZE: usize = F::ERASE_SIZE; fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - F::erase(self.flash, from, to) + F::erase(&mut self.flash, from, to) } fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - F::write(self.flash, offset, bytes) + F::write(&mut self.flash, offset, bytes) } } -impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE> +impl ReadNorFlash for BootFlash where F: ReadNorFlash + NorFlash, { const READ_SIZE: usize = F::READ_SIZE; fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - F::read(self.flash, offset, bytes) + F::read(&mut self.flash, offset, bytes) } fn capacity(&self) -> usize { - F::capacity(self.flash) + F::capacity(&self.flash) } } -- cgit From b418c0e4d620db0332d02c16fbbd455e7b8805a9 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 20 Sep 2022 14:03:04 +0200 Subject: Take into account size of revert index Fixes a bug in the partition assertions that ensures that the state page(s) have enough space for 2x active partition range. Add unit test to verify that panic is observed. --- embassy-boot/boot/src/lib.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 015dd58db..3d359533e 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -222,10 +222,7 @@ impl BootLoader { page: &mut [u8], ) -> Result { // Ensure we have enough progress pages to store copy progress - assert_eq!(self.active.len() % page.len(), 0); - assert_eq!(self.dfu.len() % page.len(), 0); - assert!(self.dfu.len() - self.active.len() >= page.len()); - assert!(self.active.len() / page.len() <= (self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE); + assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE); assert_eq!(magic.len(), P::STATE::WRITE_SIZE); // Copy contents from partition N to active @@ -409,6 +406,13 @@ impl BootLoader { } } +fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) { + assert_eq!(active.len() % page_size, 0); + assert_eq!(dfu.len() % page_size, 0); + assert!(dfu.len() - active.len() >= page_size); + assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size); +} + /// Convenience provider that uses a single flash for all partitions. pub struct SingleFlashConfig<'a, F> where @@ -919,6 +923,15 @@ mod tests { } } + #[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); + } + struct MemFlash([u8; SIZE]); impl NorFlash -- cgit From 897b72c872183221e088611aa6f30989800afd2b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 22 Sep 2022 16:28:56 +0200 Subject: Update Rust nightly. Removes feature(generic_associated_types) --- embassy-boot/boot/src/lib.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 3d359533e..96878ace9 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,5 +1,4 @@ #![feature(type_alias_impl_trait)] -#![feature(generic_associated_types)] #![no_std] #![warn(missing_docs)] #![doc = include_str!("../../README.md")] -- cgit From 7f16b1cd23f53a429bf074e76254bcf592c0b9cf Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 26 Sep 2022 06:01:18 +0200 Subject: Add blocking API to FirmwareUpdater, and allow for a split prepare/write api --- embassy-boot/boot/src/lib.rs | 186 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 179 insertions(+), 7 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 96878ace9..1c4d2d47f 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -660,12 +660,6 @@ impl FirmwareUpdater { ) -> Result<(), F::Error> { assert!(data.len() >= F::ERASE_SIZE); - trace!( - "Writing firmware at offset 0x{:x} len {}", - self.dfu.from + offset, - data.len() - ); - flash .erase( (self.dfu.from + offset) as u32, @@ -679,7 +673,141 @@ impl FirmwareUpdater { self.dfu.from + offset + data.len() ); - let mut write_offset = self.dfu.from + offset; + FirmwareWriter(self) + .write_firmware(offset, data, flash, block_size) + .await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning a `FirmwareWriter`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub fn prepare_update(&mut self, flash: &mut F) -> Result { + flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; + + trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); + + Ok(FirmwareWriter(self)) + } + + // + // Blocking API + // + + /// Mark to trigger firmware swap on next boot. + /// + /// # 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_updated_blocking(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic_blocking(aligned, SWAP_MAGIC, flash) + } + + /// 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, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic_blocking(aligned, BOOT_MAGIC, flash) + } + + fn set_magic_blocking( + &mut self, + aligned: &mut [u8], + magic: u8, + flash: &mut F, + ) -> Result<(), F::Error> { + flash.read(self.state.from as u32, aligned)?; + + if aligned.iter().any(|&b| b != magic) { + aligned.fill(0); + + flash.write(self.state.from as u32, aligned)?; + flash.erase(self.state.from as u32, self.state.to as u32)?; + + aligned.fill(magic); + flash.write(self.state.from as u32, aligned)?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware_blocking( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), F::Error> { + assert!(data.len() >= F::ERASE_SIZE); + + flash.erase( + (self.dfu.from + offset) as u32, + (self.dfu.from + offset + data.len()) as u32, + )?; + + trace!( + "Erased from {} to {}", + self.dfu.from + offset, + self.dfu.from + offset + data.len() + ); + + FirmwareWriter(self).write_firmware_blocking(offset, data, flash, block_size)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning a `FirmwareWriter`. + /// + /// 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 { + flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; + + trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); + + Ok(FirmwareWriter(self)) + } +} + +/// FirmwareWriter allows writing blocks to an already erased flash. +pub struct FirmwareWriter<'a>(&'a mut FirmwareUpdater); + +impl<'a> FirmwareWriter<'a> { + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), F::Error> { + trace!( + "Writing firmware at offset 0x{:x} len {}", + self.0.dfu.from + offset, + data.len() + ); + + let mut write_offset = self.0.dfu.from + offset; for chunk in data.chunks(block_size) { trace!("Wrote chunk at {}: {:?}", write_offset, chunk); flash.write(write_offset as u32, chunk).await?; @@ -702,6 +830,50 @@ impl FirmwareUpdater { Ok(()) } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware_blocking( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), F::Error> { + trace!( + "Writing firmware at offset 0x{:x} len {}", + self.0.dfu.from + offset, + data.len() + ); + + let mut write_offset = self.0.dfu.from + offset; + for chunk in data.chunks(block_size) { + trace!("Wrote chunk at {}: {:?}", write_offset, chunk); + flash.write(write_offset as u32, chunk)?; + write_offset += chunk.len(); + } + /* + trace!("Wrote data, reading back for verification"); + + let mut buf: [u8; 4096] = [0; 4096]; + let mut data_offset = 0; + let mut read_offset = self.dfu.from + offset; + for chunk in buf.chunks_mut(block_size) { + flash.read(read_offset as u32, chunk).await?; + trace!("Read chunk at {}: {:?}", read_offset, chunk); + assert_eq!(&data[data_offset..data_offset + block_size], chunk); + read_offset += chunk.len(); + data_offset += chunk.len(); + } + */ + + Ok(()) + } } #[cfg(test)] -- cgit From b2a327a85884f822d011964bcd44b463b301467f Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 26 Sep 2022 06:53:40 +0200 Subject: Add get_state helpers to allow self-testing before calling mark_booted --- embassy-boot/boot/src/lib.rs | 56 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 1c4d2d47f..6f22d08ea 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -604,6 +604,21 @@ impl FirmwareUpdater { self.dfu.len() } + /// 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, flash: &mut F, aligned: &mut [u8]) -> Result { + flash.read(self.state.from as u32, aligned).await?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety @@ -673,8 +688,8 @@ impl FirmwareUpdater { self.dfu.from + offset + data.len() ); - FirmwareWriter(self) - .write_firmware(offset, data, flash, block_size) + FirmwareWriter(self.dfu) + .write_block(offset, data, flash, block_size) .await?; Ok(()) @@ -690,13 +705,28 @@ impl FirmwareUpdater { trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); - Ok(FirmwareWriter(self)) + Ok(FirmwareWriter(self.dfu)) } // // Blocking API // + /// 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, flash: &mut F, aligned: &mut [u8]) -> Result { + flash.read(self.state.from as u32, aligned)?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety @@ -764,7 +794,7 @@ impl FirmwareUpdater { self.dfu.from + offset + data.len() ); - FirmwareWriter(self).write_firmware_blocking(offset, data, flash, block_size)?; + FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?; Ok(()) } @@ -779,14 +809,14 @@ impl FirmwareUpdater { trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); - Ok(FirmwareWriter(self)) + Ok(FirmwareWriter(self.dfu)) } } /// FirmwareWriter allows writing blocks to an already erased flash. -pub struct FirmwareWriter<'a>(&'a mut FirmwareUpdater); +pub struct FirmwareWriter(Partition); -impl<'a> FirmwareWriter<'a> { +impl FirmwareWriter { /// Write data to a flash page. /// /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. @@ -794,7 +824,7 @@ impl<'a> FirmwareWriter<'a> { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware( + pub async fn write_block( &mut self, offset: usize, data: &[u8], @@ -803,11 +833,11 @@ impl<'a> FirmwareWriter<'a> { ) -> Result<(), F::Error> { trace!( "Writing firmware at offset 0x{:x} len {}", - self.0.dfu.from + offset, + self.0.from + offset, data.len() ); - let mut write_offset = self.0.dfu.from + offset; + let mut write_offset = self.0.from + offset; for chunk in data.chunks(block_size) { trace!("Wrote chunk at {}: {:?}", write_offset, chunk); flash.write(write_offset as u32, chunk).await?; @@ -838,7 +868,7 @@ impl<'a> FirmwareWriter<'a> { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( + pub fn write_block_blocking( &mut self, offset: usize, data: &[u8], @@ -847,11 +877,11 @@ impl<'a> FirmwareWriter<'a> { ) -> Result<(), F::Error> { trace!( "Writing firmware at offset 0x{:x} len {}", - self.0.dfu.from + offset, + self.0.from + offset, data.len() ); - let mut write_offset = self.0.dfu.from + offset; + let mut write_offset = self.0.from + offset; for chunk in data.chunks(block_size) { trace!("Wrote chunk at {}: {:?}", write_offset, chunk); flash.write(write_offset as u32, chunk)?; -- cgit From 6fa74b0c022c41c9ac6dd0b937ef402846cbdfae Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 26 Sep 2022 10:36:21 +0200 Subject: make prepare_update async --- embassy-boot/boot/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 6f22d08ea..8286601ec 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -700,8 +700,8 @@ impl FirmwareUpdater { /// /// Using this instead of `write_firmware` allows for an optimized API in /// exchange for added complexity. - pub fn prepare_update(&mut self, flash: &mut F) -> Result { - flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; + pub async fn prepare_update(&mut self, flash: &mut F) -> Result { + flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?; trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); -- cgit From 0b2d6996e8f35496ee1242bf80a213eb36121a7a Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Sat, 29 Oct 2022 15:16:09 +0200 Subject: Fix ascii table in BootLoader doc comment Signed-off-by: Daniel Bevenius --- embassy-boot/boot/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 8286601ec..429323ec9 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -150,7 +150,7 @@ impl BootLoader { /// +-----------+------------+--------+--------+--------+--------+ /// | Active | 0 | 1 | 2 | 3 | - | /// | DFU | 0 | 3 | 2 | 1 | X | - /// +-----------+-------+--------+--------+--------+--------+ + /// +-----------+------------+--------+--------+--------+--------+ /// /// The algorithm starts by copying 'backwards', and after the first step, the layout is /// as follows: -- cgit From 89821846d77d85d940b87cfa4f62171bd532b27c Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 23 Nov 2022 14:48:51 +0100 Subject: fix: add required metadata for embassy-boot --- embassy-boot/boot/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 429323ec9..76b14bc8c 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,7 +1,7 @@ #![feature(type_alias_impl_trait)] #![no_std] #![warn(missing_docs)] -#![doc = include_str!("../../README.md")] +#![doc = include_str!("../README.md")] mod fmt; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -- cgit From b0529bc943c9da0eb5f43335d06779d6064b765a Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 6 Jan 2023 22:21:39 +1100 Subject: Support codesigning in the firmware updater This commit provides a method to verify that firmware has been signed with a private key given its public key. The implementation uses ed25519-dalek as the signature verifier. An "ed25519" feature is required to enable the functionality. When disabled (the default), calling the firmware updater's verify method will return a failure. --- embassy-boot/boot/src/lib.rs | 367 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 353 insertions(+), 14 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 76b14bc8c..be254e9d7 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -52,6 +52,16 @@ pub enum BootError { BadMagic, } +#[cfg(feature = "defmt")] +impl defmt::Format for BootError { + fn format(&self, fmt: defmt::Formatter) { + match self { + BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), + BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), + } + } +} + impl From for BootError where E: NorFlashError, @@ -557,6 +567,33 @@ where self.state } } +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to /// 'mess up' the internal bootloader state @@ -609,7 +646,11 @@ impl FirmwareUpdater { /// 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, flash: &mut F, aligned: &mut [u8]) -> Result { + pub async fn get_state( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result { flash.read(self.state.from as u32, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { @@ -619,12 +660,126 @@ impl FirmwareUpdater { } } + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: usize, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let _end = self.dfu.from + _update_len; + let _read_size = _aligned.len(); + + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_end <= self.dfu.to); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned).await?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned).await?; + digest.update(&_aligned[0..remaining]); + } + + public_key + .verify(&digest.finalize(), &signature) + .map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Sha512, Signature}; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned).await?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned).await?; + digest.update(&_aligned[0..remaining]); + } + + let message = digest.finalize(); + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC, _flash).await + } + /// Mark to trigger firmware swap on next boot. /// /// # 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_updated(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic(aligned, SWAP_MAGIC, flash).await } @@ -634,7 +789,11 @@ 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. - pub async fn mark_booted(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + pub async fn mark_booted( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic(aligned, BOOT_MAGIC, flash).await } @@ -644,7 +803,7 @@ impl FirmwareUpdater { aligned: &mut [u8], magic: u8, flash: &mut F, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { flash.read(self.state.from as u32, aligned).await?; if aligned.iter().any(|&b| b != magic) { @@ -672,7 +831,7 @@ impl FirmwareUpdater { data: &[u8], flash: &mut F, block_size: usize, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); flash @@ -700,7 +859,10 @@ 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, flash: &mut F) -> Result { + pub async fn prepare_update( + &mut self, + flash: &mut F, + ) -> Result { flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?; trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); @@ -717,7 +879,11 @@ impl FirmwareUpdater { /// 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, flash: &mut F, aligned: &mut [u8]) -> Result { + pub fn get_state_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result { flash.read(self.state.from as u32, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { @@ -727,12 +893,126 @@ impl FirmwareUpdater { } } + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::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( + &mut self, + _flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: usize, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let _end = self.dfu.from + _update_len; + let _read_size = _aligned.len(); + + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_end <= self.dfu.to); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned)?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned)?; + digest.update(&_aligned[0..remaining]); + } + + public_key + .verify(&digest.finalize(), &signature) + .map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Sha512, Signature}; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned)?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned)?; + digest.update(&_aligned[0..remaining]); + } + + let message = digest.finalize(); + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash) + } + /// Mark to trigger firmware swap on next boot. /// /// # 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_updated_blocking(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + #[cfg(not(feature = "_verify"))] + pub fn mark_updated_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic_blocking(aligned, SWAP_MAGIC, flash) } @@ -742,7 +1022,11 @@ 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. - pub fn mark_booted_blocking(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> { + pub fn mark_booted_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); self.set_magic_blocking(aligned, BOOT_MAGIC, flash) } @@ -752,7 +1036,7 @@ impl FirmwareUpdater { aligned: &mut [u8], magic: u8, flash: &mut F, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { flash.read(self.state.from as u32, aligned)?; if aligned.iter().any(|&b| b != magic) { @@ -780,7 +1064,7 @@ impl FirmwareUpdater { data: &[u8], flash: &mut F, block_size: usize, - ) -> Result<(), F::Error> { + ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); flash.erase( @@ -804,7 +1088,10 @@ impl FirmwareUpdater { /// /// 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 { + pub fn prepare_update_blocking( + &mut self, + flash: &mut F, + ) -> Result { flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); @@ -953,6 +1240,7 @@ mod tests { } #[test] + #[cfg(not(feature = "_verify"))] fn test_swap_state() { const STATE: Partition = Partition::new(0, 4096); const ACTIVE: Partition = Partition::new(4096, 61440); @@ -1022,6 +1310,7 @@ mod tests { } #[test] + #[cfg(not(feature = "_verify"))] fn test_separate_flash_active_page_biggest() { const STATE: Partition = Partition::new(2048, 4096); const ACTIVE: Partition = Partition::new(4096, 16384); @@ -1074,6 +1363,7 @@ mod tests { } #[test] + #[cfg(not(feature = "_verify"))] fn test_separate_flash_dfu_page_biggest() { const STATE: Partition = Partition::new(2048, 4096); const ACTIVE: Partition = Partition::new(4096, 16384); @@ -1133,6 +1423,55 @@ mod tests { assert_partitions(ACTIVE, DFU, STATE, 4096, 4); } + #[test] + #[cfg(feature = "_verify")] + fn test_verify() { + // The following key setup is based on: + // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example + + use ed25519_dalek::Keypair; + use rand::rngs::OsRng; + + let mut csprng = OsRng {}; + let keypair: Keypair = Keypair::generate(&mut csprng); + + use ed25519_dalek::{Digest, Sha512, Signature, Signer}; + let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; + let mut digest = Sha512::new(); + digest.update(&firmware); + let message = digest.finalize(); + let signature: Signature = keypair.sign(&message); + + use ed25519_dalek::PublicKey; + 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>([0xff; 8192]); + + let firmware_len = firmware.len(); + + let mut write_buf = [0; 4096]; + write_buf[0..firmware_len].copy_from_slice(firmware); + NorFlash::write(&mut flash, DFU.from as u32, &write_buf).unwrap(); + + // On with the test + + let mut updater = FirmwareUpdater::new(DFU, 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, + &mut aligned, + )) + .is_ok()); + } struct MemFlash([u8; SIZE]); impl NorFlash @@ -1171,7 +1510,7 @@ mod tests { impl ReadNorFlash for MemFlash { - const READ_SIZE: usize = 4; + const READ_SIZE: usize = 1; fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { let len = buf.len(); @@ -1194,7 +1533,7 @@ mod tests { impl AsyncReadNorFlash for MemFlash { - const READ_SIZE: usize = 4; + const READ_SIZE: usize = 1; type ReadFuture<'a> = impl Future> + 'a; fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { -- cgit From bc0cb43307c2a46330ce253505203dbc607bcc6c Mon Sep 17 00:00:00 2001 From: Mehmet Ali Anil Date: Mon, 6 Mar 2023 22:08:47 +0100 Subject: Bump embedded-storage-async to 0.4 --- embassy-boot/boot/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index be254e9d7..b5dad5046 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -5,7 +5,7 @@ mod fmt; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use embedded_storage_async::nor_flash::AsyncNorFlash; +use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; const BOOT_MAGIC: u8 = 0xD0; const SWAP_MAGIC: u8 = 0xF0; @@ -1199,7 +1199,7 @@ mod tests { use core::future::Future; use embedded_storage::nor_flash::ErrorType; - use embedded_storage_async::nor_flash::AsyncReadNorFlash; + use embedded_storage_async::nor_flash::ReadNorFlash as AsyncReadNorFlash; use futures::executor::block_on; use super::*; -- cgit From ba9afbc26d06ab38065cbff5b17a7f76db297ad4 Mon Sep 17 00:00:00 2001 From: sander Date: Wed, 22 Mar 2023 16:49:49 +0100 Subject: embassy-boot: add default nightly feature, makes it possible to compile with the stable compiler --- embassy-boot/boot/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 0df44f36e..7ce0c664a 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(async_fn_in_trait)] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![allow(incomplete_features)] #![no_std] #![warn(missing_docs)] @@ -6,6 +6,8 @@ mod fmt; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; + +#[cfg(feature = "nightly")] use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; const BOOT_MAGIC: u8 = 0xD0; @@ -647,6 +649,7 @@ impl FirmwareUpdater { /// 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`. + #[cfg(feature = "nightly")] pub async fn get_state( &mut self, flash: &mut F, @@ -776,6 +779,7 @@ impl FirmwareUpdater { /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. #[cfg(not(feature = "_verify"))] + #[cfg(feature = "nightly")] pub async fn mark_updated( &mut self, flash: &mut F, @@ -790,6 +794,7 @@ 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(feature = "nightly")] pub async fn mark_booted( &mut self, flash: &mut F, @@ -799,6 +804,7 @@ impl FirmwareUpdater { self.set_magic(aligned, BOOT_MAGIC, flash).await } + #[cfg(feature = "nightly")] async fn set_magic( &mut self, aligned: &mut [u8], @@ -826,6 +832,7 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. + #[cfg(feature = "nightly")] pub async fn write_firmware( &mut self, offset: usize, @@ -860,6 +867,7 @@ impl FirmwareUpdater { /// /// Using this instead of `write_firmware` allows for an optimized API in /// exchange for added complexity. + #[cfg(feature = "nightly")] pub async fn prepare_update( &mut self, flash: &mut F, @@ -1112,6 +1120,7 @@ impl FirmwareWriter { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. + #[cfg(feature = "nightly")] pub async fn write_block( &mut self, offset: usize, -- cgit From 373760a56b1bad13ebcec19247ee3ee4ae4cb07c Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Fri, 31 Mar 2023 08:05:37 +0200 Subject: Split bootloader implementation into multiple files --- embassy-boot/boot/src/boot_loader.rs | 526 +++++++++++++ embassy-boot/boot/src/firmware_updater.rs | 537 +++++++++++++ embassy-boot/boot/src/firmware_writer.rs | 97 +++ embassy-boot/boot/src/lib.rs | 1183 +---------------------------- embassy-boot/boot/src/partition.rs | 22 + 5 files changed, 1194 insertions(+), 1171 deletions(-) create mode 100644 embassy-boot/boot/src/boot_loader.rs create mode 100644 embassy-boot/boot/src/firmware_updater.rs create mode 100644 embassy-boot/boot/src/firmware_writer.rs create mode 100644 embassy-boot/boot/src/partition.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs new file mode 100644 index 000000000..ad6735112 --- /dev/null +++ b/embassy-boot/boot/src/boot_loader.rs @@ -0,0 +1,526 @@ +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; + +use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + +/// Errors returned by bootloader +#[derive(PartialEq, Eq, Debug)] +pub enum BootError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Invalid bootloader magic + BadMagic, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for BootError { + fn format(&self, fmt: defmt::Formatter) { + match self { + BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), + BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), + } + } +} + +impl From for BootError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + BootError::Flash(error.kind()) + } +} + +/// Extension of the embedded-storage flash type information with block size and erase value. +pub trait Flash: NorFlash + ReadNorFlash { + /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase + /// size of the flash, but for external QSPI flash modules, this can be lower. + const BLOCK_SIZE: usize; + /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value. + const ERASE_VALUE: u8 = 0xFF; +} + +/// Trait defining the flash handles used for active and DFU partition +pub trait FlashConfig { + /// Flash type used for the state partition. + type STATE: Flash; + /// Flash type used for the active partition. + type ACTIVE: Flash; + /// Flash type used for the dfu partition. + type DFU: Flash; + + /// 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; +} + +/// BootLoader works with any flash implementing embedded_storage and can also work with +/// different page sizes and flash write sizes. +pub struct BootLoader { + // Page with current state of bootloader. The state partition has the following format: + // | Range | Description | + // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + // | WRITE_SIZE - 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, +} + +impl BootLoader { + /// Create a new instance of a bootloader with the given 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 boot address for the active partition. + pub fn boot_address(&self) -> usize { + self.active.from + } + + /// Perform necessary boot preparations like swapping images. + /// + /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap + /// algorithm to work correctly. + /// + /// SWAPPING + /// + /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. + /// The swap index contains the copy progress, as to allow continuation of the copy process on + /// power failure. The index counter is represented within 1 or more pages (depending on total + /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) + /// contains a zero value. This ensures that index updates can be performed atomically and + /// avoid a situation where the wrong index value is set (page write size is "atomic"). + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 0 | 1 | 2 | 3 | - | + /// | DFU | 0 | 3 | 2 | 1 | X | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// The algorithm starts by copying 'backwards', and after the first step, the layout is + /// as follows: + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 1 | 1 | 2 | 1 | - | + /// | DFU | 1 | 3 | 2 | 1 | 3 | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// The next iteration performs the same steps + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 2 | 1 | 2 | 1 | - | + /// | DFU | 2 | 3 | 2 | 2 | 3 | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// And again until we're done + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 3 | 3 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// REVERTING + /// + /// The reverting algorithm uses the swap index to discover that images were swapped, but that + /// the application failed to mark the boot successful. In this case, the revert algorithm will + /// run. + /// + /// The revert index is located separately from the swap index, to ensure that revert can continue + /// on power failure. + /// + /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. + /// + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + //*/ + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// + /// + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 2 | 2 | 3 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Active | 3 | 1 | 2 | 3 | - | + /// | DFU | 3 | 3 | 2 | 1 | 3 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// + pub fn prepare_boot( + &mut self, + p: &mut P, + magic: &mut [u8], + page: &mut [u8], + ) -> Result { + // Ensure we have enough progress pages to store copy progress + assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE); + assert_eq!(magic.len(), P::STATE::WRITE_SIZE); + + // Copy contents from partition N to active + let state = self.read_state(p, magic)?; + 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, magic, page)? { + trace!("Swapping"); + self.swap(p, magic, page)?; + trace!("Swapping done"); + } else { + trace!("Reverting"); + self.revert(p, magic, page)?; + + // Overwrite magic and reset progress + let fstate = p.state(); + magic.fill(!P::STATE::ERASE_VALUE); + fstate.write(self.state.from as u32, magic)?; + fstate.erase(self.state.from as u32, self.state.to as u32)?; + + magic.fill(BOOT_MAGIC); + fstate.write(self.state.from as u32, magic)?; + } + } + Ok(state) + } + + fn is_swapped(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result { + let page_size = page.len(); + let page_count = self.active.len() / page_size; + let progress = self.current_progress(p, magic)?; + + Ok(progress >= page_count * 2) + } + + fn current_progress(&mut self, config: &mut P, aligned: &mut [u8]) -> Result { + let write_size = aligned.len(); + let max_index = ((self.state.len() - write_size) / write_size) - 1; + aligned.fill(!P::STATE::ERASE_VALUE); + + let flash = config.state(); + for i in 0..max_index { + flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?; + + if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { + return Ok(i); + } + } + Ok(max_index) + } + + fn update_progress(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { + let flash = p.state(); + let write_size = magic.len(); + let w = self.state.from + write_size + idx * write_size; + + let aligned = magic; + aligned.fill(!P::STATE::ERASE_VALUE); + flash.write(w as u32, aligned)?; + Ok(()) + } + + fn active_addr(&self, n: usize, page_size: usize) -> usize { + self.active.from + n * page_size + } + + fn dfu_addr(&self, n: usize, page_size: usize) -> usize { + self.dfu.from + n * page_size + } + + fn copy_page_once_to_active( + &mut self, + idx: usize, + from_page: usize, + to_page: usize, + p: &mut P, + magic: &mut [u8], + page: &mut [u8], + ) -> Result<(), BootError> { + let buf = page; + if self.current_progress(p, magic)? <= idx { + let mut offset = from_page; + for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { + p.dfu().read(offset as u32, chunk)?; + offset += chunk.len(); + } + + p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?; + + let mut offset = to_page; + for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { + p.active().write(offset as u32, chunk)?; + offset += chunk.len(); + } + self.update_progress(idx, p, magic)?; + } + Ok(()) + } + + fn copy_page_once_to_dfu( + &mut self, + idx: usize, + from_page: usize, + to_page: usize, + p: &mut P, + magic: &mut [u8], + page: &mut [u8], + ) -> Result<(), BootError> { + let buf = page; + if self.current_progress(p, magic)? <= idx { + let mut offset = from_page; + for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { + p.active().read(offset as u32, chunk)?; + offset += chunk.len(); + } + + p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?; + + let mut offset = to_page; + for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { + p.dfu().write(offset as u32, chunk)?; + offset += chunk.len(); + } + self.update_progress(idx, p, magic)?; + } + Ok(()) + } + + fn swap(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { + let page_size = page.len(); + let page_count = self.active.len() / page_size; + trace!("Page count: {}", page_count); + for page_num in 0..page_count { + trace!("COPY PAGE {}", page_num); + // Copy active page to the 'next' DFU page. + let active_page = self.active_addr(page_count - 1 - page_num, page_size); + let dfu_page = self.dfu_addr(page_count - page_num, page_size); + //trace!("Copy active {} to dfu {}", active_page, dfu_page); + self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?; + + // Copy DFU page to the active page + let active_page = self.active_addr(page_count - 1 - page_num, page_size); + let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size); + //trace!("Copy dfy {} to active {}", dfu_page, active_page); + self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; + } + + Ok(()) + } + + fn revert(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { + let page_size = page.len(); + let page_count = self.active.len() / page_size; + for page_num in 0..page_count { + // Copy the bad active page to the DFU page + let active_page = self.active_addr(page_num, page_size); + let dfu_page = self.dfu_addr(page_num, page_size); + self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?; + + // Copy the DFU page back to the active page + let active_page = self.active_addr(page_num, page_size); + let dfu_page = self.dfu_addr(page_num + 1, page_size); + self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; + } + + Ok(()) + } + + fn read_state(&mut self, config: &mut P, magic: &mut [u8]) -> Result { + let flash = config.state(); + flash.read(self.state.from as u32, magic)?; + + if !magic.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } +} + +fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) { + assert_eq!(active.len() % page_size, 0); + assert_eq!(dfu.len() % page_size, 0); + assert!(dfu.len() - active.len() >= page_size); + assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size); +} + +/// A flash wrapper implementing the Flash and embedded_storage traits. +pub struct BootFlash +where + F: NorFlash + ReadNorFlash, +{ + flash: F, +} + +impl BootFlash +where + F: NorFlash + ReadNorFlash, +{ + /// Create a new instance of a bootable flash + pub fn new(flash: F) -> Self { + Self { flash } + } +} + +impl Flash for BootFlash +where + F: NorFlash + ReadNorFlash, +{ + const BLOCK_SIZE: usize = BLOCK_SIZE; + const ERASE_VALUE: u8 = ERASE_VALUE; +} + +impl ErrorType for BootFlash +where + F: ReadNorFlash + NorFlash, +{ + type Error = F::Error; +} + +impl NorFlash for BootFlash +where + F: ReadNorFlash + 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: ReadNorFlash + 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: Flash, +{ + flash: &'a mut F, +} + +impl<'a, F> SingleFlashConfig<'a, F> +where + F: Flash, +{ + /// 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: Flash, +{ + 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: Flash, + STATE: Flash, + DFU: Flash, +{ + active: &'a mut ACTIVE, + state: &'a mut STATE, + dfu: &'a mut DFU, +} + +impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> +where + ACTIVE: Flash, + STATE: Flash, + DFU: Flash, +{ + /// 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: Flash, + STATE: Flash, + DFU: Flash, +{ + 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 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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); + } +} diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs new file mode 100644 index 000000000..2d8712277 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -0,0 +1,537 @@ +use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; +use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + +use crate::{FirmwareWriter, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + 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, +} + +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 usize, + &__bootloader_dfu_end as *const u32 as usize, + ) + }; + let state = unsafe { + Partition::new( + &__bootloader_state_start as *const u32 as usize, + &__bootloader_state_end as *const u32 as usize, + ) + }; + + trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); + trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); + FirmwareUpdater::new(dfu, state) + } +} + +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 } + } + + /// Return the length of the DFU area + pub fn firmware_len(&self) -> usize { + self.dfu.len() + } + + /// 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, + flash: &mut F, + aligned: &mut [u8], + ) -> Result { + flash.read(self.state.from as u32, aligned).await?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: usize, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let _end = self.dfu.from + _update_len; + let _read_size = _aligned.len(); + + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_end <= self.dfu.to); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned).await?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned).await?; + digest.update(&_aligned[0..remaining]); + } + + public_key + .verify(&digest.finalize(), &signature) + .map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Sha512, Signature}; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned).await?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned).await?; + digest.update(&_aligned[0..remaining]); + } + + let message = digest.finalize(); + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC, _flash).await + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC, flash).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, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC, flash).await + } + + async fn set_magic( + &mut self, + aligned: &mut [u8], + magic: u8, + flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + flash.read(self.state.from as u32, aligned).await?; + + if aligned.iter().any(|&b| b != magic) { + aligned.fill(0); + + flash.write(self.state.from as u32, aligned).await?; + flash.erase(self.state.from as u32, self.state.to as u32).await?; + + aligned.fill(magic); + flash.write(self.state.from as u32, aligned).await?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= F::ERASE_SIZE); + + flash + .erase( + (self.dfu.from + offset) as u32, + (self.dfu.from + offset + data.len()) as u32, + ) + .await?; + + trace!( + "Erased from {} to {}", + self.dfu.from + offset, + self.dfu.from + offset + data.len() + ); + + FirmwareWriter(self.dfu) + .write_block(offset, data, flash, block_size) + .await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning a `FirmwareWriter`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub async fn prepare_update( + &mut self, + flash: &mut F, + ) -> Result { + flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?; + + trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); + + Ok(FirmwareWriter(self.dfu)) + } + + // + // Blocking API + // + + /// 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, + flash: &mut F, + aligned: &mut [u8], + ) -> Result { + flash.read(self.state.from as u32, aligned)?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::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( + &mut self, + _flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: usize, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let _end = self.dfu.from + _update_len; + let _read_size = _aligned.len(); + + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_end <= self.dfu.to); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned)?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned)?; + digest.update(&_aligned[0..remaining]); + } + + public_key + .verify(&digest.finalize(), &signature) + .map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Sha512, Signature}; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut digest = Sha512::new(); + + let mut offset = self.dfu.from; + let last_offset = _end / _read_size * _read_size; + + while offset < last_offset { + _flash.read(offset as u32, _aligned)?; + digest.update(&_aligned); + offset += _read_size; + } + + let remaining = _end % _read_size; + + if remaining > 0 { + _flash.read(last_offset as u32, _aligned)?; + digest.update(&_aligned[0..remaining]); + } + + let message = digest.finalize(); + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub fn mark_updated_blocking( + &mut self, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic_blocking(aligned, SWAP_MAGIC, flash) + } + + /// 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, + flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic_blocking(aligned, BOOT_MAGIC, flash) + } + + fn set_magic_blocking( + &mut self, + aligned: &mut [u8], + magic: u8, + flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + flash.read(self.state.from as u32, aligned)?; + + if aligned.iter().any(|&b| b != magic) { + aligned.fill(0); + + flash.write(self.state.from as u32, aligned)?; + flash.erase(self.state.from as u32, self.state.to as u32)?; + + aligned.fill(magic); + flash.write(self.state.from as u32, aligned)?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware_blocking( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= F::ERASE_SIZE); + + flash.erase( + (self.dfu.from + offset) as u32, + (self.dfu.from + offset + data.len()) as u32, + )?; + + trace!( + "Erased from {} to {}", + self.dfu.from + offset, + self.dfu.from + offset + data.len() + ); + + FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning a `FirmwareWriter`. + /// + /// 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 { + flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; + + trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); + + Ok(FirmwareWriter(self.dfu)) + } +} diff --git a/embassy-boot/boot/src/firmware_writer.rs b/embassy-boot/boot/src/firmware_writer.rs new file mode 100644 index 000000000..f992021bb --- /dev/null +++ b/embassy-boot/boot/src/firmware_writer.rs @@ -0,0 +1,97 @@ +use embedded_storage::nor_flash::NorFlash; +use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + +use crate::Partition; + +/// FirmwareWriter allows writing blocks to an already erased flash. +pub struct FirmwareWriter(pub(crate) Partition); + +impl FirmwareWriter { + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_block( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), F::Error> { + trace!( + "Writing firmware at offset 0x{:x} len {}", + self.0.from + offset, + data.len() + ); + + let mut write_offset = self.0.from + offset; + for chunk in data.chunks(block_size) { + trace!("Wrote chunk at {}: {:?}", write_offset, chunk); + flash.write(write_offset as u32, chunk).await?; + write_offset += chunk.len(); + } + /* + trace!("Wrote data, reading back for verification"); + + let mut buf: [u8; 4096] = [0; 4096]; + let mut data_offset = 0; + let mut read_offset = self.dfu.from + offset; + for chunk in buf.chunks_mut(block_size) { + flash.read(read_offset as u32, chunk).await?; + trace!("Read chunk at {}: {:?}", read_offset, chunk); + assert_eq!(&data[data_offset..data_offset + block_size], chunk); + read_offset += chunk.len(); + data_offset += chunk.len(); + } + */ + + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_block_blocking( + &mut self, + offset: usize, + data: &[u8], + flash: &mut F, + block_size: usize, + ) -> Result<(), F::Error> { + trace!( + "Writing firmware at offset 0x{:x} len {}", + self.0.from + offset, + data.len() + ); + + let mut write_offset = self.0.from + offset; + for chunk in data.chunks(block_size) { + trace!("Wrote chunk at {}: {:?}", write_offset, chunk); + flash.write(write_offset as u32, chunk)?; + write_offset += chunk.len(); + } + /* + trace!("Wrote data, reading back for verification"); + + let mut buf: [u8; 4096] = [0; 4096]; + let mut data_offset = 0; + let mut read_offset = self.dfu.from + offset; + for chunk in buf.chunks_mut(block_size) { + flash.read(read_offset as u32, chunk).await?; + trace!("Read chunk at {}: {:?}", read_offset, chunk); + assert_eq!(&data[data_offset..data_offset + block_size], chunk); + read_offset += chunk.len(); + data_offset += chunk.len(); + } + */ + + Ok(()) + } +} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 0df44f36e..a2259411f 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -5,34 +5,18 @@ #![doc = include_str!("../README.md")] mod fmt; -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; +mod boot_loader; +mod firmware_updater; +mod firmware_writer; +mod partition; -const BOOT_MAGIC: u8 = 0xD0; -const SWAP_MAGIC: u8 = 0xF0; +pub use boot_loader::{BootError, BootFlash, BootLoader, Flash, FlashConfig, MultiFlashConfig, SingleFlashConfig}; +pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; +pub use firmware_writer::FirmwareWriter; +pub use partition::Partition; -/// A region in flash used by the bootloader. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Partition { - /// Start of the flash region. - pub from: usize, - /// End of the flash region. - pub to: usize, -} - -impl Partition { - /// Create a new partition with the provided range - pub const fn new(from: usize, to: usize) -> Self { - Self { from, to } - } - - /// Return the length of the partition - #[allow(clippy::len_without_is_empty)] - pub const fn len(&self) -> usize { - self.to - self.from - } -} +pub(crate) const BOOT_MAGIC: u8 = 0xD0; +pub(crate) const SWAP_MAGIC: u8 = 0xF0; /// The state of the bootloader after running prepare. #[derive(PartialEq, Eq, Debug)] @@ -44,34 +28,6 @@ pub enum State { Swap, } -/// Errors returned by bootloader -#[derive(PartialEq, Eq, Debug)] -pub enum BootError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Invalid bootloader magic - BadMagic, -} - -#[cfg(feature = "defmt")] -impl defmt::Format for BootError { - fn format(&self, fmt: defmt::Formatter) { - match self { - BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), - BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), - } - } -} - -impl From for BootError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - BootError::Flash(error.kind()) - } -} - /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. #[repr(align(32))] pub struct AlignedBuffer(pub [u8; N]); @@ -88,1118 +44,12 @@ impl AsMut<[u8]> for AlignedBuffer { } } -/// Extension of the embedded-storage flash type information with block size and erase value. -pub trait Flash: NorFlash + ReadNorFlash { - /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase - /// size of the flash, but for external QSPI flash modules, this can be lower. - const BLOCK_SIZE: usize; - /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value. - const ERASE_VALUE: u8 = 0xFF; -} - -/// Trait defining the flash handles used for active and DFU partition -pub trait FlashConfig { - /// Flash type used for the state partition. - type STATE: Flash; - /// Flash type used for the active partition. - type ACTIVE: Flash; - /// Flash type used for the dfu partition. - type DFU: Flash; - - /// 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; -} - -/// BootLoader works with any flash implementing embedded_storage and can also work with -/// different page sizes and flash write sizes. -pub struct BootLoader { - // Page with current state of bootloader. The state partition has the following format: - // | Range | Description | - // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - // | WRITE_SIZE - 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, -} - -impl BootLoader { - /// Create a new instance of a bootloader with the given 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 boot address for the active partition. - pub fn boot_address(&self) -> usize { - self.active.from - } - - /// Perform necessary boot preparations like swapping images. - /// - /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap - /// algorithm to work correctly. - /// - /// SWAPPING - /// - /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. - /// The swap index contains the copy progress, as to allow continuation of the copy process on - /// power failure. The index counter is represented within 1 or more pages (depending on total - /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) - /// contains a zero value. This ensures that index updates can be performed atomically and - /// avoid a situation where the wrong index value is set (page write size is "atomic"). - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 0 | 1 | 2 | 3 | - | - /// | DFU | 0 | 3 | 2 | 1 | X | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// The algorithm starts by copying 'backwards', and after the first step, the layout is - /// as follows: - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 1 | 1 | 2 | 1 | - | - /// | DFU | 1 | 3 | 2 | 1 | 3 | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// The next iteration performs the same steps - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 2 | 1 | 2 | 1 | - | - /// | DFU | 2 | 3 | 2 | 2 | 3 | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// And again until we're done - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 3 | 3 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// REVERTING - /// - /// The reverting algorithm uses the swap index to discover that images were swapped, but that - /// the application failed to mark the boot successful. In this case, the revert algorithm will - /// run. - /// - /// The revert index is located separately from the swap index, to ensure that revert can continue - /// on power failure. - /// - /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. - /// - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - //*/ - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// - /// - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 2 | 2 | 3 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Active | 3 | 1 | 2 | 3 | - | - /// | DFU | 3 | 3 | 2 | 1 | 3 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// - pub fn prepare_boot( - &mut self, - p: &mut P, - magic: &mut [u8], - page: &mut [u8], - ) -> Result { - // Ensure we have enough progress pages to store copy progress - assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE); - assert_eq!(magic.len(), P::STATE::WRITE_SIZE); - - // Copy contents from partition N to active - let state = self.read_state(p, magic)?; - 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, magic, page)? { - trace!("Swapping"); - self.swap(p, magic, page)?; - trace!("Swapping done"); - } else { - trace!("Reverting"); - self.revert(p, magic, page)?; - - // Overwrite magic and reset progress - let fstate = p.state(); - magic.fill(!P::STATE::ERASE_VALUE); - fstate.write(self.state.from as u32, magic)?; - fstate.erase(self.state.from as u32, self.state.to as u32)?; - - magic.fill(BOOT_MAGIC); - fstate.write(self.state.from as u32, magic)?; - } - } - Ok(state) - } - - fn is_swapped(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result { - let page_size = page.len(); - let page_count = self.active.len() / page_size; - let progress = self.current_progress(p, magic)?; - - Ok(progress >= page_count * 2) - } - - fn current_progress(&mut self, config: &mut P, aligned: &mut [u8]) -> Result { - let write_size = aligned.len(); - let max_index = ((self.state.len() - write_size) / write_size) - 1; - aligned.fill(!P::STATE::ERASE_VALUE); - - let flash = config.state(); - for i in 0..max_index { - flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?; - - if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { - return Ok(i); - } - } - Ok(max_index) - } - - fn update_progress(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { - let flash = p.state(); - let write_size = magic.len(); - let w = self.state.from + write_size + idx * write_size; - - let aligned = magic; - aligned.fill(!P::STATE::ERASE_VALUE); - flash.write(w as u32, aligned)?; - Ok(()) - } - - fn active_addr(&self, n: usize, page_size: usize) -> usize { - self.active.from + n * page_size - } - - fn dfu_addr(&self, n: usize, page_size: usize) -> usize { - self.dfu.from + n * page_size - } - - fn copy_page_once_to_active( - &mut self, - idx: usize, - from_page: usize, - to_page: usize, - p: &mut P, - magic: &mut [u8], - page: &mut [u8], - ) -> Result<(), BootError> { - let buf = page; - if self.current_progress(p, magic)? <= idx { - let mut offset = from_page; - for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { - p.dfu().read(offset as u32, chunk)?; - offset += chunk.len(); - } - - p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?; - - let mut offset = to_page; - for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { - p.active().write(offset as u32, chunk)?; - offset += chunk.len(); - } - self.update_progress(idx, p, magic)?; - } - Ok(()) - } - - fn copy_page_once_to_dfu( - &mut self, - idx: usize, - from_page: usize, - to_page: usize, - p: &mut P, - magic: &mut [u8], - page: &mut [u8], - ) -> Result<(), BootError> { - let buf = page; - if self.current_progress(p, magic)? <= idx { - let mut offset = from_page; - for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { - p.active().read(offset as u32, chunk)?; - offset += chunk.len(); - } - - p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?; - - let mut offset = to_page; - for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { - p.dfu().write(offset as u32, chunk)?; - offset += chunk.len(); - } - self.update_progress(idx, p, magic)?; - } - Ok(()) - } - - fn swap(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { - let page_size = page.len(); - let page_count = self.active.len() / page_size; - trace!("Page count: {}", page_count); - for page_num in 0..page_count { - trace!("COPY PAGE {}", page_num); - // Copy active page to the 'next' DFU page. - let active_page = self.active_addr(page_count - 1 - page_num, page_size); - let dfu_page = self.dfu_addr(page_count - page_num, page_size); - //trace!("Copy active {} to dfu {}", active_page, dfu_page); - self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?; - - // Copy DFU page to the active page - let active_page = self.active_addr(page_count - 1 - page_num, page_size); - let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size); - //trace!("Copy dfy {} to active {}", dfu_page, active_page); - self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; - } - - Ok(()) - } - - fn revert(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { - let page_size = page.len(); - let page_count = self.active.len() / page_size; - for page_num in 0..page_count { - // Copy the bad active page to the DFU page - let active_page = self.active_addr(page_num, page_size); - let dfu_page = self.dfu_addr(page_num, page_size); - self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?; - - // Copy the DFU page back to the active page - let active_page = self.active_addr(page_num, page_size); - let dfu_page = self.dfu_addr(page_num + 1, page_size); - self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; - } - - Ok(()) - } - - fn read_state(&mut self, config: &mut P, magic: &mut [u8]) -> Result { - let flash = config.state(); - flash.read(self.state.from as u32, magic)?; - - if !magic.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } -} - -fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) { - assert_eq!(active.len() % page_size, 0); - assert_eq!(dfu.len() % page_size, 0); - assert!(dfu.len() - active.len() >= page_size); - assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size); -} - -/// Convenience provider that uses a single flash for all partitions. -pub struct SingleFlashConfig<'a, F> -where - F: Flash, -{ - flash: &'a mut F, -} - -impl<'a, F> SingleFlashConfig<'a, F> -where - F: Flash, -{ - /// 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: Flash, -{ - 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 - } -} - -/// A flash wrapper implementing the Flash and embedded_storage traits. -pub struct BootFlash -where - F: NorFlash + ReadNorFlash, -{ - flash: F, -} - -impl BootFlash -where - F: NorFlash + ReadNorFlash, -{ - /// Create a new instance of a bootable flash - pub fn new(flash: F) -> Self { - Self { flash } - } -} - -impl Flash for BootFlash -where - F: NorFlash + ReadNorFlash, -{ - const BLOCK_SIZE: usize = BLOCK_SIZE; - const ERASE_VALUE: u8 = ERASE_VALUE; -} - -impl ErrorType for BootFlash -where - F: ReadNorFlash + NorFlash, -{ - type Error = F::Error; -} - -impl NorFlash for BootFlash -where - F: ReadNorFlash + 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: ReadNorFlash + 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 flash provider that uses separate flash instances for each partition. -pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: Flash, - STATE: Flash, - DFU: Flash, -{ - active: &'a mut ACTIVE, - state: &'a mut STATE, - dfu: &'a mut DFU, -} - -impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: Flash, - STATE: Flash, - DFU: Flash, -{ - /// 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: Flash, - STATE: Flash, - DFU: Flash, -{ - 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 - } -} -/// Errors returned by FirmwareUpdater -#[derive(Debug)] -pub enum FirmwareUpdaterError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Signature errors. - Signature(signature::Error), -} - -#[cfg(feature = "defmt")] -impl defmt::Format for FirmwareUpdaterError { - fn format(&self, fmt: defmt::Formatter) { - match self { - FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), - FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), - } - } -} - -impl From for FirmwareUpdaterError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - 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, -} - -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 usize, - &__bootloader_dfu_end as *const u32 as usize, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as usize, - &__bootloader_state_end as *const u32 as usize, - ) - }; - - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - FirmwareUpdater::new(dfu, state) - } -} - -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 } - } - - /// Return the length of the DFU area - pub fn firmware_len(&self) -> usize { - self.dfu.len() - } - - /// 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, - flash: &mut F, - aligned: &mut [u8], - ) -> Result { - flash.read(self.state.from as u32, aligned).await?; - - if !aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - /// - /// # Safety - /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - #[cfg(feature = "_verify")] - pub async fn verify_and_mark_updated( - &mut self, - _flash: &mut F, - _public_key: &[u8], - _signature: &[u8], - _update_len: usize, - _aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let _end = self.dfu.from + _update_len; - let _read_size = _aligned.len(); - - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_end <= self.dfu.to); - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - - let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned).await?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned).await?; - digest.update(&_aligned[0..remaining]); - } - - public_key - .verify(&digest.finalize(), &signature) - .map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Sha512, Signature}; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; - let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; - let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; - let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - - let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned).await?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned).await?; - digest.update(&_aligned[0..remaining]); - } - - let message = digest.finalize(); - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.set_magic(_aligned, SWAP_MAGIC, _flash).await - } - - /// Mark to trigger firmware swap on next boot. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(not(feature = "_verify"))] - pub async fn mark_updated( - &mut self, - flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, SWAP_MAGIC, flash).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, - flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, BOOT_MAGIC, flash).await - } - - async fn set_magic( - &mut self, - aligned: &mut [u8], - magic: u8, - flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - flash.read(self.state.from as u32, aligned).await?; - - if aligned.iter().any(|&b| b != magic) { - aligned.fill(0); - - flash.write(self.state.from as u32, aligned).await?; - flash.erase(self.state.from as u32, self.state.to as u32).await?; - - aligned.fill(magic); - flash.write(self.state.from as u32, aligned).await?; - } - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); - - flash - .erase( - (self.dfu.from + offset) as u32, - (self.dfu.from + offset + data.len()) as u32, - ) - .await?; - - trace!( - "Erased from {} to {}", - self.dfu.from + offset, - self.dfu.from + offset + data.len() - ); - - FirmwareWriter(self.dfu) - .write_block(offset, data, flash, block_size) - .await?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning a `FirmwareWriter`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub async fn prepare_update( - &mut self, - flash: &mut F, - ) -> Result { - flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?; - - trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); - - Ok(FirmwareWriter(self.dfu)) - } - - // - // Blocking API - // - - /// 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, - flash: &mut F, - aligned: &mut [u8], - ) -> Result { - flash.read(self.state.from as u32, aligned)?; - - if !aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - /// - /// # Safety - /// - /// The `_aligned` buffer must have a size of F::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( - &mut self, - _flash: &mut F, - _public_key: &[u8], - _signature: &[u8], - _update_len: usize, - _aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let _end = self.dfu.from + _update_len; - let _read_size = _aligned.len(); - - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_end <= self.dfu.to); - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - - let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned)?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned)?; - digest.update(&_aligned[0..remaining]); - } - - public_key - .verify(&digest.finalize(), &signature) - .map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Sha512, Signature}; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; - let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; - let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; - let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - - let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned)?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned)?; - digest.update(&_aligned[0..remaining]); - } - - let message = digest.finalize(); - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash) - } - - /// Mark to trigger firmware swap on next boot. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(not(feature = "_verify"))] - pub fn mark_updated_blocking( - &mut self, - flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, SWAP_MAGIC, flash) - } - - /// 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, - flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, BOOT_MAGIC, flash) - } - - fn set_magic_blocking( - &mut self, - aligned: &mut [u8], - magic: u8, - flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - flash.read(self.state.from as u32, aligned)?; - - if aligned.iter().any(|&b| b != magic) { - aligned.fill(0); - - flash.write(self.state.from as u32, aligned)?; - flash.erase(self.state.from as u32, self.state.to as u32)?; - - aligned.fill(magic); - flash.write(self.state.from as u32, aligned)?; - } - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); - - flash.erase( - (self.dfu.from + offset) as u32, - (self.dfu.from + offset + data.len()) as u32, - )?; - - trace!( - "Erased from {} to {}", - self.dfu.from + offset, - self.dfu.from + offset + data.len() - ); - - FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning a `FirmwareWriter`. - /// - /// 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 { - flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; - - trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); - - Ok(FirmwareWriter(self.dfu)) - } -} - -/// FirmwareWriter allows writing blocks to an already erased flash. -pub struct FirmwareWriter(Partition); - -impl FirmwareWriter { - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_block( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), F::Error> { - trace!( - "Writing firmware at offset 0x{:x} len {}", - self.0.from + offset, - data.len() - ); - - let mut write_offset = self.0.from + offset; - for chunk in data.chunks(block_size) { - trace!("Wrote chunk at {}: {:?}", write_offset, chunk); - flash.write(write_offset as u32, chunk).await?; - write_offset += chunk.len(); - } - /* - trace!("Wrote data, reading back for verification"); - - let mut buf: [u8; 4096] = [0; 4096]; - let mut data_offset = 0; - let mut read_offset = self.dfu.from + offset; - for chunk in buf.chunks_mut(block_size) { - flash.read(read_offset as u32, chunk).await?; - trace!("Read chunk at {}: {:?}", read_offset, chunk); - assert_eq!(&data[data_offset..data_offset + block_size], chunk); - read_offset += chunk.len(); - data_offset += chunk.len(); - } - */ - - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_block_blocking( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), F::Error> { - trace!( - "Writing firmware at offset 0x{:x} len {}", - self.0.from + offset, - data.len() - ); - - let mut write_offset = self.0.from + offset; - for chunk in data.chunks(block_size) { - trace!("Wrote chunk at {}: {:?}", write_offset, chunk); - flash.write(write_offset as u32, chunk)?; - write_offset += chunk.len(); - } - /* - trace!("Wrote data, reading back for verification"); - - let mut buf: [u8; 4096] = [0; 4096]; - let mut data_offset = 0; - let mut read_offset = self.dfu.from + offset; - for chunk in buf.chunks_mut(block_size) { - flash.read(read_offset as u32, chunk).await?; - trace!("Read chunk at {}: {:?}", read_offset, chunk); - assert_eq!(&data[data_offset..data_offset + block_size], chunk); - read_offset += chunk.len(); - data_offset += chunk.len(); - } - */ - - Ok(()) - } -} - #[cfg(test)] mod tests { use core::convert::Infallible; - use embedded_storage::nor_flash::ErrorType; - use embedded_storage_async::nor_flash::ReadNorFlash as AsyncReadNorFlash; + use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; use futures::executor::block_on; use super::*; @@ -1414,15 +264,6 @@ mod tests { } } - #[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); - } - #[test] #[cfg(feature = "_verify")] fn test_verify() { diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs new file mode 100644 index 000000000..46f80a23c --- /dev/null +++ b/embassy-boot/boot/src/partition.rs @@ -0,0 +1,22 @@ +/// A region in flash used by the bootloader. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Partition { + /// Start of the flash region. + pub from: usize, + /// End of the flash region. + pub to: usize, +} + +impl Partition { + /// Create a new partition with the provided range + pub const fn new(from: usize, to: usize) -> Self { + Self { from, to } + } + + /// Return the length of the partition + #[allow(clippy::len_without_is_empty)] + pub const fn len(&self) -> usize { + self.to - self.from + } +} -- cgit From 42931b51f25ca22d7df3a6e8e98bfab7904eb11e Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Fri, 31 Mar 2023 10:18:19 +0200 Subject: Let bootloader partition have read/write/erase operations This change should not have any breaking changes. --- embassy-boot/boot/src/boot_loader.rs | 102 +++++++++--------- embassy-boot/boot/src/firmware_updater.rs | 174 ++++++++++-------------------- embassy-boot/boot/src/firmware_writer.rs | 54 ++-------- embassy-boot/boot/src/partition.rs | 88 ++++++++++++++- 4 files changed, 195 insertions(+), 223 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index ad6735112..e2e361e3c 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -79,7 +79,7 @@ impl BootLoader { Self { active, dfu, state } } - /// Return the boot address for the active partition. + /// Return the offset of the active partition into the active flash. pub fn boot_address(&self) -> usize { self.active.from } @@ -193,13 +193,13 @@ impl BootLoader { self.revert(p, magic, page)?; // Overwrite magic and reset progress - let fstate = p.state(); + let state_flash = p.state(); magic.fill(!P::STATE::ERASE_VALUE); - fstate.write(self.state.from as u32, magic)?; - fstate.erase(self.state.from as u32, self.state.to as u32)?; + self.state.write_blocking(state_flash, 0, magic)?; + self.state.wipe_blocking(state_flash)?; magic.fill(BOOT_MAGIC); - fstate.write(self.state.from as u32, magic)?; + self.state.write_blocking(state_flash, 0, magic)?; } } Ok(state) @@ -218,9 +218,10 @@ impl BootLoader { let max_index = ((self.state.len() - write_size) / write_size) - 1; aligned.fill(!P::STATE::ERASE_VALUE); - let flash = config.state(); + let state_flash = config.state(); for i in 0..max_index { - flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?; + self.state + .read_blocking(state_flash, (write_size + i * write_size) as u32, aligned)?; if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { return Ok(i); @@ -230,47 +231,39 @@ impl BootLoader { } fn update_progress(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { - let flash = p.state(); let write_size = magic.len(); - let w = self.state.from + write_size + idx * write_size; let aligned = magic; aligned.fill(!P::STATE::ERASE_VALUE); - flash.write(w as u32, aligned)?; + self.state + .write_blocking(p.state(), (write_size + idx * write_size) as u32, aligned)?; Ok(()) } - fn active_addr(&self, n: usize, page_size: usize) -> usize { - self.active.from + n * page_size - } - - fn dfu_addr(&self, n: usize, page_size: usize) -> usize { - self.dfu.from + n * page_size - } - fn copy_page_once_to_active( &mut self, idx: usize, - from_page: usize, - to_page: usize, + from_offset: u32, + to_offset: u32, p: &mut P, magic: &mut [u8], page: &mut [u8], ) -> Result<(), BootError> { let buf = page; if self.current_progress(p, magic)? <= idx { - let mut offset = from_page; + let mut offset = from_offset; for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { - p.dfu().read(offset as u32, chunk)?; - offset += chunk.len(); + self.dfu.read_blocking(p.dfu(), offset, chunk)?; + offset += chunk.len() as u32; } - p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?; + self.active + .erase_blocking(p.active(), to_offset, to_offset + buf.len() as u32)?; - let mut offset = to_page; + let mut offset = to_offset; for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { - p.active().write(offset as u32, chunk)?; - offset += chunk.len(); + self.active.write_blocking(p.active(), offset, chunk)?; + offset += chunk.len() as u32; } self.update_progress(idx, p, magic)?; } @@ -280,26 +273,27 @@ impl BootLoader { fn copy_page_once_to_dfu( &mut self, idx: usize, - from_page: usize, - to_page: usize, + from_offset: u32, + to_offset: u32, p: &mut P, magic: &mut [u8], page: &mut [u8], ) -> Result<(), BootError> { let buf = page; if self.current_progress(p, magic)? <= idx { - let mut offset = from_page; + let mut offset = from_offset; for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { - p.active().read(offset as u32, chunk)?; - offset += chunk.len(); + self.active.read_blocking(p.active(), offset, chunk)?; + offset += chunk.len() as u32; } - p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?; + self.dfu + .erase_blocking(p.dfu(), to_offset as u32, to_offset + buf.len() as u32)?; - let mut offset = to_page; + let mut offset = to_offset; for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { - p.dfu().write(offset as u32, chunk)?; - offset += chunk.len(); + self.dfu.write_blocking(p.dfu(), offset, chunk)?; + offset += chunk.len() as u32; } self.update_progress(idx, p, magic)?; } @@ -312,17 +306,20 @@ impl BootLoader { trace!("Page count: {}", page_count); for page_num in 0..page_count { trace!("COPY PAGE {}", page_num); + + let idx = page_num * 2; + // Copy active page to the 'next' DFU page. - let active_page = self.active_addr(page_count - 1 - page_num, page_size); - let dfu_page = self.dfu_addr(page_count - page_num, page_size); - //trace!("Copy active {} to dfu {}", active_page, dfu_page); - self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?; + let active_from_offset = ((page_count - 1 - page_num) * page_size) as u32; + let dfu_to_offset = ((page_count - page_num) * page_size) as u32; + //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); + self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, magic, page)?; // Copy DFU page to the active page - let active_page = self.active_addr(page_count - 1 - page_num, page_size); - let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size); - //trace!("Copy dfy {} to active {}", dfu_page, active_page); - self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; + let active_to_offset = ((page_count - 1 - page_num) * page_size) as u32; + let dfu_from_offset = ((page_count - 1 - page_num) * page_size) as u32; + //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); + self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, magic, page)?; } Ok(()) @@ -332,23 +329,24 @@ impl BootLoader { let page_size = page.len(); let page_count = self.active.len() / page_size; for page_num in 0..page_count { + let idx = page_count * 2 + page_num * 2; + // Copy the bad active page to the DFU page - let active_page = self.active_addr(page_num, page_size); - let dfu_page = self.dfu_addr(page_num, page_size); - self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?; + let active_from_offset = (page_num * page_size) as u32; + let dfu_to_offset = (page_num * page_size) as u32; + self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, magic, page)?; // Copy the DFU page back to the active page - let active_page = self.active_addr(page_num, page_size); - let dfu_page = self.dfu_addr(page_num + 1, page_size); - self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?; + let active_to_offset = (page_num * page_size) as u32; + let dfu_from_offset = ((page_num + 1) * page_size) as u32; + self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, magic, page)?; } Ok(()) } fn read_state(&mut self, config: &mut P, magic: &mut [u8]) -> Result { - let flash = config.state(); - flash.read(self.state.from as u32, magic)?; + self.state.read_blocking(config.state(), 0, magic)?; if !magic.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 2d8712277..af1ba114a 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -84,10 +84,10 @@ impl FirmwareUpdater { /// `mark_booted`. pub async fn get_state( &mut self, - flash: &mut F, + state_flash: &mut F, aligned: &mut [u8], ) -> Result { - flash.read(self.state.from as u32, aligned).await?; + self.state.read(state_flash, 0, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -115,17 +115,16 @@ impl FirmwareUpdater { #[cfg(feature = "_verify")] pub async fn verify_and_mark_updated( &mut self, - _flash: &mut F, + _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _end = self.dfu.from + _update_len; let _read_size = _aligned.len(); assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_end <= self.dfu.to); + assert!(_update_len <= self.dfu.len()); #[cfg(feature = "ed25519-dalek")] { @@ -137,21 +136,10 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned).await?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned).await?; - digest.update(&_aligned[0..remaining]); + for offset in (0.._update_len).step_by(_aligned.len()) { + self.dfu.read(_state_and_dfu_flash, offset as u32, _aligned).await?; + let len = core::cmp::min(_update_len - offset, _aligned.len()); + digest.update(&_aligned[..len]); } public_key @@ -173,21 +161,10 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned).await?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned).await?; - digest.update(&_aligned[0..remaining]); + for offset in (0.._update_len).step_by(_aligned.len()) { + self.dfu.read(_state_and_dfu_flash, offset as u32, _aligned).await?; + let len = core::cmp::min(_update_len - offset, _aligned.len()); + digest.update(&_aligned[..len]); } let message = digest.finalize(); @@ -202,7 +179,7 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic(_aligned, SWAP_MAGIC, _flash).await + self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await } /// Mark to trigger firmware swap on next boot. @@ -213,11 +190,11 @@ impl FirmwareUpdater { #[cfg(not(feature = "_verify"))] pub async fn mark_updated( &mut self, - flash: &mut F, + state_flash: &mut F, aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, SWAP_MAGIC, flash).await + self.set_magic(aligned, SWAP_MAGIC, state_flash).await } /// Mark firmware boot successful and stop rollback on reset. @@ -227,29 +204,29 @@ impl FirmwareUpdater { /// 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, - flash: &mut F, + state_flash: &mut F, aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, BOOT_MAGIC, flash).await + self.set_magic(aligned, BOOT_MAGIC, state_flash).await } async fn set_magic( &mut self, aligned: &mut [u8], magic: u8, - flash: &mut F, + state_flash: &mut F, ) -> Result<(), FirmwareUpdaterError> { - flash.read(self.state.from as u32, aligned).await?; + self.state.read(state_flash, 0, aligned).await?; if aligned.iter().any(|&b| b != magic) { aligned.fill(0); - flash.write(self.state.from as u32, aligned).await?; - flash.erase(self.state.from as u32, self.state.to as u32).await?; + self.state.write(state_flash, 0, aligned).await?; + self.state.wipe(state_flash).await?; aligned.fill(magic); - flash.write(self.state.from as u32, aligned).await?; + self.state.write(state_flash, 0, aligned).await?; } Ok(()) } @@ -265,26 +242,17 @@ impl FirmwareUpdater { &mut self, offset: usize, data: &[u8], - flash: &mut F, + dfu_flash: &mut F, block_size: usize, ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); - flash - .erase( - (self.dfu.from + offset) as u32, - (self.dfu.from + offset + data.len()) as u32, - ) + self.dfu + .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) .await?; - trace!( - "Erased from {} to {}", - self.dfu.from + offset, - self.dfu.from + offset + data.len() - ); - FirmwareWriter(self.dfu) - .write_block(offset, data, flash, block_size) + .write_block(offset, data, dfu_flash, block_size) .await?; Ok(()) @@ -297,11 +265,9 @@ impl FirmwareUpdater { /// exchange for added complexity. pub async fn prepare_update( &mut self, - flash: &mut F, + dfu_flash: &mut F, ) -> Result { - flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?; - - trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); + self.dfu.wipe(dfu_flash).await?; Ok(FirmwareWriter(self.dfu)) } @@ -317,10 +283,10 @@ impl FirmwareUpdater { /// `mark_booted`. pub fn get_state_blocking( &mut self, - flash: &mut F, + state_flash: &mut F, aligned: &mut [u8], ) -> Result { - flash.read(self.state.from as u32, aligned)?; + self.state.read_blocking(state_flash, 0, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -348,7 +314,7 @@ impl FirmwareUpdater { #[cfg(feature = "_verify")] pub fn verify_and_mark_updated_blocking( &mut self, - _flash: &mut F, + _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], _update_len: usize, @@ -370,21 +336,10 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned)?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned)?; - digest.update(&_aligned[0..remaining]); + for offset in (0.._update_len).step_by(_aligned.len()) { + self.dfu.read_blocking(_state_and_dfu_flash, offset as u32, _aligned)?; + let len = core::cmp::min(_update_len - offset, _aligned.len()); + digest.update(&_aligned[..len]); } public_key @@ -406,21 +361,10 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - - let mut offset = self.dfu.from; - let last_offset = _end / _read_size * _read_size; - - while offset < last_offset { - _flash.read(offset as u32, _aligned)?; - digest.update(&_aligned); - offset += _read_size; - } - - let remaining = _end % _read_size; - - if remaining > 0 { - _flash.read(last_offset as u32, _aligned)?; - digest.update(&_aligned[0..remaining]); + for offset in (0.._update_len).step_by(_aligned.len()) { + self.dfu.read_blocking(_state_and_dfu_flash, offset as u32, _aligned)?; + let len = core::cmp::min(_update_len - offset, _aligned.len()); + digest.update(&_aligned[..len]); } let message = digest.finalize(); @@ -435,7 +379,7 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash) + self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) } /// Mark to trigger firmware swap on next boot. @@ -446,11 +390,11 @@ impl FirmwareUpdater { #[cfg(not(feature = "_verify"))] pub fn mark_updated_blocking( &mut self, - flash: &mut F, + state_flash: &mut F, aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, SWAP_MAGIC, flash) + self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) } /// Mark firmware boot successful and stop rollback on reset. @@ -460,29 +404,29 @@ impl FirmwareUpdater { /// 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, - flash: &mut F, + state_flash: &mut F, aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, BOOT_MAGIC, flash) + self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) } fn set_magic_blocking( &mut self, aligned: &mut [u8], magic: u8, - flash: &mut F, + state_flash: &mut F, ) -> Result<(), FirmwareUpdaterError> { - flash.read(self.state.from as u32, aligned)?; + self.state.read_blocking(state_flash, 0, aligned)?; if aligned.iter().any(|&b| b != magic) { aligned.fill(0); - flash.write(self.state.from as u32, aligned)?; - flash.erase(self.state.from as u32, self.state.to as u32)?; + self.state.write_blocking(state_flash, 0, aligned)?; + self.state.wipe_blocking(state_flash)?; aligned.fill(magic); - flash.write(self.state.from as u32, aligned)?; + self.state.write_blocking(state_flash, 0, aligned)?; } Ok(()) } @@ -498,23 +442,15 @@ impl FirmwareUpdater { &mut self, offset: usize, data: &[u8], - flash: &mut F, + dfu_flash: &mut F, block_size: usize, ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); - flash.erase( - (self.dfu.from + offset) as u32, - (self.dfu.from + offset + data.len()) as u32, - )?; + self.dfu + .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; - trace!( - "Erased from {} to {}", - self.dfu.from + offset, - self.dfu.from + offset + data.len() - ); - - FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?; + FirmwareWriter(self.dfu).write_block_blocking(offset, data, dfu_flash, block_size)?; Ok(()) } @@ -528,9 +464,7 @@ impl FirmwareUpdater { &mut self, flash: &mut F, ) -> Result { - flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?; - - trace!("Erased from {} to {}", self.dfu.from, self.dfu.to); + self.dfu.wipe_blocking(flash)?; Ok(FirmwareWriter(self.dfu)) } diff --git a/embassy-boot/boot/src/firmware_writer.rs b/embassy-boot/boot/src/firmware_writer.rs index f992021bb..46079e731 100644 --- a/embassy-boot/boot/src/firmware_writer.rs +++ b/embassy-boot/boot/src/firmware_writer.rs @@ -21,32 +21,11 @@ impl FirmwareWriter { flash: &mut F, block_size: usize, ) -> Result<(), F::Error> { - trace!( - "Writing firmware at offset 0x{:x} len {}", - self.0.from + offset, - data.len() - ); - - let mut write_offset = self.0.from + offset; + let mut offset = offset as u32; for chunk in data.chunks(block_size) { - trace!("Wrote chunk at {}: {:?}", write_offset, chunk); - flash.write(write_offset as u32, chunk).await?; - write_offset += chunk.len(); - } - /* - trace!("Wrote data, reading back for verification"); - - let mut buf: [u8; 4096] = [0; 4096]; - let mut data_offset = 0; - let mut read_offset = self.dfu.from + offset; - for chunk in buf.chunks_mut(block_size) { - flash.read(read_offset as u32, chunk).await?; - trace!("Read chunk at {}: {:?}", read_offset, chunk); - assert_eq!(&data[data_offset..data_offset + block_size], chunk); - read_offset += chunk.len(); - data_offset += chunk.len(); + self.0.write(flash, offset, chunk).await?; + offset += chunk.len() as u32; } - */ Ok(()) } @@ -65,32 +44,11 @@ impl FirmwareWriter { flash: &mut F, block_size: usize, ) -> Result<(), F::Error> { - trace!( - "Writing firmware at offset 0x{:x} len {}", - self.0.from + offset, - data.len() - ); - - let mut write_offset = self.0.from + offset; + let mut offset = offset as u32; for chunk in data.chunks(block_size) { - trace!("Wrote chunk at {}: {:?}", write_offset, chunk); - flash.write(write_offset as u32, chunk)?; - write_offset += chunk.len(); - } - /* - trace!("Wrote data, reading back for verification"); - - let mut buf: [u8; 4096] = [0; 4096]; - let mut data_offset = 0; - let mut read_offset = self.dfu.from + offset; - for chunk in buf.chunks_mut(block_size) { - flash.read(read_offset as u32, chunk).await?; - trace!("Read chunk at {}: {:?}", read_offset, chunk); - assert_eq!(&data[data_offset..data_offset + block_size], chunk); - read_offset += chunk.len(); - data_offset += chunk.len(); + self.0.write_blocking(flash, offset, chunk)?; + offset += chunk.len() as u32; } - */ Ok(()) } diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index 46f80a23c..9918fb836 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -1,10 +1,13 @@ +use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; +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 { - /// Start of the flash region. + /// The offset into the flash where the partition starts. pub from: usize, - /// End of the flash region. + /// The offset into the flash where the partition ends. pub to: usize, } @@ -14,9 +17,88 @@ impl Partition { Self { from, to } } - /// Return the length of the partition + /// Return the size of the partition #[allow(clippy::len_without_is_empty)] pub const fn len(&self) -> usize { self.to - self.from } + + /// Read from the partition on the provided flash + pub(crate) 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 + pub(crate) 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 + pub(crate) 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 + 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(crate) 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(crate) 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(crate) 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(()) + } } -- cgit From d9d6fd6d70f3f9971c6db65b6962199f9da7913c Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Fri, 31 Mar 2023 10:28:47 +0200 Subject: Add erase and wipe tests --- embassy-boot/boot/src/lib.rs | 3 ++- embassy-boot/boot/src/partition.rs | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index a2259411f..4c28d7aa4 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -313,7 +313,8 @@ mod tests { )) .is_ok()); } - struct MemFlash([u8; SIZE]); + + pub struct MemFlash(pub [u8; SIZE]); impl NorFlash for MemFlash diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index 9918fb836..3ccd4dd76 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -102,3 +102,49 @@ impl Partition { Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::tests::MemFlash; + use crate::Partition; + + #[test] + fn can_erase() { + let mut flash = MemFlash::<1024, 64, 4>([0x00; 1024]); + let partition = Partition::new(256, 512); + + partition.erase_blocking(&mut flash, 64, 192).unwrap(); + + for (index, byte) in flash.0.iter().copied().enumerate().take(256 + 64) { + assert_eq!(0x00, byte, "Index {}", index); + } + + for (index, byte) in flash.0.iter().copied().enumerate().skip(256 + 64).take(128) { + assert_eq!(0xFF, byte, "Index {}", index); + } + + for (index, byte) in flash.0.iter().copied().enumerate().skip(256 + 64 + 128) { + assert_eq!(0x00, byte, "Index {}", index); + } + } + + #[test] + fn can_wipe() { + let mut flash = MemFlash::<1024, 64, 4>([0x00; 1024]); + let partition = Partition::new(256, 512); + + partition.wipe_blocking(&mut flash).unwrap(); + + for (index, byte) in flash.0.iter().copied().enumerate().take(256) { + assert_eq!(0x00, byte, "Index {}", index); + } + + for (index, byte) in flash.0.iter().copied().enumerate().skip(256).take(256) { + assert_eq!(0xFF, byte, "Index {}", index); + } + + for (index, byte) in flash.0.iter().copied().enumerate().skip(512) { + assert_eq!(0x00, byte, "Index {}", index); + } + } +} -- cgit From b1e2195b49129bcccf42121a6f39248bab9e341f Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Mon, 3 Apr 2023 14:50:41 +0200 Subject: Remove FirmwareWriter FirmwareWriter currently has a "max-write-size" parameter, but this is a limitation that should be handled by chunking inside the NorFlash driver, and not "up here" in user code. In case that the driver (e.g. qspi driver) is unaware of any max-write limitations, one could simply add an intermediate NorFlash adapter providing the chunk'ing capability. --- embassy-boot/boot/src/firmware_updater.rs | 25 +++++--------- embassy-boot/boot/src/firmware_writer.rs | 55 ------------------------------- embassy-boot/boot/src/lib.rs | 8 ++--- 3 files changed, 12 insertions(+), 76 deletions(-) delete mode 100644 embassy-boot/boot/src/firmware_writer.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index af1ba114a..dd22b2fa7 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -1,7 +1,7 @@ use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; -use crate::{FirmwareWriter, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; /// Errors returned by FirmwareUpdater #[derive(Debug)] @@ -243,7 +243,6 @@ impl FirmwareUpdater { offset: usize, data: &[u8], dfu_flash: &mut F, - block_size: usize, ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); @@ -251,25 +250,23 @@ impl FirmwareUpdater { .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) .await?; - FirmwareWriter(self.dfu) - .write_block(offset, data, dfu_flash, block_size) - .await?; + self.dfu.write(dfu_flash, offset as u32, data).await?; Ok(()) } /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning a `FirmwareWriter`. + /// returning its `Partition`. /// /// Using this instead of `write_firmware` allows for an optimized API in /// exchange for added complexity. pub async fn prepare_update( &mut self, dfu_flash: &mut F, - ) -> Result { + ) -> Result { self.dfu.wipe(dfu_flash).await?; - Ok(FirmwareWriter(self.dfu)) + Ok(self.dfu) } // @@ -443,29 +440,25 @@ impl FirmwareUpdater { offset: usize, data: &[u8], dfu_flash: &mut F, - block_size: usize, ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= F::ERASE_SIZE); self.dfu .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; - FirmwareWriter(self.dfu).write_block_blocking(offset, data, dfu_flash, block_size)?; + self.dfu.write_blocking(dfu_flash, offset as u32, data)?; Ok(()) } /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning a `FirmwareWriter`. + /// 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 { + pub fn prepare_update_blocking(&mut self, flash: &mut F) -> Result { self.dfu.wipe_blocking(flash)?; - Ok(FirmwareWriter(self.dfu)) + Ok(self.dfu) } } diff --git a/embassy-boot/boot/src/firmware_writer.rs b/embassy-boot/boot/src/firmware_writer.rs deleted file mode 100644 index 46079e731..000000000 --- a/embassy-boot/boot/src/firmware_writer.rs +++ /dev/null @@ -1,55 +0,0 @@ -use embedded_storage::nor_flash::NorFlash; -use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; - -use crate::Partition; - -/// FirmwareWriter allows writing blocks to an already erased flash. -pub struct FirmwareWriter(pub(crate) Partition); - -impl FirmwareWriter { - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_block( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), F::Error> { - let mut offset = offset as u32; - for chunk in data.chunks(block_size) { - self.0.write(flash, offset, chunk).await?; - offset += chunk.len() as u32; - } - - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_block_blocking( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), F::Error> { - let mut offset = offset as u32; - for chunk in data.chunks(block_size) { - self.0.write_blocking(flash, offset, chunk)?; - offset += chunk.len() as u32; - } - - Ok(()) - } -} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4c28d7aa4..428e7ca2b 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -7,12 +7,10 @@ mod fmt; mod boot_loader; mod firmware_updater; -mod firmware_writer; mod partition; pub use boot_loader::{BootError, BootFlash, BootLoader, Flash, FlashConfig, MultiFlashConfig, SingleFlashConfig}; pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; -pub use firmware_writer::FirmwareWriter; pub use partition::Partition; pub(crate) const BOOT_MAGIC: u8 = 0xD0; @@ -109,7 +107,7 @@ mod tests { let mut updater = FirmwareUpdater::new(DFU, STATE); let mut offset = 0; for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, chunk, &mut flash, 4096)).unwrap(); + block_on(updater.write_firmware(offset, chunk, &mut flash)).unwrap(); offset += chunk.len(); } block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); @@ -182,7 +180,7 @@ mod tests { let mut offset = 0; for chunk in update.chunks(2048) { - block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap(); + block_on(updater.write_firmware(offset, chunk, &mut dfu)).unwrap(); offset += chunk.len(); } block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); @@ -235,7 +233,7 @@ mod tests { let mut offset = 0; for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap(); + block_on(updater.write_firmware(offset, chunk, &mut dfu)).unwrap(); offset += chunk.len(); } block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); -- cgit From 8aaffe82e71dfb2b3845c95bbf59ef4a34c7096c Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Mon, 3 Apr 2023 14:59:55 +0200 Subject: Add incremental hash to FirmwareUpdater This adds support for computing any hash over the update in the dtu area by providing a closure to the hash update function. --- embassy-boot/boot/src/firmware_updater.rs | 67 +++++++++++++++++++------------ embassy-boot/boot/src/lib.rs | 2 +- 2 files changed, 43 insertions(+), 26 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index af1ba114a..90157036a 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -118,13 +118,13 @@ impl FirmwareUpdater { _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], - _update_len: usize, + _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { let _read_size = _aligned.len(); assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.len()); + assert!(_update_len <= self.dfu.len() as u32); #[cfg(feature = "ed25519-dalek")] { @@ -136,11 +136,8 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read(_state_and_dfu_flash, offset as u32, _aligned).await?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + self.incremental_hash(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x)) + .await?; public_key .verify(&digest.finalize(), &signature) @@ -161,11 +158,8 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read(_state_and_dfu_flash, offset as u32, _aligned).await?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + self.incremental_hash(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x)) + .await?; let message = digest.finalize(); let r = public_key.verify(&message, &signature); @@ -182,6 +176,22 @@ impl FirmwareUpdater { self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await } + /// Iterate through the DFU and process all bytes with the provided closure. + pub async fn incremental_hash( + &mut self, + dfu_flash: &mut F, + update_len: u32, + aligned: &mut [u8], + mut update: impl FnMut(&[u8]), + ) -> Result<(), FirmwareUpdaterError> { + for offset in (0..update_len).step_by(aligned.len()) { + self.dfu.read(dfu_flash, offset, aligned).await?; + let len = core::cmp::min((update_len - offset) as usize, aligned.len()); + update(&aligned[..len]); + } + Ok(()) + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety @@ -317,14 +327,13 @@ impl FirmwareUpdater { _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], - _update_len: usize, + _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _end = self.dfu.from + _update_len; let _read_size = _aligned.len(); assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_end <= self.dfu.to); + assert!(_update_len <= self.dfu.len() as u32); #[cfg(feature = "ed25519-dalek")] { @@ -336,11 +345,7 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read_blocking(_state_and_dfu_flash, offset as u32, _aligned)?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + self.incremental_hash_blocking(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x))?; public_key .verify(&digest.finalize(), &signature) @@ -361,11 +366,7 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut digest = Sha512::new(); - for offset in (0.._update_len).step_by(_aligned.len()) { - self.dfu.read_blocking(_state_and_dfu_flash, offset as u32, _aligned)?; - let len = core::cmp::min(_update_len - offset, _aligned.len()); - digest.update(&_aligned[..len]); - } + self.incremental_hash_blocking(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x))?; let message = digest.finalize(); let r = public_key.verify(&message, &signature); @@ -382,6 +383,22 @@ impl FirmwareUpdater { self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) } + /// Iterate through the DFU and process all bytes with the provided closure. + pub fn incremental_hash_blocking( + &mut self, + dfu_flash: &mut F, + update_len: u32, + aligned: &mut [u8], + mut update: impl FnMut(&[u8]), + ) -> Result<(), FirmwareUpdaterError> { + for offset in (0..update_len).step_by(aligned.len()) { + self.dfu.read_blocking(dfu_flash, offset, aligned)?; + let len = core::cmp::min((update_len - offset) as usize, aligned.len()); + update(&aligned[..len]); + } + Ok(()) + } + /// Mark to trigger firmware swap on next boot. /// /// # Safety diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4c28d7aa4..6d0e2d8c2 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -308,7 +308,7 @@ mod tests { &mut flash, &public_key.to_bytes(), &signature.to_bytes(), - firmware_len, + firmware_len as u32, &mut aligned, )) .is_ok()); -- cgit From 7c11d85e1ea0d01e5e1b4d6564d258663d66509b Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Mon, 3 Apr 2023 15:33:20 +0200 Subject: Move MemFlash to separate module and add verify_erased_before_write verification --- embassy-boot/boot/src/lib.rs | 155 ++++----------------------- embassy-boot/boot/src/mem_flash.rs | 213 +++++++++++++++++++++++++++++++++++++ embassy-boot/boot/src/partition.rs | 18 ++-- 3 files changed, 244 insertions(+), 142 deletions(-) create mode 100644 embassy-boot/boot/src/mem_flash.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4c28d7aa4..a5795781f 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -8,6 +8,7 @@ mod fmt; mod boot_loader; mod firmware_updater; mod firmware_writer; +mod mem_flash; mod partition; pub use boot_loader::{BootError, BootFlash, BootLoader, Flash, FlashConfig, MultiFlashConfig, SingleFlashConfig}; @@ -46,13 +47,10 @@ impl AsMut<[u8]> for AlignedBuffer { #[cfg(test)] mod tests { - use core::convert::Infallible; - - use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; - use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; use futures::executor::block_on; use super::*; + use crate::mem_flash::MemFlash; /* #[test] @@ -75,8 +73,8 @@ mod tests { const ACTIVE: Partition = Partition::new(4096, 61440); const DFU: Partition = Partition::new(61440, 122880); - let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); - flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); + 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); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); @@ -95,14 +93,14 @@ mod tests { 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>([0xff; 131072]); + let mut flash = MemFlash::<131072, 4096, 4>::random().with_limited_erase_before_write_verification(4..); let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; let mut aligned = [0; 4]; for i in ACTIVE.from..ACTIVE.to { - flash.0[i] = original[i - ACTIVE.from]; + flash.mem[i] = original[i - ACTIVE.from]; } let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); @@ -124,12 +122,12 @@ mod tests { ); for i in ACTIVE.from..ACTIVE.to { - assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i); + assert_eq!(flash.mem[i], update[i - ACTIVE.from], "Index {}", i); } // First DFU page is untouched for i in DFU.from + 4096..DFU.to { - assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i); + assert_eq!(flash.mem[i], original[i - DFU.from - 4096], "Index {}", i); } // Running again should cause a revert @@ -141,12 +139,12 @@ mod tests { ); for i in ACTIVE.from..ACTIVE.to { - assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i); + assert_eq!(flash.mem[i], original[i - ACTIVE.from], "Index {}", i); } // Last page is untouched for i in DFU.from..DFU.to - 4096 { - assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i); + assert_eq!(flash.mem[i], update[i - DFU.from], "Index {}", i); } // Mark as booted @@ -166,16 +164,16 @@ mod tests { const ACTIVE: Partition = Partition::new(4096, 16384); const DFU: Partition = Partition::new(0, 16384); - let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); - let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); - let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); + let mut active = MemFlash::<16384, 4096, 8>::random(); + let mut dfu = MemFlash::<16384, 2048, 8>::random(); + let mut state = MemFlash::<4096, 128, 4>::random().with_limited_erase_before_write_verification(2048 + 4..); let mut aligned = [0; 4]; let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; for i in ACTIVE.from..ACTIVE.to { - active.0[i] = original[i - ACTIVE.from]; + active.mem[i] = original[i - ACTIVE.from]; } let mut updater = FirmwareUpdater::new(DFU, STATE); @@ -203,12 +201,12 @@ mod tests { ); for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); + assert_eq!(active.mem[i], update[i - ACTIVE.from], "Index {}", i); } // First DFU page is untouched for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); + assert_eq!(dfu.mem[i], original[i - DFU.from - 4096], "Index {}", i); } } @@ -220,15 +218,15 @@ mod tests { const DFU: Partition = Partition::new(0, 16384); let mut aligned = [0; 4]; - let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); - let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); - let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); + let mut active = MemFlash::<16384, 2048, 4>::random(); + let mut dfu = MemFlash::<16384, 4096, 8>::random(); + let mut state = MemFlash::<4096, 128, 4>::random().with_limited_erase_before_write_verification(2048 + 4..); let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; for i in ACTIVE.from..ACTIVE.to { - active.0[i] = original[i - ACTIVE.from]; + active.mem[i] = original[i - ACTIVE.from]; } let mut updater = FirmwareUpdater::new(DFU, STATE); @@ -255,12 +253,12 @@ mod tests { ); for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); + assert_eq!(active.mem[i], update[i - ACTIVE.from], "Index {}", i); } // First DFU page is untouched for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); + assert_eq!(dfu.mem[i], original[i - DFU.from - 4096], "Index {}", i); } } @@ -313,113 +311,4 @@ mod tests { )) .is_ok()); } - - pub struct MemFlash(pub [u8; SIZE]); - - impl NorFlash - for MemFlash - { - 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.0[i] = 0xFF; - } - Ok(()) - } - - fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - assert!(data.len() % WRITE_SIZE == 0); - assert!(offset as usize % WRITE_SIZE == 0); - assert!(offset as usize + data.len() <= SIZE); - - self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data); - - Ok(()) - } - } - - impl ErrorType - for MemFlash - { - type Error = Infallible; - } - - impl ReadNorFlash - for MemFlash - { - const READ_SIZE: usize = 1; - - fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { - let len = buf.len(); - buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); - Ok(()) - } - - fn capacity(&self) -> usize { - SIZE - } - } - - impl super::Flash - for MemFlash - { - const BLOCK_SIZE: usize = ERASE_SIZE; - const ERASE_VALUE: u8 = 0xFF; - } - - impl AsyncReadNorFlash - for MemFlash - { - const READ_SIZE: usize = 1; - - async fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { - let len = buf.len(); - buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); - Ok(()) - } - - fn capacity(&self) -> usize { - SIZE - } - } - - impl AsyncNorFlash - for MemFlash - { - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - async 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); - for i in from..to { - self.0[i] = 0xFF; - } - Ok(()) - } - - async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - info!("Writing {} bytes to 0x{:x}", data.len(), offset); - assert!(data.len() % WRITE_SIZE == 0); - assert!(offset as usize % WRITE_SIZE == 0); - assert!( - offset as usize + data.len() <= SIZE, - "OFFSET: {}, LEN: {}, FLASH SIZE: {}", - offset, - data.len(), - SIZE - ); - - self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data); - - Ok(()) - } - } } diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs new file mode 100644 index 000000000..e87ccd37a --- /dev/null +++ b/embassy-boot/boot/src/mem_flash.rs @@ -0,0 +1,213 @@ +#![allow(unused)] + +use core::ops::{Bound, Range, RangeBounds}; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +use crate::Flash; + +pub struct MemFlash { + pub mem: [u8; SIZE], + pub allow_same_write: bool, + pub verify_erased_before_write: Range, + pub pending_write_successes: Option, +} + +#[derive(Debug)] +pub struct MemFlashError; + +impl MemFlash { + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + allow_same_write: false, + verify_erased_before_write: 0..SIZE, + pending_write_successes: None, + } + } + + #[cfg(test)] + pub fn random() -> Self { + let mut mem = [0; SIZE]; + for byte in mem.iter_mut() { + *byte = rand::random::(); + } + Self { + mem, + allow_same_write: false, + verify_erased_before_write: 0..SIZE, + pending_write_successes: None, + } + } + + #[must_use] + pub fn allow_same_write(self, allow: bool) -> Self { + Self { + allow_same_write: allow, + ..self + } + } + + #[must_use] + pub fn with_limited_erase_before_write_verification>(self, verified_range: R) -> Self { + let start = match verified_range.start_bound() { + Bound::Included(start) => *start, + Bound::Excluded(start) => *start + 1, + Bound::Unbounded => 0, + }; + let end = match verified_range.end_bound() { + Bound::Included(end) => *end - 1, + Bound::Excluded(end) => *end, + Bound::Unbounded => self.mem.len(), + }; + Self { + verify_erased_before_write: start..end, + ..self + } + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xFF) + } +} + +impl Flash + for MemFlash +{ + const BLOCK_SIZE: usize = ERASE_SIZE; + const ERASE_VALUE: u8 = 0xFF; +} + +impl ErrorType + for MemFlash +{ + type Error = MemFlashError; +} + +impl NorFlashError for MemFlashError { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + 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> { + 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) + { + if self.allow_same_write && mem_byte == new_byte { + // Write does not change the flash memory which is allowed + } else { + if self.verify_erased_before_write.contains(&offset) { + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + } + *mem_byte &= *new_byte; + } + } + + Ok(()) + } +} + +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + ::read(self, offset, bytes) + } + + fn capacity(&self) -> usize { + ::capacity(self) + } +} + +impl AsyncNorFlash + for MemFlash +{ + 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> { + ::write(self, offset, bytes) + } +} + +#[cfg(test)] +mod tests { + use core::ops::Range; + + use embedded_storage::nor_flash::NorFlash; + + use super::MemFlash; + + #[test] + fn writes_only_flip_bits_from_1_to_0() { + let mut flash = MemFlash::<16, 16, 1>::default().with_limited_erase_before_write_verification(0..0); + + flash.write(0, &[0x55]).unwrap(); + flash.write(0, &[0xAA]).unwrap(); + + assert_eq!(0x00, flash.mem[0]); + } +} diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index 3ccd4dd76..1157e8cd8 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -105,45 +105,45 @@ impl Partition { #[cfg(test)] mod tests { - use crate::tests::MemFlash; + use crate::mem_flash::MemFlash; use crate::Partition; #[test] fn can_erase() { - let mut flash = MemFlash::<1024, 64, 4>([0x00; 1024]); + 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.0.iter().copied().enumerate().take(256 + 64) { + for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) { assert_eq!(0x00, byte, "Index {}", index); } - for (index, byte) in flash.0.iter().copied().enumerate().skip(256 + 64).take(128) { + 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.0.iter().copied().enumerate().skip(256 + 64 + 128) { + 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>([0x00; 1024]); + 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.0.iter().copied().enumerate().take(256) { + for (index, byte) in flash.mem.iter().copied().enumerate().take(256) { assert_eq!(0x00, byte, "Index {}", index); } - for (index, byte) in flash.0.iter().copied().enumerate().skip(256).take(256) { + for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) { assert_eq!(0xFF, byte, "Index {}", index); } - for (index, byte) in flash.0.iter().copied().enumerate().skip(512) { + for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) { assert_eq!(0x00, byte, "Index {}", index); } } -- cgit From df3a1e1b9d337c17e08faf41c6e3c50c35eb9a6c Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 07:18:29 +0200 Subject: Avoid write to not-erased magic This introduces an additional marker to the state partition right after the magic which indicates whether the current progress is valid or not. Validation in tests that we never write without an erase is added. There is currently a FIXME in the FirmwareUpdater. Let me know if we should take the erase value as a parameter. I opened a feature request in embedded-storage to get this value in the trait. Before this, the assumption about ERASE_VALUE=0xFF was the same. --- embassy-boot/boot/src/boot_loader.rs | 43 ++++++++++++++-------- embassy-boot/boot/src/firmware_updater.rs | 34 +++++++++++++++-- embassy-boot/boot/src/lib.rs | 6 +-- embassy-boot/boot/src/mem_flash.rs | 61 +------------------------------ 4 files changed, 63 insertions(+), 81 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index e2e361e3c..9d047f778 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -31,7 +31,7 @@ where } /// Extension of the embedded-storage flash type information with block size and erase value. -pub trait Flash: NorFlash + ReadNorFlash { +pub trait Flash: NorFlash { /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase /// size of the flash, but for external QSPI flash modules, this can be lower. const BLOCK_SIZE: usize; @@ -60,9 +60,11 @@ pub trait FlashConfig { /// different page sizes and flash write sizes. pub struct BootLoader { // Page with current state of bootloader. The state partition has the following format: - // | Range | Description | - // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - // | WRITE_SIZE - N | Progress index used while swapping or reverting | + // 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, @@ -192,12 +194,17 @@ impl BootLoader { trace!("Reverting"); self.revert(p, magic, page)?; - // Overwrite magic and reset progress let state_flash = p.state(); + + // Invalidate progress magic.fill(!P::STATE::ERASE_VALUE); - self.state.write_blocking(state_flash, 0, magic)?; + self.state + .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, magic)?; + + // Clear magic and progress self.state.wipe_blocking(state_flash)?; + // Set magic magic.fill(BOOT_MAGIC); self.state.write_blocking(state_flash, 0, magic)?; } @@ -215,28 +222,34 @@ impl BootLoader { fn current_progress(&mut self, config: &mut P, aligned: &mut [u8]) -> Result { let write_size = aligned.len(); - let max_index = ((self.state.len() - write_size) / write_size) - 1; + let max_index = ((self.state.len() - write_size) / write_size) - 2; aligned.fill(!P::STATE::ERASE_VALUE); let state_flash = config.state(); - for i in 0..max_index { + + self.state + .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, aligned)?; + if aligned.iter().any(|&b| b != P::STATE::ERASE_VALUE) { + // Progress is invalid + return Ok(max_index); + } + + for index in 0..max_index { self.state - .read_blocking(state_flash, (write_size + i * write_size) as u32, aligned)?; + .read_blocking(state_flash, (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?; if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { - return Ok(i); + return Ok(index); } } Ok(max_index) } - fn update_progress(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { - let write_size = magic.len(); - + fn update_progress(&mut self, index: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { let aligned = magic; aligned.fill(!P::STATE::ERASE_VALUE); self.state - .write_blocking(p.state(), (write_size + idx * write_size) as u32, aligned)?; + .write_blocking(p.state(), (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?; Ok(()) } @@ -360,7 +373,7 @@ fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_s assert_eq!(active.len() % page_size, 0); assert_eq!(dfu.len() % page_size, 0); assert!(dfu.len() - active.len() >= page_size); - assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size); + assert!(2 + 2 * (active.len() / page_size) <= state.len() / write_size); } /// A flash wrapper implementing the Flash and embedded_storage traits. diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index af1ba114a..fe3c0452f 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -220,11 +220,24 @@ impl FirmwareUpdater { self.state.read(state_flash, 0, aligned).await?; if aligned.iter().any(|&b| b != magic) { - aligned.fill(0); + // 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; + + 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_flash, 0, aligned).await?; + // Clear magic and progress self.state.wipe(state_flash).await?; + // Set magic aligned.fill(magic); self.state.write(state_flash, 0, aligned).await?; } @@ -420,11 +433,24 @@ impl FirmwareUpdater { self.state.read_blocking(state_flash, 0, aligned)?; if aligned.iter().any(|&b| b != magic) { - aligned.fill(0); + // 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; + + 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_blocking(state_flash, 0, aligned)?; + // Clear magic and progress self.state.wipe_blocking(state_flash)?; + // Set magic aligned.fill(magic); self.state.write_blocking(state_flash, 0, aligned)?; } diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index a5795781f..597ce3fa3 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -93,7 +93,7 @@ mod tests { 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().with_limited_erase_before_write_verification(4..); + let mut flash = MemFlash::<131072, 4096, 4>::random(); let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; @@ -166,7 +166,7 @@ mod tests { let mut active = MemFlash::<16384, 4096, 8>::random(); let mut dfu = MemFlash::<16384, 2048, 8>::random(); - let mut state = MemFlash::<4096, 128, 4>::random().with_limited_erase_before_write_verification(2048 + 4..); + let mut state = MemFlash::<4096, 128, 4>::random(); let mut aligned = [0; 4]; let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; @@ -220,7 +220,7 @@ mod tests { 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().with_limited_erase_before_write_verification(2048 + 4..); + let mut state = MemFlash::<4096, 128, 4>::random(); let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index e87ccd37a..828aad9d9 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -9,8 +9,6 @@ use crate::Flash; pub struct MemFlash { pub mem: [u8; SIZE], - pub allow_same_write: bool, - pub verify_erased_before_write: Range, pub pending_write_successes: Option, } @@ -21,8 +19,6 @@ impl MemFla pub const fn new(fill: u8) -> Self { Self { mem: [fill; SIZE], - allow_same_write: false, - verify_erased_before_write: 0..SIZE, pending_write_successes: None, } } @@ -35,37 +31,9 @@ impl MemFla } Self { mem, - allow_same_write: false, - verify_erased_before_write: 0..SIZE, pending_write_successes: None, } } - - #[must_use] - pub fn allow_same_write(self, allow: bool) -> Self { - Self { - allow_same_write: allow, - ..self - } - } - - #[must_use] - pub fn with_limited_erase_before_write_verification>(self, verified_range: R) -> Self { - let start = match verified_range.start_bound() { - Bound::Included(start) => *start, - Bound::Excluded(start) => *start + 1, - Bound::Unbounded => 0, - }; - let end = match verified_range.end_bound() { - Bound::Included(end) => *end - 1, - Bound::Excluded(end) => *end, - Bound::Unbounded => self.mem.len(), - }; - Self { - verify_erased_before_write: start..end, - ..self - } - } } impl Default @@ -150,14 +118,8 @@ impl NorFla .take(bytes.len()) .zip(bytes) { - if self.allow_same_write && mem_byte == new_byte { - // Write does not change the flash memory which is allowed - } else { - if self.verify_erased_before_write.contains(&offset) { - assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); - } - *mem_byte &= *new_byte; - } + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + *mem_byte = *new_byte; } Ok(()) @@ -192,22 +154,3 @@ impl AsyncN ::write(self, offset, bytes) } } - -#[cfg(test)] -mod tests { - use core::ops::Range; - - use embedded_storage::nor_flash::NorFlash; - - use super::MemFlash; - - #[test] - fn writes_only_flip_bits_from_1_to_0() { - let mut flash = MemFlash::<16, 16, 1>::default().with_limited_erase_before_write_verification(0..0); - - flash.write(0, &[0x55]).unwrap(); - flash.write(0, &[0xAA]).unwrap(); - - assert_eq!(0x00, flash.mem[0]); - } -} -- cgit From 7c6936a2e398e43ea3dc89736dc385402822933f Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 12:24:30 +0200 Subject: Let hash functions take a digest::Digest trait ... and add adapters for current Sha512 implementations that does not inplement the Digest trait --- .../boot/src/digest_adapters/ed25519_dalek.rs | 30 ++++++ embassy-boot/boot/src/digest_adapters/mod.rs | 5 + embassy-boot/boot/src/digest_adapters/salty.rs | 29 ++++++ embassy-boot/boot/src/firmware_updater.rs | 108 ++++++++++++++------- embassy-boot/boot/src/lib.rs | 1 + 5 files changed, 137 insertions(+), 36 deletions(-) create mode 100644 embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs create mode 100644 embassy-boot/boot/src/digest_adapters/mod.rs create mode 100644 embassy-boot/boot/src/digest_adapters/salty.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..a184d1c51 --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs @@ -0,0 +1,30 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; +use ed25519_dalek::Digest as _; + +pub struct Sha512(ed25519_dalek::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(ed25519_dalek::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "ed25519-dalek")] +pub(crate) mod ed25519_dalek; + +#[cfg(feature = "ed25519-salty")] +pub(crate) mod salty; diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/salty.rs @@ -0,0 +1,29 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; + +pub struct Sha512(salty::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(salty::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 22e3e6b00..2d1b26980 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -1,3 +1,4 @@ +use digest::Digest; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; @@ -128,25 +129,27 @@ impl FirmwareUpdater { #[cfg(feature = "ed25519-dalek")] { - use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - self.incremental_hash(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x)) + let mut message = [0; 64]; + self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) .await?; - public_key - .verify(&digest.finalize(), &signature) - .map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)? } #[cfg(feature = "ed25519-salty")] { use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Sha512, Signature}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; fn into_signature_error(_: E) -> FirmwareUpdaterError { FirmwareUpdaterError::Signature(signature::Error::default()) @@ -157,11 +160,10 @@ impl FirmwareUpdater { let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - self.incremental_hash(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x)) + let mut message = [0; 64]; + self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) .await?; - let message = digest.finalize(); let r = public_key.verify(&message, &signature); trace!( "Verifying with public key {}, signature {} and message {} yields ok: {}", @@ -176,19 +178,21 @@ impl FirmwareUpdater { self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await } - /// Iterate through the DFU and process all bytes with the provided closure. - pub async fn incremental_hash( + /// Verify the update in DFU with any digest. + pub async fn hash( &mut self, dfu_flash: &mut F, update_len: u32, - aligned: &mut [u8], - mut update: impl FnMut(&[u8]), + chunk_buf: &mut [u8], + output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - for offset in (0..update_len).step_by(aligned.len()) { - self.dfu.read(dfu_flash, offset, aligned).await?; - let len = core::cmp::min((update_len - offset) as usize, aligned.len()); - update(&aligned[..len]); + 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?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); } + output.copy_from_slice(digest.finalize().as_slice()); Ok(()) } @@ -334,24 +338,26 @@ impl FirmwareUpdater { #[cfg(feature = "ed25519-dalek")] { - use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier}; + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - self.incremental_hash_blocking(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x))?; + let mut message = [0; 64]; + self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; - public_key - .verify(&digest.finalize(), &signature) - .map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)? } #[cfg(feature = "ed25519-salty")] { use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Sha512, Signature}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; fn into_signature_error(_: E) -> FirmwareUpdaterError { FirmwareUpdaterError::Signature(signature::Error::default()) @@ -362,10 +368,9 @@ impl FirmwareUpdater { let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - let mut digest = Sha512::new(); - self.incremental_hash_blocking(_state_and_dfu_flash, _update_len, _aligned, |x| digest.update(x))?; + let mut message = [0; 64]; + self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; - let message = digest.finalize(); let r = public_key.verify(&message, &signature); trace!( "Verifying with public key {}, signature {} and message {} yields ok: {}", @@ -380,19 +385,21 @@ impl FirmwareUpdater { self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) } - /// Iterate through the DFU and process all bytes with the provided closure. - pub fn incremental_hash_blocking( + /// Verify the update in DFU with any digest. + pub fn hash_blocking( &mut self, dfu_flash: &mut F, update_len: u32, - aligned: &mut [u8], - mut update: impl FnMut(&[u8]), + chunk_buf: &mut [u8], + output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - for offset in (0..update_len).step_by(aligned.len()) { - self.dfu.read_blocking(dfu_flash, offset, aligned)?; - let len = core::cmp::min((update_len - offset) as usize, aligned.len()); - update(&aligned[..len]); + 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)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); } + output.copy_from_slice(digest.finalize().as_slice()); Ok(()) } @@ -479,3 +486,32 @@ impl FirmwareUpdater { Ok(self.dfu) } } + +#[cfg(test)] +mod tests { + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::tests::MemFlash; + + #[test] + fn can_verify() { + const STATE: Partition = Partition::new(0, 4096); + const DFU: Partition = Partition::new(65536, 131072); + + let mut flash = MemFlash::<131072, 4096, 8>([0xFF; 131072]); + + 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 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(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 6888a8055..da9055476 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -6,6 +6,7 @@ mod fmt; mod boot_loader; +mod digest_adapters; mod firmware_updater; mod partition; -- cgit From 5e19fb6fb976db869696eaa5e8a2cdba751055b1 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 12:36:50 +0200 Subject: Fix compile error when verification is enabled --- embassy-boot/boot/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index cb12f9dc7..d53c613a3 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -286,13 +286,13 @@ mod tests { const STATE: Partition = Partition::new(0, 4096); const DFU: Partition = Partition::new(4096, 8192); - let mut flash = MemFlash::<8192, 4096, 4>([0xff; 8192]); + let mut flash = MemFlash::<8192, 4096, 4>::default(); let firmware_len = firmware.len(); let mut write_buf = [0; 4096]; write_buf[0..firmware_len].copy_from_slice(firmware); - NorFlash::write(&mut flash, DFU.from as u32, &write_buf).unwrap(); + DFU.write_blocking(&mut flash, 0, &write_buf).unwrap(); // On with the test -- cgit From 803c09c300a9aeb412e08c37723cd9de3caf89e9 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 12:50:53 +0200 Subject: Expose read/write/erase on partition --- embassy-boot/boot/src/partition.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index 3ccd4dd76..217c457fc 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -24,7 +24,7 @@ impl Partition { } /// Read from the partition on the provided flash - pub(crate) async fn read( + pub async fn read( &self, flash: &mut F, offset: u32, @@ -35,12 +35,7 @@ impl Partition { } /// Write to the partition on the provided flash - pub(crate) async fn write( - &self, - flash: &mut F, - offset: u32, - bytes: &[u8], - ) -> Result<(), F::Error> { + 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()); @@ -48,7 +43,7 @@ impl Partition { } /// Erase part of the partition on the provided flash - pub(crate) async fn erase(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { + 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?; @@ -66,18 +61,13 @@ impl Partition { } /// Read from the partition on the provided flash - pub(crate) fn read_blocking( - &self, - flash: &mut F, - offset: u32, - bytes: &mut [u8], - ) -> Result<(), F::Error> { + 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(crate) fn write_blocking(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { + 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()); @@ -85,7 +75,7 @@ impl Partition { } /// Erase part of the partition on the provided flash - pub(crate) fn erase_blocking(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { + 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)?; -- cgit From 8256ac104405400a15aa9a6a2d9afe38a552a98b Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 19:07:45 +0200 Subject: Use MemFlash::default() in sha1 verify test --- embassy-boot/boot/src/firmware_updater.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 819e20201..fffb9a500 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -519,14 +519,14 @@ mod tests { use sha1::{Digest, Sha1}; use super::*; - use crate::tests::MemFlash; + use crate::mem_flash::MemFlash; #[test] - fn can_verify() { + 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>([0xFF; 131072]); + let mut flash = MemFlash::<131072, 4096, 8>::default(); let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; let mut to_write = [0; 4096]; -- cgit From 9242ad89d436422fd5abdafadb2faf845e730a16 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 20:25:55 +0200 Subject: Remove magic buffer argument from prepare_boot and use the aligned page buffer instead --- embassy-boot/boot/src/boot_loader.rs | 134 +++++++++++++++++++---------------- embassy-boot/boot/src/lib.rs | 22 ++---- 2 files changed, 78 insertions(+), 78 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 9d047f778..698075599 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -56,6 +56,16 @@ pub trait FlashConfig { fn state(&mut self) -> &mut Self::STATE; } +trait FlashConfigEx { + fn page_size() -> usize; +} + +impl FlashConfigEx for T { + fn page_size() -> usize { + core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) + } +} + /// BootLoader works with any flash implementing embedded_storage and can also work with /// different page sizes and flash write sizes. pub struct BootLoader { @@ -91,6 +101,9 @@ impl BootLoader { /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap /// algorithm to work correctly. /// + /// The provided aligned_buf argument must satisfy any alignment requirements + /// given by the partition flashes. All flash operations will use this buffer. + /// /// SWAPPING /// /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. @@ -169,87 +182,89 @@ impl BootLoader { /// | DFU | 3 | 3 | 2 | 1 | 3 | /// +-----------+--------------+--------+--------+--------+--------+ /// - pub fn prepare_boot( - &mut self, - p: &mut P, - magic: &mut [u8], - page: &mut [u8], - ) -> Result { + pub fn prepare_boot(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { // Ensure we have enough progress pages to store copy progress - assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE); - assert_eq!(magic.len(), P::STATE::WRITE_SIZE); + assert_eq!(aligned_buf.len(), P::page_size()); + assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); + assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); // Copy contents from partition N to active - let state = self.read_state(p, magic)?; + let state = self.read_state(p, 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, magic, page)? { + if !self.is_swapped(p, aligned_buf)? { trace!("Swapping"); - self.swap(p, magic, page)?; + self.swap(p, aligned_buf)?; trace!("Swapping done"); } else { trace!("Reverting"); - self.revert(p, magic, page)?; + self.revert(p, aligned_buf)?; let state_flash = p.state(); + let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; // Invalidate progress - magic.fill(!P::STATE::ERASE_VALUE); + state_word.fill(!P::STATE::ERASE_VALUE); self.state - .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, magic)?; + .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; // Clear magic and progress self.state.wipe_blocking(state_flash)?; // Set magic - magic.fill(BOOT_MAGIC); - self.state.write_blocking(state_flash, 0, magic)?; + state_word.fill(BOOT_MAGIC); + self.state.write_blocking(state_flash, 0, state_word)?; } } Ok(state) } - fn is_swapped(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result { - let page_size = page.len(); - let page_count = self.active.len() / page_size; - let progress = self.current_progress(p, magic)?; + fn is_swapped(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.len() / P::page_size(); + let progress = self.current_progress(p, aligned_buf)?; Ok(progress >= page_count * 2) } - fn current_progress(&mut self, config: &mut P, aligned: &mut [u8]) -> Result { - let write_size = aligned.len(); - let max_index = ((self.state.len() - write_size) / write_size) - 2; - aligned.fill(!P::STATE::ERASE_VALUE); - + fn current_progress(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result { + let max_index = ((self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE) - 2; let state_flash = config.state(); + let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; self.state - .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, aligned)?; - if aligned.iter().any(|&b| b != P::STATE::ERASE_VALUE) { + .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; + if state_word.iter().any(|&b| b != P::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 * P::STATE::WRITE_SIZE as u32, aligned)?; + self.state.read_blocking( + state_flash, + (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, + state_word, + )?; - if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { + if state_word.iter().any(|&b| b == P::STATE::ERASE_VALUE) { return Ok(index); } } Ok(max_index) } - fn update_progress(&mut self, index: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { - let aligned = magic; - aligned.fill(!P::STATE::ERASE_VALUE); + fn update_progress( + &mut self, + 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 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?; + .write_blocking(p.state(), (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, state_word)?; Ok(()) } @@ -259,26 +274,24 @@ impl BootLoader { from_offset: u32, to_offset: u32, p: &mut P, - magic: &mut [u8], - page: &mut [u8], + aligned_buf: &mut [u8], ) -> Result<(), BootError> { - let buf = page; - if self.current_progress(p, magic)? <= idx { + if self.current_progress(p, aligned_buf)? <= idx { let mut offset = from_offset; - for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { + for chunk in aligned_buf.chunks_mut(P::DFU::BLOCK_SIZE) { self.dfu.read_blocking(p.dfu(), offset, chunk)?; offset += chunk.len() as u32; } self.active - .erase_blocking(p.active(), to_offset, to_offset + buf.len() as u32)?; + .erase_blocking(p.active(), to_offset, to_offset + P::page_size() as u32)?; let mut offset = to_offset; - for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { + for chunk in aligned_buf.chunks(P::ACTIVE::BLOCK_SIZE) { self.active.write_blocking(p.active(), offset, chunk)?; offset += chunk.len() as u32; } - self.update_progress(idx, p, magic)?; + self.update_progress(idx, p, aligned_buf)?; } Ok(()) } @@ -289,32 +302,30 @@ impl BootLoader { from_offset: u32, to_offset: u32, p: &mut P, - magic: &mut [u8], - page: &mut [u8], + aligned_buf: &mut [u8], ) -> Result<(), BootError> { - let buf = page; - if self.current_progress(p, magic)? <= idx { + if self.current_progress(p, aligned_buf)? <= idx { let mut offset = from_offset; - for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { + for chunk in aligned_buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { self.active.read_blocking(p.active(), offset, chunk)?; offset += chunk.len() as u32; } self.dfu - .erase_blocking(p.dfu(), to_offset as u32, to_offset + buf.len() as u32)?; + .erase_blocking(p.dfu(), to_offset as u32, to_offset + P::page_size() as u32)?; let mut offset = to_offset; - for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { + for chunk in aligned_buf.chunks(P::DFU::BLOCK_SIZE) { self.dfu.write_blocking(p.dfu(), offset, chunk)?; offset += chunk.len() as u32; } - self.update_progress(idx, p, magic)?; + self.update_progress(idx, p, aligned_buf)?; } Ok(()) } - fn swap(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { - let page_size = page.len(); + fn swap(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_size = P::page_size(); let page_count = self.active.len() / page_size; trace!("Page count: {}", page_count); for page_num in 0..page_count { @@ -326,20 +337,20 @@ impl BootLoader { let active_from_offset = ((page_count - 1 - page_num) * page_size) as u32; let dfu_to_offset = ((page_count - page_num) * page_size) as u32; //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); - self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, magic, page)?; + self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, aligned_buf)?; // Copy DFU page to the active page let active_to_offset = ((page_count - 1 - page_num) * page_size) as u32; let dfu_from_offset = ((page_count - 1 - page_num) * page_size) as u32; //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); - self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, magic, page)?; + self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; } Ok(()) } - fn revert(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { - let page_size = page.len(); + fn revert(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_size = P::page_size(); let page_count = self.active.len() / page_size; for page_num in 0..page_count { let idx = page_count * 2 + page_num * 2; @@ -347,21 +358,22 @@ impl BootLoader { // Copy the bad active page to the DFU page let active_from_offset = (page_num * page_size) as u32; let dfu_to_offset = (page_num * page_size) as u32; - self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, magic, page)?; + self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, aligned_buf)?; // Copy the DFU page back to the active page let active_to_offset = (page_num * page_size) as u32; let dfu_from_offset = ((page_num + 1) * page_size) as u32; - self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, magic, page)?; + self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; } Ok(()) } - fn read_state(&mut self, config: &mut P, magic: &mut [u8]) -> Result { - self.state.read_blocking(config.state(), 0, magic)?; + 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)?; - if !magic.iter().any(|&b| b != SWAP_MAGIC) { + if !state_word.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) } else { Ok(State::Boot) diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index d53c613a3..896498c0b 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -77,12 +77,8 @@ mod tests { let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); - let mut magic = [0; 4]; let mut page = [0; 4096]; - assert_eq!( - State::Boot, - bootloader.prepare_boot(&mut flash, &mut magic, &mut page).unwrap() - ); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap()); } #[test] @@ -110,12 +106,11 @@ mod tests { } block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); - let mut magic = [0; 4]; let mut page = [0; 4096]; assert_eq!( State::Swap, bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) + .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) .unwrap() ); @@ -132,7 +127,7 @@ mod tests { assert_eq!( State::Swap, bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) + .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) .unwrap() ); @@ -150,7 +145,7 @@ mod tests { assert_eq!( State::Boot, bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) + .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) .unwrap() ); } @@ -184,17 +179,12 @@ mod tests { block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); - let mut magic = [0; 4]; let mut page = [0; 4096]; assert_eq!( State::Swap, bootloader - .prepare_boot( - &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), - &mut magic, - &mut page - ) + .prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page) .unwrap() ); @@ -237,14 +227,12 @@ mod tests { block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); - let mut magic = [0; 4]; let mut page = [0; 4096]; assert_eq!( State::Swap, bootloader .prepare_boot( &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,), - &mut magic, &mut page ) .unwrap() -- cgit From 25577e0eafd8a3d4ffaa4b8f17cb55399fd58038 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 21:09:30 +0200 Subject: Assert active and dfu have same erase size and copy in smaller chunks The copy from active to dfu (and vice versa) is now done in smaller portions depending on aligned_buf, which now does not need to be erase_size big. --- embassy-boot/boot/src/boot_loader.rs | 66 +++++++++++++++---------------- embassy-boot/boot/src/large_erase.rs | 76 ++++++++++++++++++++++++++++++++++++ embassy-boot/boot/src/lib.rs | 32 ++++++--------- embassy-boot/boot/src/mem_flash.rs | 1 - 4 files changed, 118 insertions(+), 57 deletions(-) create mode 100644 embassy-boot/boot/src/large_erase.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 698075599..db067da5b 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -32,14 +32,13 @@ where /// Extension of the embedded-storage flash type information with block size and erase value. pub trait Flash: NorFlash { - /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase - /// size of the flash, but for external QSPI flash modules, this can be lower. - const BLOCK_SIZE: usize; /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value. const ERASE_VALUE: u8 = 0xFF; } -/// Trait defining the flash handles used for active and DFU partition +/// Trait defining the flash handles used for active and DFU partition. +/// The ACTIVE and DFU erase sizes must be equal. If this is not the case, then consider adding an adapter for the +/// smallest flash to increase its erase size such that they match. See e.g. [`crate::large_erase::LargeErase`]. pub trait FlashConfig { /// Flash type used for the state partition. type STATE: Flash; @@ -62,12 +61,12 @@ trait FlashConfigEx { impl FlashConfigEx for T { fn page_size() -> usize { - core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) + assert_eq!(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE); + T::ACTIVE::ERASE_SIZE } } -/// BootLoader works with any flash implementing embedded_storage and can also work with -/// different page sizes and flash write sizes. +/// 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. @@ -184,7 +183,9 @@ impl BootLoader { /// pub fn prepare_boot(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { // Ensure we have enough progress pages to store copy progress - assert_eq!(aligned_buf.len(), P::page_size()); + assert_eq!(0, P::page_size() % aligned_buf.len()); + assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE); + assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE); assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); @@ -277,20 +278,18 @@ impl BootLoader { aligned_buf: &mut [u8], ) -> Result<(), BootError> { if self.current_progress(p, aligned_buf)? <= idx { - let mut offset = from_offset; - for chunk in aligned_buf.chunks_mut(P::DFU::BLOCK_SIZE) { - self.dfu.read_blocking(p.dfu(), offset, chunk)?; - offset += chunk.len() as u32; - } + let page_size = P::page_size() as u32; self.active - .erase_blocking(p.active(), to_offset, to_offset + P::page_size() as u32)?; + .erase_blocking(p.active(), to_offset, to_offset + page_size)?; - let mut offset = to_offset; - for chunk in aligned_buf.chunks(P::ACTIVE::BLOCK_SIZE) { - self.active.write_blocking(p.active(), offset, chunk)?; - offset += chunk.len() as u32; + 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.update_progress(idx, p, aligned_buf)?; } Ok(()) @@ -305,20 +304,18 @@ impl BootLoader { aligned_buf: &mut [u8], ) -> Result<(), BootError> { if self.current_progress(p, aligned_buf)? <= idx { - let mut offset = from_offset; - for chunk in aligned_buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { - self.active.read_blocking(p.active(), offset, chunk)?; - offset += chunk.len() as u32; - } + let page_size = P::page_size() as u32; self.dfu - .erase_blocking(p.dfu(), to_offset as u32, to_offset + P::page_size() as u32)?; + .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?; - let mut offset = to_offset; - for chunk in aligned_buf.chunks(P::DFU::BLOCK_SIZE) { - self.dfu.write_blocking(p.dfu(), offset, chunk)?; - offset += chunk.len() as u32; + 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.update_progress(idx, p, aligned_buf)?; } Ok(()) @@ -389,14 +386,14 @@ fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_s } /// A flash wrapper implementing the Flash and embedded_storage traits. -pub struct BootFlash +pub struct BootFlash where F: NorFlash + ReadNorFlash, { flash: F, } -impl BootFlash +impl BootFlash where F: NorFlash + ReadNorFlash, { @@ -406,22 +403,21 @@ where } } -impl Flash for BootFlash +impl Flash for BootFlash where F: NorFlash + ReadNorFlash, { - const BLOCK_SIZE: usize = BLOCK_SIZE; const ERASE_VALUE: u8 = ERASE_VALUE; } -impl ErrorType for BootFlash +impl ErrorType for BootFlash where F: ReadNorFlash + NorFlash, { type Error = F::Error; } -impl NorFlash for BootFlash +impl NorFlash for BootFlash where F: ReadNorFlash + NorFlash, { @@ -437,7 +433,7 @@ where } } -impl ReadNorFlash for BootFlash +impl ReadNorFlash for BootFlash where F: ReadNorFlash + NorFlash, { diff --git a/embassy-boot/boot/src/large_erase.rs b/embassy-boot/boot/src/large_erase.rs new file mode 100644 index 000000000..d00d43599 --- /dev/null +++ b/embassy-boot/boot/src/large_erase.rs @@ -0,0 +1,76 @@ +#![allow(unused)] + +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +use crate::Flash; + +pub struct LargeErase(pub F); + +impl LargeErase { + pub const fn new(flash: F) -> Self { + Self(flash) + } +} + +impl Flash for LargeErase { + const ERASE_VALUE: u8 = F::ERASE_VALUE; +} + +impl ErrorType for LargeErase { + type Error = F::Error; +} + +impl NorFlash for LargeErase { + const WRITE_SIZE: usize = F::ERASE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + assert!(ERASE_SIZE >= F::ERASE_SIZE); + assert_eq!(0, ERASE_SIZE % F::ERASE_SIZE); + self.0.erase(from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.0.write(offset, bytes) + } +} + +impl ReadNorFlash for LargeErase { + const READ_SIZE: usize = F::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.0.read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.0.capacity() + } +} + +impl AsyncNorFlash for LargeErase { + const WRITE_SIZE: usize = F::ERASE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + assert!(ERASE_SIZE >= F::ERASE_SIZE); + assert_eq!(0, ERASE_SIZE % F::ERASE_SIZE); + self.0.erase(from, to).await + } + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.0.write(offset, bytes).await + } +} + +impl AsyncReadNorFlash for LargeErase { + const READ_SIZE: usize = F::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.0.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.0.capacity() + } +} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 896498c0b..79759124b 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -7,6 +7,7 @@ mod fmt; mod boot_loader; mod firmware_updater; +mod large_erase; mod mem_flash; mod partition; @@ -48,6 +49,7 @@ mod tests { use futures::executor::block_on; use super::*; + use crate::large_erase::LargeErase; use crate::mem_flash::MemFlash; /* @@ -99,14 +101,10 @@ mod tests { let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut updater = FirmwareUpdater::new(DFU, STATE); - let mut offset = 0; - for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, chunk, &mut flash)).unwrap(); - offset += chunk.len(); - } + block_on(updater.write_firmware(0, &update, &mut flash)).unwrap(); block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); - let mut page = [0; 4096]; + let mut page = [0; 1024]; assert_eq!( State::Swap, bootloader @@ -158,7 +156,7 @@ mod tests { const DFU: Partition = Partition::new(0, 16384); let mut active = MemFlash::<16384, 4096, 8>::random(); - let mut dfu = MemFlash::<16384, 2048, 8>::random(); + let mut dfu = LargeErase::<_, 4096>::new(MemFlash::<16384, 2048, 8>::random()); let mut state = MemFlash::<4096, 128, 4>::random(); let mut aligned = [0; 4]; @@ -171,11 +169,7 @@ mod tests { let mut updater = FirmwareUpdater::new(DFU, STATE); - let mut offset = 0; - for chunk in update.chunks(2048) { - block_on(updater.write_firmware(offset, chunk, &mut dfu)).unwrap(); - offset += chunk.len(); - } + 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); @@ -194,7 +188,7 @@ mod tests { // First DFU page is untouched for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.mem[i], original[i - DFU.from - 4096], "Index {}", i); + assert_eq!(dfu.0.mem[i], original[i - DFU.from - 4096], "Index {}", i); } } @@ -206,7 +200,7 @@ mod tests { const DFU: Partition = Partition::new(0, 16384); let mut aligned = [0; 4]; - let mut active = MemFlash::<16384, 2048, 4>::random(); + let mut active = LargeErase::<_, 4096>::new(MemFlash::<16384, 2048, 4>::random()); let mut dfu = MemFlash::<16384, 4096, 8>::random(); let mut state = MemFlash::<4096, 128, 4>::random(); @@ -214,16 +208,12 @@ mod tests { let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; for i in ACTIVE.from..ACTIVE.to { - active.mem[i] = original[i - ACTIVE.from]; + active.0.mem[i] = original[i - ACTIVE.from]; } let mut updater = FirmwareUpdater::new(DFU, STATE); - let mut offset = 0; - for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, chunk, &mut dfu)).unwrap(); - offset += chunk.len(); - } + 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); @@ -239,7 +229,7 @@ mod tests { ); for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.mem[i], update[i - ACTIVE.from], "Index {}", i); + assert_eq!(active.0.mem[i], update[i - ACTIVE.from], "Index {}", i); } // First DFU page is untouched diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index 828aad9d9..2598bf4de 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -47,7 +47,6 @@ impl Defaul impl Flash for MemFlash { - const BLOCK_SIZE: usize = ERASE_SIZE; const ERASE_VALUE: u8 = 0xFF; } -- cgit From 6c93309df490020f0ae4a515bf404dfd251b9b69 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 21:18:41 +0200 Subject: Remove the Flash trait --- embassy-boot/boot/src/boot_loader.rs | 73 +++++++++++++++--------------------- embassy-boot/boot/src/large_erase.rs | 6 --- embassy-boot/boot/src/lib.rs | 2 +- embassy-boot/boot/src/mem_flash.rs | 8 ---- 4 files changed, 32 insertions(+), 57 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index db067da5b..37fff621a 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -30,22 +30,18 @@ where } } -/// Extension of the embedded-storage flash type information with block size and erase value. -pub trait Flash: NorFlash { - /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value. - const ERASE_VALUE: u8 = 0xFF; -} - /// Trait defining the flash handles used for active and DFU partition. /// The ACTIVE and DFU erase sizes must be equal. If this is not the case, then consider adding an adapter for the /// smallest flash to increase its erase size such that they match. See e.g. [`crate::large_erase::LargeErase`]. 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; /// Flash type used for the state partition. - type STATE: Flash; + type STATE: NorFlash; /// Flash type used for the active partition. - type ACTIVE: Flash; + type ACTIVE: NorFlash; /// Flash type used for the dfu partition. - type DFU: Flash; + type DFU: NorFlash; /// Return flash instance used to write/read to/from active partition. fn active(&mut self) -> &mut Self::ACTIVE; @@ -208,7 +204,7 @@ impl BootLoader { let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; // Invalidate progress - state_word.fill(!P::STATE::ERASE_VALUE); + state_word.fill(!P::STATE_ERASE_VALUE); self.state .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; @@ -237,7 +233,7 @@ impl BootLoader { self.state .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; - if state_word.iter().any(|&b| b != P::STATE::ERASE_VALUE) { + if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) { // Progress is invalid return Ok(max_index); } @@ -249,7 +245,7 @@ impl BootLoader { state_word, )?; - if state_word.iter().any(|&b| b == P::STATE::ERASE_VALUE) { + if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) { return Ok(index); } } @@ -263,7 +259,7 @@ impl BootLoader { aligned_buf: &mut [u8], ) -> Result<(), BootError> { let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - state_word.fill(!P::STATE::ERASE_VALUE); + state_word.fill(!P::STATE_ERASE_VALUE); self.state .write_blocking(p.state(), (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, state_word)?; Ok(()) @@ -386,16 +382,16 @@ fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_s } /// A flash wrapper implementing the Flash and embedded_storage traits. -pub struct BootFlash +pub struct BootFlash where - F: NorFlash + ReadNorFlash, + F: NorFlash, { flash: F, } -impl BootFlash +impl BootFlash where - F: NorFlash + ReadNorFlash, + F: NorFlash, { /// Create a new instance of a bootable flash pub fn new(flash: F) -> Self { @@ -403,23 +399,16 @@ where } } -impl Flash for BootFlash -where - F: NorFlash + ReadNorFlash, -{ - const ERASE_VALUE: u8 = ERASE_VALUE; -} - -impl ErrorType for BootFlash +impl ErrorType for BootFlash where - F: ReadNorFlash + NorFlash, + F: NorFlash, { type Error = F::Error; } -impl NorFlash for BootFlash +impl NorFlash for BootFlash where - F: ReadNorFlash + NorFlash, + F: NorFlash, { const WRITE_SIZE: usize = F::WRITE_SIZE; const ERASE_SIZE: usize = F::ERASE_SIZE; @@ -433,9 +422,9 @@ where } } -impl ReadNorFlash for BootFlash +impl ReadNorFlash for BootFlash where - F: ReadNorFlash + NorFlash, + F: NorFlash, { const READ_SIZE: usize = F::READ_SIZE; @@ -451,14 +440,14 @@ where /// Convenience provider that uses a single flash for all partitions. pub struct SingleFlashConfig<'a, F> where - F: Flash, + F: NorFlash, { flash: &'a mut F, } impl<'a, F> SingleFlashConfig<'a, F> where - F: Flash, + F: NorFlash, { /// Create a provider for a single flash. pub fn new(flash: &'a mut F) -> Self { @@ -468,7 +457,7 @@ where impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> where - F: Flash, + F: NorFlash, { type STATE = F; type ACTIVE = F; @@ -488,9 +477,9 @@ where /// Convenience flash provider that uses separate flash instances for each partition. pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> where - ACTIVE: Flash, - STATE: Flash, - DFU: Flash, + ACTIVE: NorFlash, + STATE: NorFlash, + DFU: NorFlash, { active: &'a mut ACTIVE, state: &'a mut STATE, @@ -499,9 +488,9 @@ where impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> where - ACTIVE: Flash, - STATE: Flash, - DFU: Flash, + 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 { @@ -511,9 +500,9 @@ where impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> where - ACTIVE: Flash, - STATE: Flash, - DFU: Flash, + ACTIVE: NorFlash, + STATE: NorFlash, + DFU: NorFlash, { type STATE = STATE; type ACTIVE = ACTIVE; diff --git a/embassy-boot/boot/src/large_erase.rs b/embassy-boot/boot/src/large_erase.rs index d00d43599..b999a046f 100644 --- a/embassy-boot/boot/src/large_erase.rs +++ b/embassy-boot/boot/src/large_erase.rs @@ -3,8 +3,6 @@ use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; -use crate::Flash; - pub struct LargeErase(pub F); impl LargeErase { @@ -13,10 +11,6 @@ impl LargeErase { } } -impl Flash for LargeErase { - const ERASE_VALUE: u8 = F::ERASE_VALUE; -} - impl ErrorType for LargeErase { type Error = F::Error; } diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 79759124b..cc812d797 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -11,7 +11,7 @@ mod large_erase; mod mem_flash; mod partition; -pub use boot_loader::{BootError, BootFlash, BootLoader, Flash, FlashConfig, MultiFlashConfig, SingleFlashConfig}; +pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; pub use partition::Partition; diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index 2598bf4de..dd85405c8 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -5,8 +5,6 @@ use core::ops::{Bound, Range, RangeBounds}; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; -use crate::Flash; - pub struct MemFlash { pub mem: [u8; SIZE], pub pending_write_successes: Option, @@ -44,12 +42,6 @@ impl Defaul } } -impl Flash - for MemFlash -{ - const ERASE_VALUE: u8 = 0xFF; -} - impl ErrorType for MemFlash { -- cgit From 53efb029009e3cb92bb19c8ac8f521407aa4d1e2 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 21:30:49 +0200 Subject: Allow different erase sizes for active and dfu --- embassy-boot/boot/src/boot_loader.rs | 6 ++-- embassy-boot/boot/src/large_erase.rs | 70 ------------------------------------ embassy-boot/boot/src/lib.rs | 12 +++---- 3 files changed, 9 insertions(+), 79 deletions(-) delete mode 100644 embassy-boot/boot/src/large_erase.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 37fff621a..25f81009e 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -56,9 +56,9 @@ trait FlashConfigEx { } impl FlashConfigEx for T { + /// Get the page size which is the "unit of operation" within the bootloader. fn page_size() -> usize { - assert_eq!(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE); - T::ACTIVE::ERASE_SIZE + core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) } } @@ -182,6 +182,8 @@ impl BootLoader { assert_eq!(0, P::page_size() % aligned_buf.len()); assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE); assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE); + assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE); + assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE); assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); diff --git a/embassy-boot/boot/src/large_erase.rs b/embassy-boot/boot/src/large_erase.rs deleted file mode 100644 index b999a046f..000000000 --- a/embassy-boot/boot/src/large_erase.rs +++ /dev/null @@ -1,70 +0,0 @@ -#![allow(unused)] - -use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; -use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; - -pub struct LargeErase(pub F); - -impl LargeErase { - pub const fn new(flash: F) -> Self { - Self(flash) - } -} - -impl ErrorType for LargeErase { - type Error = F::Error; -} - -impl NorFlash for LargeErase { - const WRITE_SIZE: usize = F::ERASE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - assert!(ERASE_SIZE >= F::ERASE_SIZE); - assert_eq!(0, ERASE_SIZE % F::ERASE_SIZE); - self.0.erase(from, to) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.0.write(offset, bytes) - } -} - -impl ReadNorFlash for LargeErase { - const READ_SIZE: usize = F::READ_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.0.read(offset, bytes) - } - - fn capacity(&self) -> usize { - self.0.capacity() - } -} - -impl AsyncNorFlash for LargeErase { - const WRITE_SIZE: usize = F::ERASE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - assert!(ERASE_SIZE >= F::ERASE_SIZE); - assert_eq!(0, ERASE_SIZE % F::ERASE_SIZE); - self.0.erase(from, to).await - } - - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.0.write(offset, bytes).await - } -} - -impl AsyncReadNorFlash for LargeErase { - const READ_SIZE: usize = F::READ_SIZE; - - async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.0.read(offset, bytes).await - } - - fn capacity(&self) -> usize { - self.0.capacity() - } -} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index cc812d797..3109f2b47 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -7,7 +7,6 @@ mod fmt; mod boot_loader; mod firmware_updater; -mod large_erase; mod mem_flash; mod partition; @@ -49,7 +48,6 @@ mod tests { use futures::executor::block_on; use super::*; - use crate::large_erase::LargeErase; use crate::mem_flash::MemFlash; /* @@ -156,7 +154,7 @@ mod tests { const DFU: Partition = Partition::new(0, 16384); let mut active = MemFlash::<16384, 4096, 8>::random(); - let mut dfu = LargeErase::<_, 4096>::new(MemFlash::<16384, 2048, 8>::random()); + let mut dfu = MemFlash::<16384, 2048, 8>::random(); let mut state = MemFlash::<4096, 128, 4>::random(); let mut aligned = [0; 4]; @@ -188,7 +186,7 @@ mod tests { // First DFU page is untouched for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.0.mem[i], original[i - DFU.from - 4096], "Index {}", i); + assert_eq!(dfu.mem[i], original[i - DFU.from - 4096], "Index {}", i); } } @@ -200,7 +198,7 @@ mod tests { const DFU: Partition = Partition::new(0, 16384); let mut aligned = [0; 4]; - let mut active = LargeErase::<_, 4096>::new(MemFlash::<16384, 2048, 4>::random()); + let mut active = MemFlash::<16384, 2048, 4>::random(); let mut dfu = MemFlash::<16384, 4096, 8>::random(); let mut state = MemFlash::<4096, 128, 4>::random(); @@ -208,7 +206,7 @@ mod tests { let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; for i in ACTIVE.from..ACTIVE.to { - active.0.mem[i] = original[i - ACTIVE.from]; + active.mem[i] = original[i - ACTIVE.from]; } let mut updater = FirmwareUpdater::new(DFU, STATE); @@ -229,7 +227,7 @@ mod tests { ); for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.0.mem[i], update[i - ACTIVE.from], "Index {}", i); + assert_eq!(active.mem[i], update[i - ACTIVE.from], "Index {}", i); } // First DFU page is untouched -- cgit From 78e6b4d26134887b50e3ff3239a20f9b3002e8a0 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 21:43:18 +0200 Subject: Remove comment about equal erase size requirement --- embassy-boot/boot/src/boot_loader.rs | 2 -- 1 file changed, 2 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 25f81009e..ccd74b237 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -31,8 +31,6 @@ where } /// Trait defining the flash handles used for active and DFU partition. -/// The ACTIVE and DFU erase sizes must be equal. If this is not the case, then consider adding an adapter for the -/// smallest flash to increase its erase size such that they match. See e.g. [`crate::large_erase::LargeErase`]. 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; -- cgit From e962fe794ce3c83b094e22523d0c59264983b036 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 4 Apr 2023 21:57:28 +0200 Subject: Add assertions about the aligned_buf % write sizes --- embassy-boot/boot/src/boot_loader.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index ccd74b237..2412427c0 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -179,10 +179,12 @@ impl BootLoader { // Ensure we have enough progress pages to store copy progress assert_eq!(0, P::page_size() % aligned_buf.len()); assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE); - assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE); assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE); + assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE); assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE); 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); // Copy contents from partition N to active -- cgit From d8e2f82569e1182e2a3a7ebe43af64f91d1e57e0 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Wed, 5 Apr 2023 06:57:56 +0200 Subject: Let update_len be usize for now --- embassy-boot/boot/src/firmware_updater.rs | 20 +++++++++----------- embassy-boot/boot/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index fffb9a500..48e15024e 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -119,13 +119,11 @@ impl FirmwareUpdater { _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], - _update_len: u32, + _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _read_size = _aligned.len(); - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.len() as u32); + assert!(_update_len <= self.dfu.len()); #[cfg(feature = "ed25519-dalek")] { @@ -182,10 +180,11 @@ impl FirmwareUpdater { pub async fn hash( &mut self, dfu_flash: &mut F, - update_len: u32, + update_len: usize, chunk_buf: &mut [u8], output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { + let update_len = update_len as u32; 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?; @@ -341,13 +340,11 @@ impl FirmwareUpdater { _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], - _update_len: u32, + _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _read_size = _aligned.len(); - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.len() as u32); + assert!(_update_len <= self.dfu.len()); #[cfg(feature = "ed25519-dalek")] { @@ -402,10 +399,11 @@ impl FirmwareUpdater { pub fn hash_blocking( &mut self, dfu_flash: &mut F, - update_len: u32, + update_len: usize, chunk_buf: &mut [u8], output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { + let update_len = update_len as u32; 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)?; @@ -536,7 +534,7 @@ mod tests { block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).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::<_, Sha1>(&mut flash, update.len(), &mut chunk_buf, &mut hash)).unwrap(); assert_eq!(Sha1::digest(update).as_slice(), hash); } diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 605e5253c..acd90996f 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -281,7 +281,7 @@ mod tests { &mut flash, &public_key.to_bytes(), &signature.to_bytes(), - firmware_len as u32, + firmware_len, &mut aligned, )) .is_ok()); -- cgit From 2deb2c624c78f4ff582441d7d00a654ac3844e33 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Wed, 5 Apr 2023 08:28:31 +0200 Subject: Let Partition range be u32 instead of usize --- embassy-boot/boot/src/boot_loader.rs | 101 +++++++++++++++--------------- embassy-boot/boot/src/firmware_updater.rs | 17 ++--- embassy-boot/boot/src/lib.rs | 60 +++++------------- embassy-boot/boot/src/mem_flash.rs | 17 +++++ embassy-boot/boot/src/partition.rs | 9 ++- 5 files changed, 93 insertions(+), 111 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 2412427c0..b959de2c4 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -50,13 +50,13 @@ pub trait FlashConfig { } trait FlashConfigEx { - fn page_size() -> usize; + fn page_size() -> u32; } impl FlashConfigEx for T { /// Get the page size which is the "unit of operation" within the bootloader. - fn page_size() -> usize { - core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) + fn page_size() -> u32 { + core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32 } } @@ -86,7 +86,7 @@ impl BootLoader { /// Return the offset of the active partition into the active flash. pub fn boot_address(&self) -> usize { - self.active.from + self.active.from as usize } /// Perform necessary boot preparations like swapping images. @@ -177,11 +177,11 @@ impl BootLoader { /// pub fn prepare_boot(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { // Ensure we have enough progress pages to store copy progress - assert_eq!(0, P::page_size() % aligned_buf.len()); - assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE); - assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE); - assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE); - assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE); + 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); @@ -222,30 +222,27 @@ impl BootLoader { } fn is_swapped(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { - let page_count = self.active.len() / P::page_size(); + let page_count = (self.active.size() / P::page_size()) as usize; let progress = self.current_progress(p, aligned_buf)?; Ok(progress >= page_count * 2) } fn current_progress(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let max_index = ((self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE) - 2; + 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(); - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; + let state_word = &mut aligned_buf[..write_size as usize]; - self.state - .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; + self.state.read_blocking(state_flash, write_size, state_word)?; if state_word.iter().any(|&b| b != P::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 * P::STATE::WRITE_SIZE as u32, - state_word, - )?; + self.state + .read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?; if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) { return Ok(index); @@ -256,26 +253,29 @@ impl BootLoader { fn update_progress( &mut self, - index: usize, + 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 + index) as u32 * P::STATE::WRITE_SIZE as u32, state_word)?; + self.state.write_blocking( + p.state(), + (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32, + state_word, + )?; Ok(()) } fn copy_page_once_to_active( &mut self, - idx: usize, + 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)? <= idx { + if self.current_progress(p, aligned_buf)? <= progress_index { let page_size = P::page_size() as u32; self.active @@ -288,20 +288,20 @@ impl BootLoader { .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?; } - self.update_progress(idx, p, aligned_buf)?; + self.update_progress(progress_index, p, aligned_buf)?; } Ok(()) } fn copy_page_once_to_dfu( &mut self, - idx: usize, + 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)? <= idx { + if self.current_progress(p, aligned_buf)? <= progress_index { let page_size = P::page_size() as u32; self.dfu @@ -314,31 +314,28 @@ impl BootLoader { .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?; } - self.update_progress(idx, p, aligned_buf)?; + self.update_progress(progress_index, p, 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.len() / page_size; - trace!("Page count: {}", page_count); + let page_count = self.active.size() / page_size; for page_num in 0..page_count { - trace!("COPY PAGE {}", page_num); - - let idx = page_num * 2; + 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) as u32; - let dfu_to_offset = ((page_count - page_num) * page_size) as u32; + let active_from_offset = (page_count - 1 - page_num) * page_size; + let dfu_to_offset = (page_count - page_num) * page_size; //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); - self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, aligned_buf)?; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; // Copy DFU page to the active page - let active_to_offset = ((page_count - 1 - page_num) * page_size) as u32; - let dfu_from_offset = ((page_count - 1 - page_num) * page_size) as u32; + let active_to_offset = (page_count - 1 - page_num) * page_size; + let dfu_from_offset = (page_count - 1 - page_num) * page_size; //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); - self.copy_page_once_to_active(idx + 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, p, aligned_buf)?; } Ok(()) @@ -346,19 +343,19 @@ impl BootLoader { fn revert(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { let page_size = P::page_size(); - let page_count = self.active.len() / page_size; + let page_count = self.active.size() / page_size; for page_num in 0..page_count { - let idx = page_count * 2 + page_num * 2; + 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) as u32; - let dfu_to_offset = (page_num * page_size) as u32; - self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, aligned_buf)?; + 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)?; // Copy the DFU page back to the active page - let active_to_offset = (page_num * page_size) as u32; - let dfu_from_offset = ((page_num + 1) * page_size) as u32; - self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; + 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)?; } Ok(()) @@ -376,11 +373,11 @@ impl BootLoader { } } -fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) { - assert_eq!(active.len() % page_size, 0); - assert_eq!(dfu.len() % page_size, 0); - assert!(dfu.len() - active.len() >= page_size); - assert!(2 + 2 * (active.len() / page_size) <= state.len() / write_size); +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. diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 2b5cc72fa..93d4a4c12 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -49,14 +49,14 @@ impl Default for FirmwareUpdater { let dfu = unsafe { Partition::new( - &__bootloader_dfu_start as *const u32 as usize, - &__bootloader_dfu_end as *const u32 as usize, + &__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 usize, - &__bootloader_state_end as *const u32 as usize, + &__bootloader_state_start as *const u32 as u32, + &__bootloader_state_end as *const u32 as u32, ) }; @@ -121,10 +121,8 @@ impl FirmwareUpdater { _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _read_size = _aligned.len(); - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.len()); + assert!(_update_len as u32 <= self.dfu.size()); #[cfg(feature = "ed25519-dalek")] { @@ -330,11 +328,8 @@ impl FirmwareUpdater { _update_len: usize, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let _end = self.dfu.from + _update_len; - let _read_size = _aligned.len(); - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_end <= self.dfu.to); + assert!(_update_len as u32 <= self.dfu.size()); #[cfg(feature = "ed25519-dalek")] { diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 3109f2b47..8b94b6bdc 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -89,13 +89,11 @@ mod tests { const DFU: Partition = Partition::new(61440, 122880); let mut flash = MemFlash::<131072, 4096, 4>::random(); - let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; - let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + let original = [rand::random::(); ACTIVE.size() as usize]; + let update = [rand::random::(); ACTIVE.size() as usize]; let mut aligned = [0; 4]; - for i in ACTIVE.from..ACTIVE.to { - flash.mem[i] = original[i - ACTIVE.from]; - } + flash.program(ACTIVE.from, &original).unwrap(); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut updater = FirmwareUpdater::new(DFU, STATE); @@ -110,14 +108,9 @@ mod tests { .unwrap() ); - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(flash.mem[i], update[i - ACTIVE.from], "Index {}", i); - } - + flash.assert_eq(ACTIVE.from, &update); // First DFU page is untouched - for i in DFU.from + 4096..DFU.to { - assert_eq!(flash.mem[i], original[i - DFU.from - 4096], "Index {}", i); - } + flash.assert_eq(DFU.from + 4096, &original); // Running again should cause a revert assert_eq!( @@ -127,14 +120,9 @@ mod tests { .unwrap() ); - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(flash.mem[i], original[i - ACTIVE.from], "Index {}", i); - } - + flash.assert_eq(ACTIVE.from, &original); // Last page is untouched - for i in DFU.from..DFU.to - 4096 { - assert_eq!(flash.mem[i], update[i - DFU.from], "Index {}", i); - } + flash.assert_eq(DFU.from, &update); // Mark as booted block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap(); @@ -158,12 +146,10 @@ mod tests { let mut state = MemFlash::<4096, 128, 4>::random(); let mut aligned = [0; 4]; - let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; - let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + let original = [rand::random::(); ACTIVE.size() as usize]; + let update = [rand::random::(); ACTIVE.size() as usize]; - for i in ACTIVE.from..ACTIVE.to { - active.mem[i] = original[i - ACTIVE.from]; - } + active.program(ACTIVE.from, &original).unwrap(); let mut updater = FirmwareUpdater::new(DFU, STATE); @@ -180,14 +166,9 @@ mod tests { .unwrap() ); - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.mem[i], update[i - ACTIVE.from], "Index {}", i); - } - + active.assert_eq(ACTIVE.from, &update); // First DFU page is untouched - for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.mem[i], original[i - DFU.from - 4096], "Index {}", i); - } + dfu.assert_eq(DFU.from + 4096, &original); } #[test] @@ -202,12 +183,10 @@ mod tests { let mut dfu = MemFlash::<16384, 4096, 8>::random(); let mut state = MemFlash::<4096, 128, 4>::random(); - let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; - let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + let original = [rand::random::(); ACTIVE.size() as usize]; + let update = [rand::random::(); ACTIVE.size() as usize]; - for i in ACTIVE.from..ACTIVE.to { - active.mem[i] = original[i - ACTIVE.from]; - } + active.program(ACTIVE.from, &original).unwrap(); let mut updater = FirmwareUpdater::new(DFU, STATE); @@ -226,14 +205,9 @@ mod tests { .unwrap() ); - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.mem[i], update[i - ACTIVE.from], "Index {}", i); - } - + active.assert_eq(ACTIVE.from, &update); // First DFU page is untouched - for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.mem[i], original[i - DFU.from - 4096], "Index {}", i); - } + dfu.assert_eq(DFU.from + 4096, &original); } #[test] diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index dd85405c8..c62379b24 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -32,6 +32,23 @@ impl MemFla pending_write_successes: None, } } + + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + + Ok(()) + } + + 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 diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index ac6b0ed0f..7529059b6 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -6,20 +6,19 @@ use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Partition { /// The offset into the flash where the partition starts. - pub from: usize, + pub from: u32, /// The offset into the flash where the partition ends. - pub to: usize, + pub to: u32, } impl Partition { /// Create a new partition with the provided range - pub const fn new(from: usize, to: usize) -> Self { + pub const fn new(from: u32, to: u32) -> Self { Self { from, to } } /// Return the size of the partition - #[allow(clippy::len_without_is_empty)] - pub const fn len(&self) -> usize { + pub const fn size(&self) -> u32 { self.to - self.from } -- cgit From 7e5ead78fed6b9c352852f2619c523f20e7b7fb7 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Wed, 5 Apr 2023 08:28:46 +0200 Subject: Remove firmware_len --- embassy-boot/boot/src/firmware_updater.rs | 5 ----- 1 file changed, 5 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 93d4a4c12..61c902ed0 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -72,11 +72,6 @@ impl FirmwareUpdater { Self { dfu, state } } - /// Return the length of the DFU area - pub fn firmware_len(&self) -> usize { - self.dfu.len() - } - /// Obtain the current state. /// /// This is useful to check if the bootloader has just done a swap, in order -- cgit From d3ce64254aecb24feded2408119855daf7380e80 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 11 Apr 2023 07:46:05 +0200 Subject: Let update_len be u32 --- embassy-boot/boot/src/firmware_updater.rs | 16 +++++++--------- embassy-boot/boot/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 6aedec003..a2f822f4a 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -114,11 +114,11 @@ impl FirmwareUpdater { _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], - _update_len: usize, + _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len as u32 <= self.dfu.size()); + assert!(_update_len <= self.dfu.size()); #[cfg(feature = "ed25519-dalek")] { @@ -175,11 +175,10 @@ impl FirmwareUpdater { pub async fn hash( &mut self, dfu_flash: &mut F, - update_len: usize, + update_len: u32, chunk_buf: &mut [u8], output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let update_len = update_len as u32; 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?; @@ -335,11 +334,11 @@ impl FirmwareUpdater { _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], - _update_len: usize, + _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len as u32 <= self.dfu.size()); + assert!(_update_len <= self.dfu.size()); #[cfg(feature = "ed25519-dalek")] { @@ -394,11 +393,10 @@ impl FirmwareUpdater { pub fn hash_blocking( &mut self, dfu_flash: &mut F, - update_len: usize, + update_len: u32, chunk_buf: &mut [u8], output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - let update_len = update_len as u32; 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)?; @@ -529,7 +527,7 @@ mod tests { block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); let mut chunk_buf = [0; 2]; let mut hash = [0; 20]; - block_on(updater.hash::<_, Sha1>(&mut flash, update.len(), &mut chunk_buf, &mut hash)).unwrap(); + block_on(updater.hash::<_, Sha1>(&mut flash, 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/lib.rs b/embassy-boot/boot/src/lib.rs index ef9333d36..87457b173 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -255,7 +255,7 @@ mod tests { &mut flash, &public_key.to_bytes(), &signature.to_bytes(), - firmware_len, + firmware_len as u32, &mut aligned, )) .is_ok()); -- cgit From f51cbebffd8a2c7a66888b5cbd288a6eb912a784 Mon Sep 17 00:00:00 2001 From: sander Date: Tue, 11 Apr 2023 13:49:32 +0200 Subject: embassy-boot: add nightly feature gates --- embassy-boot/boot/src/firmware_updater.rs | 128 ++++++++++++++++-------------- embassy-boot/boot/src/mem_flash.rs | 3 + embassy-boot/boot/src/partition.rs | 48 ++++++----- 3 files changed, 97 insertions(+), 82 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index a2f822f4a..f3d76a56c 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -1,5 +1,6 @@ use digest::Digest; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; +#[cfg(feature = "nightly")] use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; @@ -73,17 +74,21 @@ impl FirmwareUpdater { Self { dfu, state } } + // + // Blocking API + // + /// 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( + pub fn get_state_blocking( &mut self, state_flash: &mut F, aligned: &mut [u8], ) -> Result { - self.state.read(state_flash, 0, aligned).await?; + self.state.read_blocking(state_flash, 0, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -109,7 +114,7 @@ impl FirmwareUpdater { /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from /// and written to. #[cfg(feature = "_verify")] - pub async fn verify_and_mark_updated( + pub fn verify_and_mark_updated_blocking( &mut self, _state_and_dfu_flash: &mut F, _public_key: &[u8], @@ -132,8 +137,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_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -154,8 +158,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_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; let r = public_key.verify(&message, &signature); trace!( @@ -168,11 +171,11 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await + self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) } /// Verify the update in DFU with any digest. - pub async fn hash( + pub fn hash_blocking( &mut self, dfu_flash: &mut F, update_len: u32, @@ -181,7 +184,7 @@ impl FirmwareUpdater { ) -> 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_blocking(dfu_flash, offset, chunk_buf)?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -195,13 +198,13 @@ impl FirmwareUpdater { /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. #[cfg(not(feature = "_verify"))] - pub async fn mark_updated( + 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(aligned, SWAP_MAGIC, state_flash).await + self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) } /// Mark firmware boot successful and stop rollback on reset. @@ -209,26 +212,26 @@ 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. - pub async fn mark_booted( + 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(aligned, BOOT_MAGIC, state_flash).await + self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) } - async fn set_magic( + fn set_magic_blocking( &mut self, aligned: &mut [u8], magic: u8, state_flash: &mut F, ) -> Result<(), FirmwareUpdaterError> { - self.state.read(state_flash, 0, aligned).await?; + self.state.read_blocking(state_flash, 0, aligned)?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; + self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; // FIXME: Do not make this assumption. const STATE_ERASE_VALUE: u8 = 0xFF; @@ -238,15 +241,15 @@ impl FirmwareUpdater { } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; + self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; } // Clear magic and progress - self.state.wipe(state_flash).await?; + self.state.wipe_blocking(state_flash)?; // Set magic aligned.fill(magic); - self.state.write(state_flash, 0, aligned).await?; + self.state.write_blocking(state_flash, 0, aligned)?; } Ok(()) } @@ -258,7 +261,7 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware( + pub fn write_firmware_blocking( &mut self, offset: usize, data: &[u8], @@ -267,10 +270,9 @@ impl FirmwareUpdater { assert!(data.len() >= F::ERASE_SIZE); self.dfu - .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) - .await?; + .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; - self.dfu.write(dfu_flash, offset as u32, data).await?; + self.dfu.write_blocking(dfu_flash, offset as u32, data)?; Ok(()) } @@ -278,32 +280,29 @@ impl FirmwareUpdater { /// Prepare for an incoming DFU update by erasing the entire DFU area and /// returning its `Partition`. /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub async fn prepare_update( - &mut self, - dfu_flash: &mut F, - ) -> Result { - self.dfu.wipe(dfu_flash).await?; + /// 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)?; Ok(self.dfu) } +} - // - // Blocking API - // - +// Async API +#[cfg(feature = "nightly")] +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 fn get_state_blocking( + pub async fn get_state( &mut self, state_flash: &mut F, aligned: &mut [u8], ) -> Result { - self.state.read_blocking(state_flash, 0, aligned)?; + self.state.read(state_flash, 0, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -329,7 +328,7 @@ impl FirmwareUpdater { /// The `_aligned` buffer must have a size of F::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 async fn verify_and_mark_updated( &mut self, _state_and_dfu_flash: &mut F, _public_key: &[u8], @@ -352,7 +351,8 @@ 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::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -373,7 +373,8 @@ 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::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; let r = public_key.verify(&message, &signature); trace!( @@ -386,11 +387,11 @@ 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, _state_and_dfu_flash).await } /// Verify the update in DFU with any digest. - pub fn hash_blocking( + pub async fn hash( &mut self, dfu_flash: &mut F, update_len: u32, @@ -399,7 +400,7 @@ impl FirmwareUpdater { ) -> 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(dfu_flash, offset, chunk_buf).await?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -413,13 +414,13 @@ impl FirmwareUpdater { /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. #[cfg(not(feature = "_verify"))] - pub fn mark_updated_blocking( + 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_blocking(aligned, SWAP_MAGIC, state_flash) + self.set_magic(aligned, SWAP_MAGIC, state_flash).await } /// Mark firmware boot successful and stop rollback on reset. @@ -427,26 +428,26 @@ 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. - pub fn mark_booted_blocking( + 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_blocking(aligned, BOOT_MAGIC, state_flash) + self.set_magic(aligned, BOOT_MAGIC, state_flash).await } - fn set_magic_blocking( + async fn set_magic( &mut self, aligned: &mut [u8], magic: u8, state_flash: &mut F, ) -> Result<(), FirmwareUpdaterError> { - self.state.read_blocking(state_flash, 0, aligned)?; + self.state.read(state_flash, 0, aligned).await?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; // FIXME: Do not make this assumption. const STATE_ERASE_VALUE: u8 = 0xFF; @@ -456,15 +457,15 @@ impl FirmwareUpdater { } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; } // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.wipe(state_flash).await?; // Set magic aligned.fill(magic); - self.state.write_blocking(state_flash, 0, aligned)?; + self.state.write(state_flash, 0, aligned).await?; } Ok(()) } @@ -476,7 +477,7 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( + pub async fn write_firmware( &mut self, offset: usize, data: &[u8], @@ -485,9 +486,10 @@ impl FirmwareUpdater { assert!(data.len() >= F::ERASE_SIZE); self.dfu - .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; + .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) + .await?; - self.dfu.write_blocking(dfu_flash, offset as u32, data)?; + self.dfu.write(dfu_flash, offset as u32, data).await?; Ok(()) } @@ -495,16 +497,20 @@ 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 async fn prepare_update( + &mut self, + dfu_flash: &mut F, + ) -> Result { + self.dfu.wipe(dfu_flash).await?; Ok(self.dfu) } } -#[cfg(test)] + + #[cfg(test)] mod tests { use futures::executor::block_on; use sha1::{Digest, Sha1}; diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index c62379b24..3ba84a92c 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -3,6 +3,7 @@ use core::ops::{Bound, Range, RangeBounds}; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +#[cfg(feature = "nightly")] use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; pub struct MemFlash { @@ -134,6 +135,7 @@ impl NorFla } } +#[cfg(feature = "nightly")] impl AsyncReadNorFlash for MemFlash { @@ -148,6 +150,7 @@ impl AsyncR } } +#[cfg(feature = "nightly")] impl AsyncNorFlash for MemFlash { diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index 7529059b6..67bd7abba 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -1,4 +1,5 @@ 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. @@ -23,70 +24,75 @@ impl Partition { } /// Read from the partition on the provided flash - pub async fn read( - &self, - flash: &mut F, - offset: u32, - bytes: &mut [u8], - ) -> Result<(), F::Error> { + 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).await + flash.read(offset, bytes) } /// Write to the partition on the provided flash - pub async fn write(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { + 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).await?; + flash.write(offset, bytes)?; trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); Ok(()) } /// Erase part of the partition on the provided flash - pub async fn erase(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { + 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).await?; + flash.erase(from, to)?; trace!("Erased from 0x{:x} to 0x{:x}", from, to); Ok(()) } /// Erase the entire partition - pub(crate) async fn wipe(&self, flash: &mut F) -> Result<(), F::Error> { + 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).await?; + flash.erase(from, to)?; trace!("Wiped from 0x{:x} to 0x{:x}", from, to); Ok(()) } +} + +// Async API +#[cfg(feature = "nightly")] +impl Partition { /// Read from the partition on the provided flash - pub fn read_blocking(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> { + 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) + flash.read(offset, bytes).await } /// Write to the partition on the provided flash - pub fn write_blocking(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { + 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)?; + flash.write(offset, bytes).await?; 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> { + 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)?; + flash.erase(from, to).await?; 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> { + 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)?; + flash.erase(from, to).await?; trace!("Wiped from 0x{:x} to 0x{:x}", from, to); Ok(()) } -- cgit From 3002ee0dcf0ae186867b2fd869daed609d7a4a23 Mon Sep 17 00:00:00 2001 From: sander Date: Fri, 14 Apr 2023 11:27:23 +0200 Subject: embassy-boot: add nightly feature gate for async usage --- embassy-boot/boot/src/firmware_updater.rs | 137 +++++++++++++++--------------- embassy-boot/boot/src/partition.rs | 51 ++++++----- 2 files changed, 94 insertions(+), 94 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index f3d76a56c..1f2a6d24e 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -74,21 +74,18 @@ impl FirmwareUpdater { Self { dfu, state } } - // - // Blocking API - // - /// 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( + #[cfg(feature = "nightly")] + pub async fn get_state( &mut self, state_flash: &mut F, aligned: &mut [u8], ) -> Result { - self.state.read_blocking(state_flash, 0, aligned)?; + self.state.read(state_flash, 0, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -113,8 +110,8 @@ impl FirmwareUpdater { /// /// The `_aligned` buffer must have a size of F::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( + #[cfg(all(feature = "_verify", feature = "nightly"))] + pub async fn verify_and_mark_updated( &mut self, _state_and_dfu_flash: &mut F, _public_key: &[u8], @@ -137,7 +134,8 @@ 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::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -158,7 +156,8 @@ 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::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; let r = public_key.verify(&message, &signature); trace!( @@ -171,11 +170,12 @@ 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, _state_and_dfu_flash).await } /// Verify the update in DFU with any digest. - pub fn hash_blocking( + #[cfg(feature = "nightly")] + pub async fn hash( &mut self, dfu_flash: &mut F, update_len: u32, @@ -184,7 +184,7 @@ impl FirmwareUpdater { ) -> 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(dfu_flash, offset, chunk_buf).await?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -197,14 +197,14 @@ 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(not(feature = "_verify"))] - pub fn mark_updated_blocking( + #[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_blocking(aligned, SWAP_MAGIC, state_flash) + self.set_magic(aligned, SWAP_MAGIC, state_flash).await } /// Mark firmware boot successful and stop rollback on reset. @@ -212,26 +212,28 @@ 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. - pub fn mark_booted_blocking( + #[cfg(feature = "nightly")] + 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_blocking(aligned, BOOT_MAGIC, state_flash) + self.set_magic(aligned, BOOT_MAGIC, state_flash).await } - fn set_magic_blocking( + #[cfg(feature = "nightly")] + async fn set_magic( &mut self, aligned: &mut [u8], magic: u8, state_flash: &mut F, ) -> Result<(), FirmwareUpdaterError> { - self.state.read_blocking(state_flash, 0, aligned)?; + self.state.read(state_flash, 0, aligned).await?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; // FIXME: Do not make this assumption. const STATE_ERASE_VALUE: u8 = 0xFF; @@ -241,15 +243,15 @@ impl FirmwareUpdater { } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; } // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.wipe(state_flash).await?; // Set magic aligned.fill(magic); - self.state.write_blocking(state_flash, 0, aligned)?; + self.state.write(state_flash, 0, aligned).await?; } Ok(()) } @@ -261,7 +263,8 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( + #[cfg(feature = "nightly")] + pub async fn write_firmware( &mut self, offset: usize, data: &[u8], @@ -270,9 +273,10 @@ impl FirmwareUpdater { assert!(data.len() >= F::ERASE_SIZE); self.dfu - .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; + .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) + .await?; - self.dfu.write_blocking(dfu_flash, offset as u32, data)?; + self.dfu.write(dfu_flash, offset as u32, data).await?; Ok(()) } @@ -280,29 +284,33 @@ 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. + #[cfg(feature = "nightly")] + pub async fn prepare_update( + &mut self, + dfu_flash: &mut F, + ) -> Result { + self.dfu.wipe(dfu_flash).await?; Ok(self.dfu) } -} -// Async API -#[cfg(feature = "nightly")] -impl FirmwareUpdater { + // + // Blocking API + // + /// 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( + pub fn get_state_blocking( &mut self, state_flash: &mut F, aligned: &mut [u8], ) -> Result { - self.state.read(state_flash, 0, aligned).await?; + self.state.read_blocking(state_flash, 0, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -328,7 +336,7 @@ impl FirmwareUpdater { /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from /// and written to. #[cfg(feature = "_verify")] - pub async fn verify_and_mark_updated( + pub fn verify_and_mark_updated_blocking( &mut self, _state_and_dfu_flash: &mut F, _public_key: &[u8], @@ -351,8 +359,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_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -373,8 +380,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_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; let r = public_key.verify(&message, &signature); trace!( @@ -387,11 +393,11 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await + self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) } /// Verify the update in DFU with any digest. - pub async fn hash( + pub fn hash_blocking( &mut self, dfu_flash: &mut F, update_len: u32, @@ -400,7 +406,7 @@ impl FirmwareUpdater { ) -> 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_blocking(dfu_flash, offset, chunk_buf)?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -414,13 +420,13 @@ impl FirmwareUpdater { /// /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. #[cfg(not(feature = "_verify"))] - pub async fn mark_updated( + 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(aligned, SWAP_MAGIC, state_flash).await + self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) } /// Mark firmware boot successful and stop rollback on reset. @@ -428,26 +434,26 @@ 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. - pub async fn mark_booted( + 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(aligned, BOOT_MAGIC, state_flash).await + self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) } - async fn set_magic( + fn set_magic_blocking( &mut self, aligned: &mut [u8], magic: u8, state_flash: &mut F, ) -> Result<(), FirmwareUpdaterError> { - self.state.read(state_flash, 0, aligned).await?; + self.state.read_blocking(state_flash, 0, aligned)?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; + self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; // FIXME: Do not make this assumption. const STATE_ERASE_VALUE: u8 = 0xFF; @@ -457,15 +463,15 @@ impl FirmwareUpdater { } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; + self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; } // Clear magic and progress - self.state.wipe(state_flash).await?; + self.state.wipe_blocking(state_flash)?; // Set magic aligned.fill(magic); - self.state.write(state_flash, 0, aligned).await?; + self.state.write_blocking(state_flash, 0, aligned)?; } Ok(()) } @@ -477,7 +483,7 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware( + pub fn write_firmware_blocking( &mut self, offset: usize, data: &[u8], @@ -486,10 +492,9 @@ impl FirmwareUpdater { assert!(data.len() >= F::ERASE_SIZE); self.dfu - .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) - .await?; + .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; - self.dfu.write(dfu_flash, offset as u32, data).await?; + self.dfu.write_blocking(dfu_flash, offset as u32, data)?; Ok(()) } @@ -497,20 +502,16 @@ impl FirmwareUpdater { /// Prepare for an incoming DFU update by erasing the entire DFU area and /// returning its `Partition`. /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - pub async fn prepare_update( - &mut self, - dfu_flash: &mut F, - ) -> Result { - self.dfu.wipe(dfu_flash).await?; + /// 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)?; Ok(self.dfu) } } - - #[cfg(test)] +#[cfg(test)] mod tests { use futures::executor::block_on; use sha1::{Digest, Sha1}; diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs index 67bd7abba..7b56a8240 100644 --- a/embassy-boot/boot/src/partition.rs +++ b/embassy-boot/boot/src/partition.rs @@ -24,75 +24,74 @@ impl Partition { } /// Read from the partition on the provided flash - pub fn read_blocking(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> { + #[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) + flash.read(offset, bytes).await } /// Write to the partition on the provided flash - pub fn write_blocking(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { + #[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)?; + flash.write(offset, bytes).await?; 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> { + #[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)?; + flash.erase(from, to).await?; 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> { + #[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)?; + flash.erase(from, to).await?; trace!("Wiped from 0x{:x} to 0x{:x}", from, to); Ok(()) } -} - -// Async API -#[cfg(feature = "nightly")] -impl Partition { /// Read from the partition on the provided flash - pub async fn read( - &self, - flash: &mut F, - offset: u32, - bytes: &mut [u8], - ) -> Result<(), F::Error> { + 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).await + flash.read(offset, bytes) } /// Write to the partition on the provided flash - pub async fn write(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { + 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).await?; + flash.write(offset, bytes)?; trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); Ok(()) } /// Erase part of the partition on the provided flash - pub async fn erase(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { + 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).await?; + flash.erase(from, to)?; trace!("Erased from 0x{:x} to 0x{:x}", from, to); Ok(()) } /// Erase the entire partition - pub(crate) async fn wipe(&self, flash: &mut F) -> Result<(), F::Error> { + 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).await?; + flash.erase(from, to)?; trace!("Wiped from 0x{:x} to 0x{:x}", from, to); Ok(()) } -- cgit From a73f9474a0dc4af1ebfb22d7960e6c4b3aca81bd Mon Sep 17 00:00:00 2001 From: sander Date: Thu, 20 Apr 2023 10:56:59 +0200 Subject: embassy-boot: ensure tests can run on the stable compiler --- embassy-boot/boot/src/firmware_updater.rs | 1 + embassy-boot/boot/src/lib.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 1f2a6d24e..92987825f 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -520,6 +520,7 @@ mod tests { use crate::mem_flash::MemFlash; #[test] + #[cfg(feature = "nightly")] fn can_verify_sha1() { const STATE: Partition = Partition::new(0, 4096); const DFU: Partition = Partition::new(65536, 131072); diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index e268d8883..8eb3ba96d 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -83,7 +83,7 @@ mod tests { } #[test] - #[cfg(not(feature = "_verify"))] + #[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); @@ -136,7 +136,7 @@ mod tests { } #[test] - #[cfg(not(feature = "_verify"))] + #[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); @@ -173,7 +173,7 @@ mod tests { } #[test] - #[cfg(not(feature = "_verify"))] + #[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); @@ -212,7 +212,7 @@ mod tests { } #[test] - #[cfg(feature = "_verify")] + #[cfg(all(feature = "nightly", feature = "_verify"))] fn test_verify() { // The following key setup is based on: // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example -- cgit From 9f7392474b6a6e3a2f20e6419743afb196456c66 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 19 May 2023 17:12:29 +0200 Subject: Update Rust nightly. --- embassy-boot/boot/src/lib.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 8eb3ba96d..4521fecb0 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] -#![allow(incomplete_features)] #![no_std] #![warn(missing_docs)] #![doc = include_str!("../README.md")] -- cgit From 18c62aa5b475161c0cbd98c23c006691e084cb2b Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Mon, 22 May 2023 11:32:39 +0200 Subject: Protect default implementations for FirmwareUpdater and BootLoader It seems as if the arm compiler can does not care about whether the bootloader symbols are undefined if the default() function is never used. The x64 compiler does care however, so this change ensures that we can instantiate the types from tests. --- embassy-boot/boot/src/firmware_updater.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs index 92987825f..aeea206f9 100644 --- a/embassy-boot/boot/src/firmware_updater.rs +++ b/embassy-boot/boot/src/firmware_updater.rs @@ -40,6 +40,7 @@ pub struct FirmwareUpdater { dfu: Partition, } +#[cfg(target_os = "none")] impl Default for FirmwareUpdater { fn default() -> Self { extern "C" { -- cgit From c844894a6e25bbf38c10ed7e60ee554e565b56b1 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Mon, 29 May 2023 21:29:13 +0200 Subject: Split the FirmwareUpdater into blocking and async --- embassy-boot/boot/src/firmware_updater.rs | 543 --------------------- embassy-boot/boot/src/firmware_updater/asynch.rs | 251 ++++++++++ embassy-boot/boot/src/firmware_updater/blocking.rs | 221 +++++++++ embassy-boot/boot/src/firmware_updater/mod.rs | 71 +++ 4 files changed, 543 insertions(+), 543 deletions(-) delete mode 100644 embassy-boot/boot/src/firmware_updater.rs create mode 100644 embassy-boot/boot/src/firmware_updater/asynch.rs create mode 100644 embassy-boot/boot/src/firmware_updater/blocking.rs create mode 100644 embassy-boot/boot/src/firmware_updater/mod.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs deleted file mode 100644 index aeea206f9..000000000 --- a/embassy-boot/boot/src/firmware_updater.rs +++ /dev/null @@ -1,543 +0,0 @@ -use digest::Digest; -use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -#[cfg(feature = "nightly")] -use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; - -use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; - -/// Errors returned by FirmwareUpdater -#[derive(Debug)] -pub enum FirmwareUpdaterError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Signature errors. - Signature(signature::Error), -} - -#[cfg(feature = "defmt")] -impl defmt::Format for FirmwareUpdaterError { - fn format(&self, fmt: defmt::Formatter) { - match self { - FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), - FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), - } - } -} - -impl From for FirmwareUpdaterError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - 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) - } -} - -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 } - } - - /// 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`. - #[cfg(feature = "nightly")] - pub async fn get_state( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read(state_flash, 0, aligned).await?; - - if !aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - /// - /// # Safety - /// - /// The `_aligned` buffer must have a size of F::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( - &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()); - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; - 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?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; - let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; - let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; - 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?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await - } - - /// Verify the update in DFU with any digest. - #[cfg(feature = "nightly")] - 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?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - /// - /// # 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 - } - - /// 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. - #[cfg(feature = "nightly")] - 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 - } - - #[cfg(feature = "nightly")] - 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?; - - 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; - - 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?; - } - - // Clear magic and progress - self.state.wipe(state_flash).await?; - - // Set magic - aligned.fill(magic); - self.state.write(state_flash, 0, aligned).await?; - } - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - #[cfg(feature = "nightly")] - pub async fn write_firmware( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); - - self.dfu - .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) - .await?; - - self.dfu.write(dfu_flash, offset as u32, data).await?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - #[cfg(feature = "nightly")] - pub async fn prepare_update( - &mut self, - dfu_flash: &mut F, - ) -> Result { - self.dfu.wipe(dfu_flash).await?; - - Ok(self.dfu) - } - - // - // Blocking API - // - - /// 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)?; - - if !aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - /// - /// # Safety - /// - /// The `_aligned` buffer must have a size of F::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( - &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()); - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; - 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)?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; - let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; - let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; - 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)?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) - } - - /// Verify the update in DFU with any digest. - pub fn hash_blocking( - &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)?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::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) - } - - /// 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) - } - - 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)?; - - 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; - - 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)?; - } - - // Clear magic and progress - self.state.wipe_blocking(state_flash)?; - - // Set magic - aligned.fill(magic); - self.state.write_blocking(state_flash, 0, aligned)?; - } - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); - - self.dfu - .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; - - self.dfu.write_blocking(dfu_flash, offset as u32, data)?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware_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)?; - - Ok(self.dfu) - } -} - -#[cfg(test)] -mod tests { - use futures::executor::block_on; - use sha1::{Digest, Sha1}; - - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - #[cfg(feature = "nightly")] - 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 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 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(); - - assert_eq!(Sha1::digest(update).as_slice(), hash); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..bdd03bff4 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -0,0 +1,251 @@ +use digest::Digest; +use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + +use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + +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?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::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( + &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()); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + 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?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + 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?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await + } + + /// Verify the update in DFU with any digest. + 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?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # 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 + } + + /// 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 + } + + 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?; + + 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; + + 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?; + } + + // Clear magic and progress + self.state.wipe(state_flash).await?; + + // Set magic + aligned.fill(magic); + self.state.write(state_flash, 0, aligned).await?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware( + &mut self, + offset: usize, + data: &[u8], + dfu_flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= F::ERASE_SIZE); + + self.dfu + .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) + .await?; + + self.dfu.write(dfu_flash, offset as u32, data).await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub async fn prepare_update( + &mut self, + dfu_flash: &mut F, + ) -> Result { + self.dfu.wipe(dfu_flash).await?; + + Ok(self.dfu) + } +} + +#[cfg(test)] +mod tests { + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[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 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 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(); + + 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 new file mode 100644 index 000000000..50caaf08c --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -0,0 +1,221 @@ +use digest::Digest; +use embedded_storage::nor_flash::NorFlash; + +use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + +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 } + } + + /// 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)?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::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( + &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()); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + 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)?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + 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)?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) + } + + /// Verify the update in DFU with any digest. + pub fn hash_blocking( + &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)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::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) + } + + /// 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) + } + + 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)?; + + 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; + + 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)?; + } + + // Clear magic and progress + self.state.wipe_blocking(state_flash)?; + + // Set magic + aligned.fill(magic); + self.state.write_blocking(state_flash, 0, aligned)?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware_blocking( + &mut self, + offset: usize, + data: &[u8], + dfu_flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= F::ERASE_SIZE); + + self.dfu + .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; + + self.dfu.write_blocking(dfu_flash, offset as u32, data)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware_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)?; + + Ok(self.dfu) + } +} diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs new file mode 100644 index 000000000..e09f5eb6c --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -0,0 +1,71 @@ +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +use crate::Partition; + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + 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) + } +} -- cgit From 94046f30ffefc96a7b110ed1d9bb60d64cb4780f Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Sat, 27 May 2023 19:26:45 +0200 Subject: Remove the usage of the local Partition type in BootLoader --- embassy-boot/boot/src/boot_loader.rs | 340 ++++++++++++----------------------- 1 file changed, 111 insertions(+), 229 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index b959de2c4..50eb5e668 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -1,6 +1,8 @@ use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use crate::{State, BOOT_MAGIC, SWAP_MAGIC}; + +const STATE_ERASE_VALUE: u8 = 0xFF; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -30,65 +32,39 @@ 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 works with any flash implementing embedded_storage. +pub struct BootLoader { + /// Flash type used for the active partition - the partition which will be booted from. + active: ACTIVE, + /// Flash type used for the dfu partition - he partition which will be swapped in when requested. + 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; -} - -trait FlashConfigEx { - fn page_size() -> u32; + /// + /// 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 FlashConfigEx for T { +impl BootLoader { /// 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 - } -} + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; -/// 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, -} - -impl BootLoader { - /// Create a new instance of a bootloader with the given partitions. + /// 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 { + pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> 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 - } - /// Perform necessary boot preparations like swapping images. /// /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap @@ -175,195 +151,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,11 +328,16 @@ 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); +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); } /// A flash wrapper implementing the Flash and embedded_storage traits. @@ -436,98 +396,20 @@ where } } -/// 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 - } -} - #[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); } } -- cgit From 5205b5b09588d6e0006e5479764ee94b113b03b9 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:36:42 +0200 Subject: Split FirmwareUpdater into async and blocking types --- embassy-boot/boot/src/firmware_updater/asynch.rs | 176 ++++++++++--------- embassy-boot/boot/src/firmware_updater/blocking.rs | 187 +++++++++++++-------- embassy-boot/boot/src/firmware_updater/mod.rs | 51 ++---- embassy-boot/boot/src/lib.rs | 4 +- 4 files changed, 236 insertions(+), 182 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index bdd03bff4..d0780bdf1 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; +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage_async::nor_flash::NorFlash; + +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, +} + +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + #[cfg(target_os = "none")] + pub fn from_linkerfile(flash: &'a Mutex) -> Self { + use embassy_sync::mutex::Mutex; + + 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) + }; -use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + 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..c44126149 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; +use embassy_embedded_hal::flash::partition::BlockingPartition; +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, +} + +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + use core::cell::RefCell; + + use embassy_sync::blocking_mutex::Mutex; + + 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(&mut self.dfu) + } +} - Ok(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..af2d3ce02 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -11,8 +11,10 @@ mod mem_flash; mod partition; pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; -pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; pub use partition::Partition; +#[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; -- cgit From c5ec453ec1e87c2f5c5047bf357ae99298aa6d12 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:38:00 +0200 Subject: Add bootloader helper for creating config from linkerfile symbols --- embassy-boot/boot/src/boot_loader.rs | 136 ++++++++++++++++++----------------- embassy-boot/boot/src/lib.rs | 2 +- 2 files changed, 72 insertions(+), 66 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 50eb5e668..bdf7bd7fd 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -1,8 +1,11 @@ -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use core::cell::RefCell; -use crate::{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}; -const STATE_ERASE_VALUE: u8 = 0xFF; +use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -32,14 +35,69 @@ where } } +/// Bootloader flash configuration holding the three flashes used by the bootloader +/// +/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. +/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct BootLoaderConfig { + /// Flash type used for the active partition - the partition which will be booted from. + pub active: ACTIVE, + /// Flash type used for the dfu partition - the partition which will be swapped in when requested. + pub dfu: DFU, + /// Flash type used for the state partition. + pub state: STATE, +} + +impl<'a, FLASH: NorFlash> + BootLoaderConfig< + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + > +{ + /// Create a bootloader config from the flash and address symbols defined in the linkerfile + // #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + let start = &__bootloader_active_start as *const u32 as u32; + let end = &__bootloader_active_end as *const u32 as u32; + trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { active, dfu, state } + } +} + /// BootLoader works with any flash implementing embedded_storage. pub struct BootLoader { - /// Flash type used for the active partition - the partition which will be booted from. active: ACTIVE, - /// Flash type used for the dfu partition - he partition which will be swapped in when requested. dfu: DFU, - /// Flash type used for the state partition. - /// /// The state partition has the following format: /// All ranges are in multiples of WRITE_SIZE bytes. /// | Range | Description | @@ -61,8 +119,12 @@ impl BootLoader Self { - Self { active, dfu, state } + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } } /// Perform necessary boot preparations like swapping images. @@ -340,62 +402,6 @@ fn assert_partitions( assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / 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) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index af2d3ce02..f2034fa8a 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -10,8 +10,8 @@ mod firmware_updater; mod mem_flash; mod partition; -pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; pub use partition::Partition; +pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; #[cfg(feature = "nightly")] pub use firmware_updater::FirmwareUpdater; pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; -- cgit From 1cd87f0028c8a0f9b87a09016a22179fb05ced33 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:40:04 +0200 Subject: Cleanup MemFlash --- embassy-boot/boot/src/mem_flash.rs | 106 ++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 50 deletions(-) (limited to 'embassy-boot/boot/src') 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,21 +34,61 @@ impl MemFla } } - pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + 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); - self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + 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(()) } - 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); + fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { + let from = from as usize; + let to = to as usize; + assert!(from % ERASE_SIZE == 0); + assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); + for i in from..to { + self.mem[i] = 0xFF; } + Ok(()) + } + + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + + Ok(()) } } @@ -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> { - 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; - } + self.write(offset, bytes) + } - 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) } } -- cgit From b23e40f72242a9f4759d8a6d4a924c8c9d041c67 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:41:10 +0200 Subject: Add TestFlash helper --- embassy-boot/boot/src/lib.rs | 6 +++ embassy-boot/boot/src/test_flash/asynch.rs | 57 ++++++++++++++++++++++++ embassy-boot/boot/src/test_flash/blocking.rs | 65 ++++++++++++++++++++++++++++ embassy-boot/boot/src/test_flash/mod.rs | 7 +++ 4 files changed, 135 insertions(+) create mode 100644 embassy-boot/boot/src/test_flash/asynch.rs create mode 100644 embassy-boot/boot/src/test_flash/blocking.rs create mode 100644 embassy-boot/boot/src/test_flash/mod.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index f2034fa8a..c76087ff1 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -7,10 +7,16 @@ 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 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; 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..b5b3c2538 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/asynch.rs @@ -0,0 +1,57 @@ +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; + +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(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + Self { + active: Mutex::new(active), + dfu: Mutex::new(dfu), + state: Mutex::new(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 { + super::BlockingTestFlash::new(self.active.into_inner(), self.dfu.into_inner(), self.state.into_inner()) + } +} 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..77876a218 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/blocking.rs @@ -0,0 +1,65 @@ +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; + +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(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + Self { + active: Mutex::new(RefCell::new(active)), + dfu: Mutex::new(RefCell::new(dfu)), + state: Mutex::new(RefCell::new(state)), + } + } + + pub fn active(&self) -> BlockingPartition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> BlockingPartition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> BlockingPartition { + Self::create_partition(&self.state) + } + + pub fn create_partition( + mutex: &Mutex>, + ) -> BlockingPartition { + BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) + } +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, +{ + pub fn into_async(self) -> super::AsyncTestFlash { + super::AsyncTestFlash::new( + self.active.into_inner().into_inner(), + self.dfu.into_inner().into_inner(), + self.state.into_inner().into_inner(), + ) + } +} 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; -- cgit From 551f76c70067bfa14b159a198cdb92f810f40607 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:44:12 +0200 Subject: Remove legacy Partition type and use the one from embedded-hal --- embassy-boot/boot/src/lib.rs | 2 - embassy-boot/boot/src/partition.rs | 144 ------------------------------------- 2 files changed, 146 deletions(-) delete mode 100644 embassy-boot/boot/src/partition.rs (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index c76087ff1..15d3a4f21 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -9,11 +9,9 @@ mod digest_adapters; mod firmware_updater; #[cfg(test)] mod mem_flash; -mod partition; #[cfg(test)] mod test_flash; -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; 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); - } - } -} -- cgit From c6a984f506530bc08464800abc332be9c49ac198 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:55:49 +0200 Subject: Align tests --- embassy-boot/boot/src/boot_loader.rs | 2 +- embassy-boot/boot/src/lib.rs | 248 +++++++++++++++------------ embassy-boot/boot/src/test_flash/asynch.rs | 17 +- embassy-boot/boot/src/test_flash/blocking.rs | 22 ++- 4 files changed, 167 insertions(+), 122 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index bdf7bd7fd..a8c19197b 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -40,7 +40,7 @@ where /// 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 { +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. diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 15d3a4f21..d13eafdd0 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -51,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] @@ -73,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(); - - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + const FIRMWARE_SIZE: usize = 57344; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - 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 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 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 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); - - let mut active = MemFlash::<16384, 4096, 8>::random(); - let mut dfu = MemFlash::<16384, 2048, 8>::random(); - let mut state = MemFlash::<4096, 128, 4>::random(); + fn test_swap_state_active_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::<12288, 4096, 8>::random(), + dfu: MemFlash::<16384, 2048, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - 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]; - - active.program(ACTIVE.from, &original).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); + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + 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; 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] @@ -239,25 +273,25 @@ 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(flash.dfu(), 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/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs index b5b3c2538..3ac9e71ab 100644 --- a/embassy-boot/boot/src/test_flash/asynch.rs +++ b/embassy-boot/boot/src/test_flash/asynch.rs @@ -3,6 +3,8 @@ 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, @@ -20,11 +22,11 @@ where DFU: NorFlash, STATE: NorFlash, { - pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - active: Mutex::new(active), - dfu: Mutex::new(dfu), - state: Mutex::new(state), + active: Mutex::new(config.active), + dfu: Mutex::new(config.dfu), + state: Mutex::new(config.state), } } @@ -52,6 +54,11 @@ where STATE: NorFlash + embedded_storage::nor_flash::NorFlash, { pub fn into_blocking(self) -> super::BlockingTestFlash { - super::BlockingTestFlash::new(self.active.into_inner(), self.dfu.into_inner(), self.state.into_inner()) + 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 index 77876a218..ba33c9208 100644 --- a/embassy-boot/boot/src/test_flash/blocking.rs +++ b/embassy-boot/boot/src/test_flash/blocking.rs @@ -5,6 +5,8 @@ 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, @@ -22,11 +24,11 @@ where DFU: NorFlash, STATE: NorFlash, { - pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - active: Mutex::new(RefCell::new(active)), - dfu: Mutex::new(RefCell::new(dfu)), - state: Mutex::new(RefCell::new(state)), + active: Mutex::new(RefCell::new(config.active)), + dfu: Mutex::new(RefCell::new(config.dfu)), + state: Mutex::new(RefCell::new(config.state)), } } @@ -49,6 +51,7 @@ where } } +#[cfg(feature = "nightly")] impl BlockingTestFlash where ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, @@ -56,10 +59,11 @@ where STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, { pub fn into_async(self) -> super::AsyncTestFlash { - super::AsyncTestFlash::new( - self.active.into_inner().into_inner(), - self.dfu.into_inner().into_inner(), - self.state.into_inner().into_inner(), - ) + 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) } } -- cgit From b703db4c09d1d4f0c4296a7fd7b808d96f29e1a2 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 14:07:35 +0200 Subject: Fix verify test --- embassy-boot/boot/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index d13eafdd0..45a87bd0e 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -287,7 +287,10 @@ mod tests { // On with the test let flash = flash.into_async(); - let mut updater = FirmwareUpdater::new(flash.dfu(), flash.state()); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); let mut aligned = [0; 4]; -- cgit From c22d2b5b5bbc5e3c7d3a039e90b50d39809a10f2 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 14:13:53 +0200 Subject: Remove unused use's --- embassy-boot/boot/src/firmware_updater/asynch.rs | 8 ++++---- embassy-boot/boot/src/firmware_updater/blocking.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index d0780bdf1..0b3f88313 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -1,5 +1,7 @@ use digest::Digest; +#[cfg(target_os = "none")] use embassy_embedded_hal::flash::partition::Partition; +#[cfg(target_os = "none")] use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage_async::nor_flash::NorFlash; @@ -13,14 +15,12 @@ pub struct FirmwareUpdater { 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 - #[cfg(target_os = "none")] - pub fn from_linkerfile(flash: &'a Mutex) -> Self { - use embassy_sync::mutex::Mutex; - + pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { extern "C" { static __bootloader_state_start: u32; static __bootloader_state_end: u32; diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index c44126149..551150c4f 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -1,5 +1,7 @@ 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; @@ -13,16 +15,14 @@ pub struct BlockingFirmwareUpdater { 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 - #[cfg(target_os = "none")] - pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { - use core::cell::RefCell; - - use embassy_sync::blocking_mutex::Mutex; - + 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; -- cgit From 76659d9003104f8edd2472a36149565e4a55c0e6 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 19 Jun 2023 22:37:23 +0200 Subject: Prevent accidental revert when using firmware updater This change prevents accidentally overwriting the previous firmware before the new one has been marked as booted. --- embassy-boot/boot/src/firmware_updater/asynch.rs | 34 ++++++++++++++++++++-- embassy-boot/boot/src/firmware_updater/blocking.rs | 32 ++++++++++++++++++-- embassy-boot/boot/src/firmware_updater/mod.rs | 3 ++ embassy-boot/boot/src/lib.rs | 12 ++++++-- 4 files changed, 72 insertions(+), 9 deletions(-) (limited to 'embassy-boot/boot/src') diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index 0b3f88313..20731ee0a 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -56,6 +56,16 @@ impl FirmwareUpdater { } } + // Make sure we are running a booted firmware to avoid reverting to a bad state. + async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + if self.get_state(aligned).await? == State::Boot { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + /// Obtain the current state. /// /// This is useful to check if the bootloader has just done a swap, in order @@ -98,6 +108,8 @@ impl FirmwareUpdater { assert_eq!(_aligned.len(), STATE::WRITE_SIZE); assert!(_update_len <= self.dfu.capacity() as u32); + self.verify_booted(_aligned).await?; + #[cfg(feature = "ed25519-dalek")] { use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; @@ -217,8 +229,16 @@ 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]) -> Result<(), FirmwareUpdaterError> { + pub async fn write_firmware( + &mut self, + aligned: &mut [u8], + offset: usize, + data: &[u8], + ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= DFU::ERASE_SIZE); + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + + self.verify_booted(aligned).await?; self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; @@ -232,7 +252,14 @@ 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) -> Result<&mut DFU, FirmwareUpdaterError> { + /// + /// # Safety + /// + /// 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 prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.verify_booted(aligned).await?; + self.dfu.erase(0, self.dfu.capacity() as u32).await?; Ok(&mut self.dfu) @@ -255,13 +282,14 @@ mod tests { let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); let state = Partition::new(&flash, 0, 4096); let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; let mut to_write = [0; 4096]; to_write[..7].copy_from_slice(update.as_slice()); let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); - block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); + block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap(); let mut chunk_buf = [0; 2]; let mut hash = [0; 20]; block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index 551150c4f..f03f53e4d 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -58,6 +58,16 @@ impl BlockingFirmwareUpdater { } } + // Make sure we are running a booted firmware to avoid reverting to a bad state. + fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + if self.get_state(aligned)? == State::Boot { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + /// Obtain the current state. /// /// This is useful to check if the bootloader has just done a swap, in order @@ -100,6 +110,8 @@ impl BlockingFirmwareUpdater { assert_eq!(_aligned.len(), STATE::WRITE_SIZE); assert!(_update_len <= self.dfu.capacity() as u32); + self.verify_booted(_aligned)?; + #[cfg(feature = "ed25519-dalek")] { use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; @@ -219,8 +231,15 @@ impl BlockingFirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + pub fn write_firmware( + &mut self, + aligned: &mut [u8], + offset: usize, + data: &[u8], + ) -> Result<(), FirmwareUpdaterError> { assert!(data.len() >= DFU::ERASE_SIZE); + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.verify_booted(aligned)?; self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; @@ -234,7 +253,13 @@ impl BlockingFirmwareUpdater { /// /// 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> { + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.verify_booted(aligned)?; self.dfu.erase(0, self.dfu.capacity() as u32)?; Ok(&mut self.dfu) @@ -264,7 +289,8 @@ mod tests { 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 aligned = [0; 8]; + updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap(); let mut chunk_buf = [0; 2]; let mut hash = [0; 20]; updater diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs index a37984a3a..55ce8f363 100644 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -26,6 +26,8 @@ pub enum FirmwareUpdaterError { Flash(NorFlashErrorKind), /// Signature errors. Signature(signature::Error), + /// Bad state. + BadState, } #[cfg(feature = "defmt")] @@ -34,6 +36,7 @@ impl defmt::Format for FirmwareUpdaterError { match self { FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), } } } diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 45a87bd0e..016362b86 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -51,6 +51,8 @@ impl AsMut<[u8]> for AlignedBuffer { #[cfg(test)] mod tests { + #![allow(unused_imports)] + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; #[cfg(feature = "nightly")] use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; @@ -120,9 +122,13 @@ mod tests { dfu: flash.dfu(), state: flash.state(), }); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); block_on(updater.mark_updated(&mut aligned)).unwrap(); + // Writing after marking updated is not allowed until marked as booted. + let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)); + assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); + let flash = flash.into_blocking(); let mut bootloader = BootLoader::new(BootLoaderConfig { active: flash.active(), @@ -188,7 +194,7 @@ mod tests { dfu: flash.dfu(), state: flash.state(), }); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); block_on(updater.mark_updated(&mut aligned)).unwrap(); let flash = flash.into_blocking(); @@ -230,7 +236,7 @@ mod tests { dfu: flash.dfu(), state: flash.state(), }); - block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); block_on(updater.mark_updated(&mut aligned)).unwrap(); let flash = flash.into_blocking(); -- cgit