Skip to content
New issue

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

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

Already on GitHub? # to your account

Enhancement: RmwNorFlash #14

Merged
merged 3 commits into from
Sep 15, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

include:
# Test MSRV
- rust: 1.36.0
- rust: 1.50.0
TARGET: x86_64-unknown-linux-gnu

# Test nightly but don't fail
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ NOR-flash & NAND-flash, both external and internal.

## [API reference]

[API reference]: https://docs.rs/embedded-storage

## How-to: add a new trait

This is the suggested approach to adding a new trait to `embedded-storage`
Expand Down Expand Up @@ -37,7 +35,7 @@ These issues / PRs will be labeled as `proposal`s in the issue tracker.

## Minimum Supported Rust Version (MSRV)

This crate is guaranteed to compile on stable Rust 1.36.0 and up. It *might*
This crate is guaranteed to compile on stable Rust 1.50.0 and up. It *might*
compile with older versions but that may change in any new patch release.

## License
Expand All @@ -55,3 +53,5 @@ at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

[API reference]: https://docs.rs/embedded-storage
192 changes: 192 additions & 0 deletions src/nor_flash.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::{iter::IterableByOverlaps, ReadStorage, Region, Storage};

/// Read only NOR flash trait.
pub trait ReadNorFlash {
/// An enumeration of storage errors
Expand Down Expand Up @@ -52,3 +54,193 @@ pub trait NorFlash: ReadNorFlash {
/// - 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)
}
}

///
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;

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

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

impl<'a, S> Storage for RmwNorFlashStorage<'a, S>
where
S: NorFlash,
{
fn try_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
.try_read(page.start, &mut self.merge_buffer[..S::ERASE_SIZE])?;

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

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

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

impl<'a, S> Storage for RmwMultiwriteNorFlashStorage<'a, S>
where
S: MultiwriteNorFlash,
{
fn try_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
.try_read(page.start, &mut self.merge_buffer[..S::ERASE_SIZE])?;

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
.try_write(addr - offset as u32, &self.merge_buffer[..aligned_end])?;
} else {
self.storage.try_erase(page.start, page.end())?;
self.merge_buffer[..S::ERASE_SIZE]
.iter_mut()
.skip(offset_into_page)
.zip(data)
.for_each(|(byte, input)| *byte = *input);
self.storage
.try_write(page.start, &self.merge_buffer[..S::ERASE_SIZE])?;
}
}
Ok(())
}
}