Skip to content
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

Rework NorFlash & Storage traits #12

Merged
merged 13 commits into from
May 18, 2021
Merged

Rework NorFlash & Storage traits #12

merged 13 commits into from
May 18, 2021

Conversation

MathiasKoch
Copy link
Collaborator

@MathiasKoch MathiasKoch commented Mar 3, 2021

  • Rework NorFlash & Storage traits
  • Add RmwNorFlashStorage wrapper between them

The RmwNorFlashStorage implementation might be possible to improve? Please consider that as my crude first attempt. It is tested and works though. I am not 100% into the write_size/erase_size usage, so please correct me on those.

I have added an implementation of RmwNorFlashStorage that should be able to utilize MultiwriteNorFlash as well, but it is still untested. Also it requires rust-lang/rust#31844 to live alongside the implementation without multiwrite.

I currently have an implementation for STM32L4xx_HAL if you are interested.

This is my attempt to encapsulate the discussion going on, so please keep it coming!

@eldruin @Sympatron

@MathiasKoch MathiasKoch force-pushed the storage-rework branch 2 times, most recently from 1078baa to 7667a56 Compare March 3, 2021 14:00
@MathiasKoch
Copy link
Collaborator Author

MathiasKoch commented Mar 5, 2021

I think the latest change her addresses most of the concerns people have highlighted, with some limitations?

capacity() has been left a runtime function, as i think lots of chip families have common read, write and erase sizes, but just change the capacity, making it easier to read capacity at runtime and return that. This might mean that it should actually take &mut self though?

#13 Should also be addressed by this

Stuff left to figure out:

Limitations:

  • Multiple regions are only supported through multiple implementations: impl NorFlash for Region1 + impl NorFlash for Region2 or similar.
  • Non-uniform regions are unsupported, but can be implement using biggest region size, and combining smaller regions.

@Sympatron @Dirbaio

@Dirbaio
Copy link
Contributor

Dirbaio commented Mar 5, 2021

should make use of S::ERASE_SIZE

To get rid of that error, add where [u8; S::ERASE_SIZE]: Sized

Type of addresses? u32/usize/Address()

  • For NorFlash u32/usize would be OK, I don't think there are 4GB+ NOR flashes.
  • For Storage, people will want to do ipmls for SD cards, which can easily be bigger than 4GB.
  • For consistency it's nice if all traits have the same address type.
  • It's not nice to force an u64 address on everyone, it's useless overhead on 32bit arches if you don't need it.

Maybe given this the best way to go is make it generic? I'm not a fan of adding even more generic params but maybe it's the best option :S

All addresses are normalized from 0..self.capacity() ? Document this without a doubt!

I'd say yes. Makes using traits harder otherwise, and I don't think it adds anything of value :|

Error handling (generic error? impl Into<..>? Basically same discussion as embedded-hal)

No idea 🤷 I somewhat prefer a non_exhaustive Enum instead of a type Error, for the reasons here. TLDR is drivers using the traits can't really handle the errors otherwise, as they know nothing about the type. The downside is it doesn't allow for custom errors. I personally don't find that a problem but other people have different opinions.

@MathiasKoch
Copy link
Collaborator Author

MathiasKoch commented Mar 8, 2021

@Dirbaio Hmm, i can't seem to make the where [u8; S::ERASE_SIZE]: Sized work?

error[E0599]: no associated item named `ERASE_SIZE` found for type parameter `S` in the current scope
   --> src/nor_flash.rs:113:9
    |
113 | [u8; S::ERASE_SIZE]: Sized,
    |         ^^^^^^^^^^ associated item not found in `S`
    |
    = help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `ERASE_SIZE`, perhaps you need to restrict type parameter `S` with it:
    |
111 | impl<S: NorFlash> Storage for RmwNorFlashStorage<S>
    |      ^^^^^^^^^^^

error: aborting due to previous error

On the error part how about something like

pub enum Error<E> {
    CommonErrors,
    ...,
    Custom(E)
}
pub trait NorFlash: ReadNorFlash {
    type UserError: Debug;

    fn try_write(&mut self, address: u32, bytes: &[u8]) -> Result<(), Error<Self::UserError>> {
         ...
    }
}

That way it should be easy to just make UserError = Infallible/() if everything is covered by the Error enum. We can still amke the Error enum non_exhaustive to allow adding error handling as we go.

