From 62e799da09e144c9bd2bc3935011913e62c86d16 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Fri, 26 May 2023 21:40:12 +0200 Subject: Create flash partition for shared flash access --- embassy-embedded-hal/src/flash/concat_flash.rs | 228 +++++++++++++++++++++++++ embassy-embedded-hal/src/flash/mem_flash.rs | 127 ++++++++++++++ embassy-embedded-hal/src/flash/mod.rs | 9 + embassy-embedded-hal/src/flash/partition.rs | 150 ++++++++++++++++ 4 files changed, 514 insertions(+) create mode 100644 embassy-embedded-hal/src/flash/concat_flash.rs create mode 100644 embassy-embedded-hal/src/flash/mem_flash.rs create mode 100644 embassy-embedded-hal/src/flash/mod.rs create mode 100644 embassy-embedded-hal/src/flash/partition.rs (limited to 'embassy-embedded-hal/src/flash') diff --git a/embassy-embedded-hal/src/flash/concat_flash.rs b/embassy-embedded-hal/src/flash/concat_flash.rs new file mode 100644 index 000000000..1ea84269c --- /dev/null +++ b/embassy-embedded-hal/src/flash/concat_flash.rs @@ -0,0 +1,228 @@ +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash}; +#[cfg(feature = "nightly")] +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +/// Convenience helper for concatenating two consecutive flashes into one. +/// This is especially useful if used with "flash regions", where one may +/// want to concatenate multiple regions into one larger region. +pub struct ConcatFlash(First, Second); + +impl ConcatFlash { + /// Create a new flash that concatenates two consecutive flashes. + pub fn new(first: First, second: Second) -> Self { + Self(first, second) + } +} + +const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize { + if first_read_size != second_read_size { + panic!("The read size for the concatenated flashes must be the same"); + } + first_read_size +} + +const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize { + if first_write_size != second_write_size { + panic!("The write size for the concatenated flashes must be the same"); + } + first_write_size +} + +const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize { + let max_erase_size = if first_erase_size > second_erase_size { + first_erase_size + } else { + second_erase_size + }; + if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 { + panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size"); + } + max_erase_size +} + +impl ErrorType for ConcatFlash +where + First: ErrorType, + Second: ErrorType, + E: NorFlashError, +{ + type Error = E; +} + +impl ReadNorFlash for ConcatFlash +where + First: ReadNorFlash, + Second: ReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len])?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +impl NorFlash for ConcatFlash +where + First: NorFlash, + Second: NorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len])?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to)?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?; + } + + Ok(()) + } +} + +#[cfg(feature = "nightly")] +impl AsyncReadNorFlash for ConcatFlash +where + First: AsyncReadNorFlash, + Second: AsyncReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len]).await?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +#[cfg(feature = "nightly")] +impl AsyncNorFlash for ConcatFlash +where + First: AsyncNorFlash, + Second: AsyncNorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len]).await?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to).await?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32) + .await?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + + use super::ConcatFlash; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_write_and_read_across_flashes() { + let first = MemFlash::<64, 16, 4>::default(); + let second = MemFlash::<64, 64, 4>::default(); + let mut f = ConcatFlash::new(first, second); + + f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]); + assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]); + + let mut read_buf = [0; 8]; + f.read(60, &mut read_buf).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf); + } + + #[test] + fn can_erase_across_flashes() { + let first = MemFlash::<128, 16, 4>::new(0x00); + let second = MemFlash::<128, 64, 4>::new(0x00); + let mut f = ConcatFlash::new(first, second); + + f.erase(64, 192).unwrap(); + + assert_eq!(&[0x00; 64], &f.0.mem[0..64]); + assert_eq!(&[0xff; 64], &f.0.mem[64..128]); + assert_eq!(&[0xff; 64], &f.1.mem[0..64]); + assert_eq!(&[0x00; 64], &f.1.mem[64..128]); + } +} diff --git a/embassy-embedded-hal/src/flash/mem_flash.rs b/embassy-embedded-hal/src/flash/mem_flash.rs new file mode 100644 index 000000000..4e10627df --- /dev/null +++ b/embassy-embedded-hal/src/flash/mem_flash.rs @@ -0,0 +1,127 @@ +use alloc::vec::Vec; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +extern crate alloc; + +pub(crate) struct MemFlash { + pub mem: [u8; SIZE], + pub writes: Vec<(u32, usize)>, + pub erases: Vec<(u32, u32)>, +} + +impl MemFlash { + #[allow(unused)] + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + writes: Vec::new(), + erases: Vec::new(), + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + } + + fn write(&mut self, offset: u32, bytes: &[u8]) { + self.writes.push((offset, bytes.len())); + let offset = offset as usize; + assert_eq!(0, bytes.len() % WRITE_SIZE); + assert_eq!(0, offset % WRITE_SIZE); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + } + + fn erase(&mut self, from: u32, to: u32) { + self.erases.push((from, to)); + let from = from as usize; + let to = to as usize; + assert_eq!(0, from % ERASE_SIZE); + assert_eq!(0, to % ERASE_SIZE); + self.mem[from..to].fill(0xff); + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xff) + } +} + +impl ErrorType + for MemFlash +{ + type Error = core::convert::Infallible; +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} + +#[cfg(feature = "nightly")] +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +#[cfg(feature = "nightly")] +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} diff --git a/embassy-embedded-hal/src/flash/mod.rs b/embassy-embedded-hal/src/flash/mod.rs new file mode 100644 index 000000000..c80dd6aac --- /dev/null +++ b/embassy-embedded-hal/src/flash/mod.rs @@ -0,0 +1,9 @@ +//! Utilities related to flash. + +mod concat_flash; +#[cfg(test)] +pub(crate) mod mem_flash; +mod partition; + +pub use concat_flash::ConcatFlash; +pub use partition::Partition; diff --git a/embassy-embedded-hal/src/flash/partition.rs b/embassy-embedded-hal/src/flash/partition.rs new file mode 100644 index 000000000..084425e95 --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition.rs @@ -0,0 +1,150 @@ +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage::nor_flash::{ErrorType, NorFlashError, NorFlashErrorKind}; +#[cfg(feature = "nightly")] +use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash}; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct Partition<'a, M: RawMutex, T> { + flash: &'a Mutex, + offset: u32, + size: u32, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Partition, + OutOfBounds, + Flash(T), +} + +impl<'a, M: RawMutex, T> Partition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex, offset: u32, size: u32) -> Self { + Self { flash, offset, size } + } +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Error::Partition => NorFlashErrorKind::Other, + Error::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Error::Flash(f) => f.kind(), + } + } +} + +impl ErrorType for Partition<'_, M, T> { + type Error = Error; +} + +#[cfg(feature = "nightly")] +impl ReadNorFlash for Partition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if self.offset % T::READ_SIZE as u32 != 0 || self.size % T::READ_SIZE as u32 != 0 { + return Err(Error::Partition); + } + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.read(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +#[cfg(feature = "nightly")] +impl NorFlash for Partition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if self.offset % T::WRITE_SIZE as u32 != 0 || self.size % T::WRITE_SIZE as u32 != 0 { + return Err(Error::Partition); + } + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.write(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if self.offset % T::ERASE_SIZE as u32 != 0 || self.size % T::ERASE_SIZE as u32 != 0 { + return Err(Error::Partition); + } + if to > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash + .erase(self.offset + from, self.offset + to) + .await + .map_err(Error::Flash) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[12..20].fill(0xAA); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 8, 12); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).await.unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 8, 12); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[12..20].iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + partition.erase(0, 128).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} -- cgit