Skip to content

Add async version of the Storage trait and RmwNorFlashStorage implementation #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions embedded-storage-async/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

- Add RMW helpers for Nor flashes, implementing `Storage` trait.

## [0.4.1] - 2023-11-28

- Let `&mut` `NorFlash` implement `NorFlash`.
Expand Down
27 changes: 27 additions & 0 deletions embedded-storage-async/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,30 @@
#![allow(async_fn_in_trait)]

pub mod nor_flash;

/// Transparent read only storage trait
pub trait ReadStorage {
/// An enumeration of storage errors
type Error;

/// Read a slice of data from the storage peripheral, starting the read
/// operation at the given address offset, and reading `bytes.len()` bytes.
///
/// This should throw an error in case `bytes.len()` will be larger than
/// `self.capacity() - offset`.
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error>;

/// The capacity of the storage peripheral in bytes.
fn capacity(&self) -> usize;
}

/// Transparent read/write storage trait
pub trait Storage: ReadStorage {
/// Write a slice of data to the storage peripheral, starting the write
/// operation at the given address offset (between 0 and `self.capacity()`).
///
/// **NOTE:**
/// This function will automatically erase any pages necessary to write the given data,
/// and might as such do RMW operations at an undesirable performance impact.
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error>;
}
200 changes: 200 additions & 0 deletions embedded-storage-async/src/nor_flash.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use embedded_storage::iter::IterableByOverlaps;
pub use embedded_storage::nor_flash::{ErrorType, NorFlashError, NorFlashErrorKind};
use embedded_storage::Region;

use crate::{ReadStorage, Storage};

/// Read only NOR flash trait.
pub trait ReadNorFlash: ErrorType {
Expand Down Expand Up @@ -85,3 +89,199 @@ impl<T: NorFlash> NorFlash for &mut T {
/// - Bits that were 0 on flash are guaranteed to stay as 0
/// - Rest of the bits in the page are guaranteed to be unchanged
pub trait MultiwriteNorFlash: NorFlash {}

struct Page {
pub start: u32,
pub size: usize,
}

impl Page {
fn new(index: u32, size: usize) -> Self {
Self {
start: index * size as u32,
size,
}
}

/// The end address of the page
const fn end(&self) -> u32 {
self.start + self.size as u32
}
}

impl Region for Page {
/// Checks if an address offset is contained within the page
fn contains(&self, address: u32) -> bool {
(self.start <= address) && (self.end() > address)
}
}

///
#[derive(Debug)]
pub struct RmwNorFlashStorage<'a, S> {
storage: S,
merge_buffer: &'a mut [u8],
}

impl<'a, S> RmwNorFlashStorage<'a, S>
where
S: NorFlash,
{
/// Instantiate a new generic `Storage` from a `NorFlash` peripheral
///
/// **NOTE** This will panic if the provided merge buffer,
/// is smaller than the erase size of the flash peripheral
pub fn new(nor_flash: S, merge_buffer: &'a mut [u8]) -> Self {
if merge_buffer.len() < S::ERASE_SIZE {
panic!("Merge buffer is too small");
}

Self {
storage: nor_flash,
merge_buffer,
}
}
}

impl<'a, S> ReadStorage for RmwNorFlashStorage<'a, S>
where
S: ReadNorFlash,
{
type Error = S::Error;

async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
// Nothing special to be done for reads
self.storage.read(offset, bytes).await
}

fn capacity(&self) -> usize {
self.storage.capacity()
}
}

impl<'a, S> Storage for RmwNorFlashStorage<'a, S>
where
S: NorFlash,
{
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
// Perform read/modify/write operations on the byte slice.
let last_page = self.storage.capacity() / S::ERASE_SIZE;

// `data` is the part of `bytes` contained within `page`,
// and `addr` in the address offset of `page` + any offset into the page as requested by `address`
for (data, page, addr) in (0..last_page as u32)
.map(move |i| Page::new(i, S::ERASE_SIZE))
.overlaps(bytes, offset)
{
let offset_into_page = addr.saturating_sub(page.start) as usize;

self.storage
.read(page.start, &mut self.merge_buffer[..S::ERASE_SIZE])
.await?;

// If we cannot write multiple times to the same page, we will have to erase it
self.storage.erase(page.start, page.end()).await?;
self.merge_buffer[..S::ERASE_SIZE]
.iter_mut()
.skip(offset_into_page)
.zip(data)
.for_each(|(byte, input)| *byte = *input);
self.storage
.write(page.start, &self.merge_buffer[..S::ERASE_SIZE])
.await?;
}
Ok(())
}
}

///
pub struct RmwMultiwriteNorFlashStorage<'a, S> {
storage: S,
merge_buffer: &'a mut [u8],
}

impl<'a, S> RmwMultiwriteNorFlashStorage<'a, S>
where
S: MultiwriteNorFlash,
{
/// Instantiate a new generic `Storage` from a `NorFlash` peripheral
///
/// **NOTE** This will panic if the provided merge buffer,
/// is smaller than the erase size of the flash peripheral
pub fn new(nor_flash: S, merge_buffer: &'a mut [u8]) -> Self {
if merge_buffer.len() < S::ERASE_SIZE {
panic!("Merge buffer is too small");
}

Self {
storage: nor_flash,
merge_buffer,
}
}
}

impl<'a, S> ReadStorage for RmwMultiwriteNorFlashStorage<'a, S>
where
S: ReadNorFlash,
{
type Error = S::Error;

async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
// Nothing special to be done for reads
self.storage.read(offset, bytes).await
}

fn capacity(&self) -> usize {
self.storage.capacity()
}
}

impl<'a, S> Storage for RmwMultiwriteNorFlashStorage<'a, S>
where
S: MultiwriteNorFlash,
{
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
// Perform read/modify/write operations on the byte slice.
let last_page = self.storage.capacity() / S::ERASE_SIZE;

// `data` is the part of `bytes` contained within `page`,
// and `addr` in the address offset of `page` + any offset into the page as requested by `address`
for (data, page, addr) in (0..last_page as u32)
.map(move |i| Page::new(i, S::ERASE_SIZE))
.overlaps(bytes, offset)
{
let offset_into_page = addr.saturating_sub(page.start) as usize;

self.storage
.read(page.start, &mut self.merge_buffer[..S::ERASE_SIZE])
.await?;

let rhs = &self.merge_buffer[offset_into_page..S::ERASE_SIZE];
let is_subset = data.iter().zip(rhs.iter()).all(|(a, b)| *a & *b == *a);

// Check if we can write the data block directly, under the limitations imposed by NorFlash:
// - We can only change 1's to 0's
if is_subset {
// Use `merge_buffer` as allocation for padding `data` to `WRITE_SIZE`
let offset = addr as usize % S::WRITE_SIZE;
let aligned_end = data.len() % S::WRITE_SIZE + offset + data.len();
self.merge_buffer[..aligned_end].fill(0xff);
self.merge_buffer[offset..offset + data.len()].copy_from_slice(data);
self.storage
.write(addr - offset as u32, &self.merge_buffer[..aligned_end])
.await?;
} else {
self.storage.erase(page.start, page.end()).await?;
self.merge_buffer[..S::ERASE_SIZE]
.iter_mut()
.skip(offset_into_page)
.zip(data)
.for_each(|(byte, input)| *byte = *input);
self.storage
.write(page.start, &self.merge_buffer[..S::ERASE_SIZE])
.await?;
}
}
Ok(())
}
}
Loading