@Dirbaio
Copy link
Contributor

Dirbaio commented Mar 8, 2021

@Dirbaio Hmm, i can't seem to make the where [u8; S::ERASE_SIZE]: Sized work?

Do you have the code somewhere? I can try taking a look

…ase sizes. Split traits into Read and ReadWrite versions
@MathiasKoch
Copy link
Collaborator Author

@Dirbaio
Copy link
Contributor

Dirbaio commented Mar 10, 2021

Wow, I think it's not possible with the 1.51 min_const_generics. It does work with these features:

#![feature(const_generics)]
#![feature(const_evaluatable_checked)]

An alternative is something like pub struct RmwNorFlashStorage<S, const ERASE_SIZE: usize> (S), then the array works with min_const_generics BUT there's no way to enforce that S::ERASE_SIZE = Self::ERASE_SIZE :(

@MathiasKoch
Copy link
Collaborator Author

Hmm, how would i propagate this PR the remaining distance? It seems like all it is missing is

  • Error handling discussion (Could be postponed to another PR)
  • Handling that const merge_buffer size in the RmwNorFlashStorage
  • Enabling the RmwMultiwriteNorFlashStorage after finding a better name for it?

@Dirbaio
Copy link
Contributor

Dirbaio commented Mar 16, 2021

No strong opinion on errors, we could leave it as it is now which is the same as embedded-hal, and revisit it later once it's revisited in embedded-hal?

Not sure what to do about the merge buffer. Maybe have the user pass a &mut [u8] and panic at runtime if it's too small?

Is the RmwMultiwriteNorFlashStorage really needed? All MultiwriteNorFlash is also NorFlash, so you can already use the current adapter for all flashes. It might allow for a more efficient implementation (if writing to a range that's all 0xFF no need to erase first), but we can always add that later.

What's the usecase for Region and OverlapIterator now that we've dropped support for multiregion flash?

@MathiasKoch
Copy link
Collaborator Author

No strong opinion on errors, we could leave it as it is now which is the same as embedded-hal, and revisit it later once it's revisited in embedded-hal?

Sounds good, that was exactly my point with postponing it

Maybe have the user pass a &mut [u8] and panic at runtime if it's too small

I think that sounds like a decent tradeoff, i'll see if i can make it work in an elegant way tomorrow.

Is the RmwMultiwriteNorFlashStorage really needed

Not necessarily. Personally i wont be needing it, but it sounded like @Sympatron could make use of it, as it does improve the worst-case performance quite a bit.

What's the usecase for Region and OverlapIterator now that we've dropped support for multiregion flash?

Good point! I think they are leftovers from the past.. I'll remove them if they are not needed.

@Sympatron
Copy link
Contributor

I agree with @Dirbaio about the errors.

Maybe have the user pass a &mut [u8] and panic at runtime if it's too small

This might be a good idea, but I don't quite see how to get there... Wouldn't you either have to modify the trait or let the storage own the buffer (i.e. provide one with new())?

I think RmwMultiwriteNorFlashStorage is fine for now. It's not ideal, but I can't think of a better one.

@Dirbaio
Copy link
Contributor

Dirbaio commented Mar 16, 2021

let the storage own the buffer (i.e. provide one with new())?

Yes, I'd do that. It also opens the possibility of optimizations like coalescing multiple writes?

When writing load the page from flash into the buffer and apply the modifications but don't write it to flash yet. On next writes, if they're to the same page apply them to the buffer. If they're to other pages, write out the previous page first.

The downside is it'd need a flush() method to ensure the last write is actually written out...

@MathiasKoch
Copy link
Collaborator Author

Hmm, at first-hand we seem to run into a double mutable borrow at self.try_read(page.start, self.merge_buffer)?;

@Dirbaio
Copy link
Contributor

Dirbaio commented Mar 18, 2021

Shouldn't it do try_read on the inner NorFlash, not on self?

@MathiasKoch
Copy link
Collaborator Author

That would make sense yes :p Missing the obvious here!

@MathiasKoch
Copy link
Collaborator Author

@Dirbaio @Sympatron
Guess that should do it.
We might be able to get rid of the overlapIterator, but both Rmw implementations makes use of it, and is greatly simplified by it.
Maybe we could postpone that cleanup to a future PR if anyone wants to do it?

It could probably do with a bit more doc-comments scattered around, so i will se if i can get around to that.

One thing. Do you guys prefer address, or offset as argument names? I tend to lean towards offset given that we have agreed they are in the range (0..capacity()), and thus no langer is an actual address?

@MathiasKoch MathiasKoch deleted the storage-rework branch March 18, 2021 15:13
…e atleast EraseSize long. Introduce RmwMultiwriteNorFlashStorage
@MathiasKoch
Copy link
Collaborator Author

@Dirbaio @eldruin @Sympatron
I think this is ready for a final round of review, followed by a 0.1.0 release?

Copy link
Member

@eldruin eldruin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than my comments, looks good to me! However, I have no experience with NOR.

Cargo.toml Outdated Show resolved Hide resolved
src/iter.rs Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
MathiasKoch and others added 2 commits April 19, 2021 13:20
Co-authored-by: Diego Barrios Romero <[email protected]>
Copy link
Member

@eldruin eldruin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! (NOR not reviewed)

Copy link
Contributor

@Dirbaio Dirbaio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed NOR flash stuff. Traits look great modulo a few minor doc nits.

Maybe it's best to add the RMW Storage impls in a later PR, so we don't block the traits release?

I also wonder if the RMW impls might be more readable using index loops and copy_from_slice instead of iterators

src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated
// 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 {
self.storage.try_write(addr, data)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may fail if addr or data len is not a multiple of WRITE_SIZE. For example with a WRITE_SIZE of 4 (like on nrf52s) it'd fail if you try to write a single byte.

Fortunately this is possible to do by "padding" to WRITE_SIZE with 0xFFs. For example, if you want to write 0x42 to offset 1, you can do so by writing 0xFF 0x42 0xFF 0xFF to offset 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/nor_flash.rs Outdated Show resolved Hide resolved
src/nor_flash.rs Outdated Show resolved Hide resolved
@MathiasKoch
Copy link
Collaborator Author

@Dirbaio Thanks for the great comments!

I will address them as soon as i get a moment of time ;)

