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.
This commit is contained in:
Rasmus Melchior Jacobsen 2023-04-04 21:09:30 +02:00
parent 9242ad89d4
commit 25577e0eaf
4 changed files with 118 additions and 57 deletions

View File

@ -32,14 +32,13 @@ where
/// Extension of the embedded-storage flash type information with block size and erase value. /// Extension of the embedded-storage flash type information with block size and erase value.
pub trait Flash: NorFlash { 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. /// 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; 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 { pub trait FlashConfig {
/// Flash type used for the state partition. /// Flash type used for the state partition.
type STATE: Flash; type STATE: Flash;
@ -62,12 +61,12 @@ trait FlashConfigEx {
impl<T: FlashConfig> FlashConfigEx for T { impl<T: FlashConfig> FlashConfigEx for T {
fn page_size() -> usize { 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 /// BootLoader works with any flash implementing embedded_storage.
/// different page sizes and flash write sizes.
pub struct BootLoader { pub struct BootLoader {
// Page with current state of bootloader. The state partition has the following format: // Page with current state of bootloader. The state partition has the following format:
// All ranges are in multiples of WRITE_SIZE bytes. // All ranges are in multiples of WRITE_SIZE bytes.
@ -184,7 +183,9 @@ impl BootLoader {
/// ///
pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> { pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
// Ensure we have enough progress pages to store copy progress // 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!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
assert_partitions(self.active, self.dfu, self.state, P::page_size(), 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], aligned_buf: &mut [u8],
) -> Result<(), BootError> { ) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= idx { if self.current_progress(p, aligned_buf)? <= idx {
let mut offset = from_offset; let page_size = P::page_size() as u32;
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 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 offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
for chunk in aligned_buf.chunks(P::ACTIVE::BLOCK_SIZE) { self.dfu
self.active.write_blocking(p.active(), offset, chunk)?; .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
offset += chunk.len() as u32; self.active
.write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
} }
self.update_progress(idx, p, aligned_buf)?; self.update_progress(idx, p, aligned_buf)?;
} }
Ok(()) Ok(())
@ -305,20 +304,18 @@ impl BootLoader {
aligned_buf: &mut [u8], aligned_buf: &mut [u8],
) -> Result<(), BootError> { ) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= idx { if self.current_progress(p, aligned_buf)? <= idx {
let mut offset = from_offset; let page_size = P::page_size() as u32;
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 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 offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
for chunk in aligned_buf.chunks(P::DFU::BLOCK_SIZE) { self.active
self.dfu.write_blocking(p.dfu(), offset, chunk)?; .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
offset += chunk.len() as u32; self.dfu
.write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
} }
self.update_progress(idx, p, aligned_buf)?; self.update_progress(idx, p, aligned_buf)?;
} }
Ok(()) 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. /// A flash wrapper implementing the Flash and embedded_storage traits.
pub struct BootFlash<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF> pub struct BootFlash<F, const ERASE_VALUE: u8 = 0xFF>
where where
F: NorFlash + ReadNorFlash, F: NorFlash + ReadNorFlash,
{ {
flash: F, flash: F,
} }
impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<F, BLOCK_SIZE, ERASE_VALUE> impl<F, const ERASE_VALUE: u8> BootFlash<F, ERASE_VALUE>
where where
F: NorFlash + ReadNorFlash, F: NorFlash + ReadNorFlash,
{ {
@ -406,22 +403,21 @@ where
} }
} }
impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> impl<F, const ERASE_VALUE: u8> Flash for BootFlash<F, ERASE_VALUE>
where where
F: NorFlash + ReadNorFlash, F: NorFlash + ReadNorFlash,
{ {
const BLOCK_SIZE: usize = BLOCK_SIZE;
const ERASE_VALUE: u8 = ERASE_VALUE; const ERASE_VALUE: u8 = ERASE_VALUE;
} }
impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> impl<F, const ERASE_VALUE: u8> ErrorType for BootFlash<F, ERASE_VALUE>
where where
F: ReadNorFlash + NorFlash, F: ReadNorFlash + NorFlash,
{ {
type Error = F::Error; type Error = F::Error;
} }
impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> impl<F, const ERASE_VALUE: u8> NorFlash for BootFlash<F, ERASE_VALUE>
where where
F: ReadNorFlash + NorFlash, F: ReadNorFlash + NorFlash,
{ {
@ -437,7 +433,7 @@ where
} }
} }
impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> impl<F, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, ERASE_VALUE>
where where
F: ReadNorFlash + NorFlash, F: ReadNorFlash + NorFlash,
{ {

View File

@ -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<F, const ERASE_SIZE: usize>(pub F);
impl<F, const ERASE_SIZE: usize> LargeErase<F, ERASE_SIZE> {
pub const fn new(flash: F) -> Self {
Self(flash)
}
}
impl<F: Flash, const ERASE_SIZE: usize> Flash for LargeErase<F, ERASE_SIZE> {
const ERASE_VALUE: u8 = F::ERASE_VALUE;
}
impl<F: ErrorType, const ERASE_SIZE: usize> ErrorType for LargeErase<F, ERASE_SIZE> {
type Error = F::Error;
}
impl<F: NorFlash, const ERASE_SIZE: usize> NorFlash for LargeErase<F, ERASE_SIZE> {
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<F: ReadNorFlash, const ERASE_SIZE: usize> ReadNorFlash for LargeErase<F, ERASE_SIZE> {
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<F: AsyncNorFlash, const ERASE_SIZE: usize> AsyncNorFlash for LargeErase<F, ERASE_SIZE> {
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<F: AsyncReadNorFlash, const ERASE_SIZE: usize> AsyncReadNorFlash for LargeErase<F, ERASE_SIZE> {
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()
}
}

View File

@ -7,6 +7,7 @@ mod fmt;
mod boot_loader; mod boot_loader;
mod firmware_updater; mod firmware_updater;
mod large_erase;
mod mem_flash; mod mem_flash;
mod partition; mod partition;
@ -48,6 +49,7 @@ mod tests {
use futures::executor::block_on; use futures::executor::block_on;
use super::*; use super::*;
use crate::large_erase::LargeErase;
use crate::mem_flash::MemFlash; use crate::mem_flash::MemFlash;
/* /*
@ -99,14 +101,10 @@ mod tests {
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut updater = FirmwareUpdater::new(DFU, STATE); let mut updater = FirmwareUpdater::new(DFU, STATE);
let mut offset = 0; block_on(updater.write_firmware(0, &update, &mut flash)).unwrap();
for chunk in update.chunks(4096) {
block_on(updater.write_firmware(offset, chunk, &mut flash)).unwrap();
offset += chunk.len();
}
block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
let mut page = [0; 4096]; let mut page = [0; 1024];
assert_eq!( assert_eq!(
State::Swap, State::Swap,
bootloader bootloader
@ -158,7 +156,7 @@ mod tests {
const DFU: Partition = Partition::new(0, 16384); const DFU: Partition = Partition::new(0, 16384);
let mut active = MemFlash::<16384, 4096, 8>::random(); 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 state = MemFlash::<4096, 128, 4>::random();
let mut aligned = [0; 4]; let mut aligned = [0; 4];
@ -171,11 +169,7 @@ mod tests {
let mut updater = FirmwareUpdater::new(DFU, STATE); let mut updater = FirmwareUpdater::new(DFU, STATE);
let mut offset = 0; block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
for chunk in update.chunks(2048) {
block_on(updater.write_firmware(offset, chunk, &mut dfu)).unwrap();
offset += chunk.len();
}
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
@ -194,7 +188,7 @@ mod tests {
// First DFU page is untouched // First DFU page is untouched
for i in DFU.from + 4096..DFU.to { 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); const DFU: Partition = Partition::new(0, 16384);
let mut aligned = [0; 4]; 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 dfu = MemFlash::<16384, 4096, 8>::random();
let mut state = MemFlash::<4096, 128, 4>::random(); let mut state = MemFlash::<4096, 128, 4>::random();
@ -214,16 +208,12 @@ mod tests {
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
for i in ACTIVE.from..ACTIVE.to { 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 updater = FirmwareUpdater::new(DFU, STATE);
let mut offset = 0; block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
for chunk in update.chunks(4096) {
block_on(updater.write_firmware(offset, chunk, &mut dfu)).unwrap();
offset += chunk.len();
}
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
@ -239,7 +229,7 @@ mod tests {
); );
for i in ACTIVE.from..ACTIVE.to { 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 // First DFU page is untouched

View File

@ -47,7 +47,6 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Defaul
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Flash impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Flash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{ {
const BLOCK_SIZE: usize = ERASE_SIZE;
const ERASE_VALUE: u8 = 0xFF; const ERASE_VALUE: u8 = 0xFF;
} }