src/nor_flash.rs Outdated
let offset = addr as usize % S::WRITE_SIZE;
self.merge_buffer[..S::WRITE_SIZE]
.iter_mut()
.for_each(|c| *c = 0u8);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be 0xFF? 0x00 will set all bits to 0, 0xFF leaves them untouched.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you could use .fill(0xff) instead of using iterators :)

Same for the copies, you could use .copy_from_slice() which compiles to a memcpy. I don't think the compiler is able to optimize the iterator version to a memcpy.

src/nor_flash.rs Outdated
self.merge_buffer[..S::WRITE_SIZE]
.iter_mut()
.skip(offset)
.zip(data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is correct, data may be bigger than WRITE_SIZE since it's chunked by ERASE_SIZE...

@MathiasKoch
Copy link
Collaborator Author

I have extracted the Rmw implementations to their own PR #14.

@Dirbaio @Sympatron @eldruin

Can we merge and release the traits here?

Copy link
Member

@eldruin eldruin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside for the nitpicks, looks good to me!

src/lib.rs Outdated
///
/// (start_addr, end_addr)
fn range(&self) -> (Address, Address);
/// This should throw an error in case `bytes.len()` will be larger than
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you fix the tab usage clippy warning here?

Cargo.toml Outdated
@@ -15,6 +15,4 @@ keywords = ["storage"]
categories = ["embedded", "hardware-support", "no-std"]

[dependencies]
nb = "1"
no-std-net = "0.4"
heapless = "^0.5"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we bump heapless? The latest version (0.7) requires 1.51.0, though, due to const-generics.

@MathiasKoch
Copy link
Collaborator Author

MathiasKoch commented May 18, 2021

@eldruin Good point.

I removed the heapless dependency entirely for this one, as it was only used in the Rmw implementations, and updated it to 0.7 in #14

Update:
Apparently not used by Rmw implementations either, i removed the heapless dependency all together.

@Sympatron
Copy link
Contributor

@MathiasKoch I think it is good to go.

@MathiasKoch MathiasKoch merged commit 7d70e13 into master May 18, 2021
@MathiasKoch MathiasKoch deleted the storage-rework branch May 18, 2021 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Read-only storage support Example of NorFlash::Region Specialized traits per storage type
4 participants