-
Notifications
You must be signed in to change notification settings - Fork 24
Example of NorFlash::Region #9
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
Comments
Would it be possible to model the interface so that there is no hard 4 erase size regions limit? I'm just thinking if we want to increase it to 5 in the future, that would be a breaking change. Furthermore, 4 seems a bit of a magic number. |
I had a long chat in the Our suggestion is still a bit WIP, but basically it revolves around two key things: Only support uniform flashes for nowThis makes everything much easier, and can still handle non-uniform edge cases if users REALLY want to in two ways.
These two options can be combined as implementors see fit, but reality is that this will be a very small amount of implementations, as most are uniform. Marker trait for
|
@MathiasKoch From what I know from the |
@MathiasKoch I think the solution you came up with is good. You are probably right that non-uniform regions are a bit niche and don't necessarily need to be handled by I would add the following:
|
Looks very nice!
|
It can be extremely bad performing, but i think in most cases where a user would just hand it a slice of data to persist, you wouldn't be able to make the performance any better without RMW?
Personally i am not a fan of splitting this? I don't see what we gain from it, other than more complexity on the users? If you really want a ReadOnlyStorage, implement Storage and return an unsupported/illegal error on write/erase? Thoughts? @Dirbaio |
As I said: A simple RMW implementation has it's use cases, like the one you described. And there are certainly cases were it's hard or impossible to do better. I was just saying that it should be conveyed that it's not suitable for arbitrary usage, especially not random writes. |
I think it's a fair point 👍 I have no strong naming opinions, so for me it's alright. As long as the code works 😄 Another thing: |
I got more edge cases! 🎉 The nrf52 QSPI peripehral can only read in multiples of 4 bytes, even if the underlying QSPI chip can read at any byte offset. 🤦♂️
This could be addressed by adding a Even more annoyingly, the RAM addresses must also be 4-byte aligned, for both reading and writing.
I'm very not sure what to do about this one. Options:
|
Stop doing that! :p Regarding the alignment, what we are currently doing is: https://github.com/stm32-rs/stm32l4xx-hal/blob/683c78138871ee39910bfcc944b2b67ba28ff313/src/flash.rs#L240-L318 And then implement these traits ontop of the |
STM32F205 supports byte write, so for sectors 0..=3 it would be
In the L4s with ECC, it would be
|
Hm... IMO the NorFlash trait should capture the reality of the hardware and expose it with no performance penalty. There could be a wrapper that turns a NorFlash with write_size=8 into a NorFlash with write_size=1. Users could optionally wrap the HAL impl with it if they want to use it with something that requires write_size=1... |
I agree this would be by far the best outcome! That said, i think we also need to keep this at a level where it will actually get adoption. Not that the two can't coincide. |
Just out of curiosity: Why would it be much harder to use? If |
Yeah, if a trait user requires Following the same logic, maybe read_size, write_size and erase_size should be const-generic params? |
I thought about that, too. But what if this information needs to be acquired at runtime? I don't know if that's possible, but I could imagine a rather generic NOR flash driver that works with most NOR flash chips on the market by using standard commands. Then some of this information could be queried at runtime from the chip itself. I don't know if that is a use case we want to support though. This could be solved by requiring to provide these generic parameters to the device driver, because the end user has to have this information. Then it would only be a problem if one firmware binary had to be used on boards with different flash chip. We could choose to not support this. |
I think that would make them much easier to use! That would also mean that one can use the |
@Sympatron That is a valid point! I have driver crates for such chips, where it would be nice to make it a runtime thing, but it wouldn't be fatal to require users to provide info around which variant they are actually using |
|
Soo, when was it that const-generic params arrives to stable? 1.51? |
At least Is it better to use const generics or associated constants in this case? I would tend towards the latter, but I am not sure. |
I think const generics would still allow for one implementation covering a family of chips, while associated constants would probably be easier to make complete? Soo, pros and cons? |
First, this is only a problem with external flash. Characteristics of internal flash are known, you're compiling for a particular MCU anyway :D For external flashes, what about this: The user instantiates the driver with the maximum read/write/erase size they're willing to accept. The driver can check at runtime the actual read/write/erase sizes supported by the chip, and panic if they're greater. If they're smaller it's all fine! For example, imagine have a key-value DB that requires |
Also, how common are SPI flashes with read/write size greater than 1? All I've seen have read_size=1, write_size=1 |
Hmm. I tried to make a /// Read only NOR flash trait.
pub trait ReadNorFlash<const READ_SIZE: usize> {
/// An enumeration of storage errors
type Error;
/// Read a slice of data from the storage peripheral, starting the read
/// operation at the given address, and reading `bytes.len()` bytes.
///
/// This should throw an error in case `bytes.len()` will be larger than
/// the peripheral end address.
fn try_read(&mut self, address: u32, bytes: &mut [u8]) -> Result<(), Self::Error>;
/// The capacity of the peripheral in bytes.
fn capacity(&self) -> usize;
}
/// NOR flash trait.
pub trait NorFlash<const READ_SIZE: usize, const WRITE_SIZE: usize, const ERASE_SIZE: usize>:
ReadNorFlash<READ_SIZE>
{
/// Erase the given storage range, clearing all data within `[from..to]`.
/// The given range will contain all 1s afterwards.
///
/// This should return an error if the range is not aligned to a proper
/// erase resolution
/// Erases page at addr, sets it all to 0xFF
/// If power is lost during erase, contents of the page are undefined.
/// `from` and `to` must both be multiples of `erase_size()` and `from` <= `to`.
fn try_erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error>;
/// Writes data to addr, bitwise ANDing if there's already data written at that location,
/// If power is lost during write, the contents of the written words are undefined.
/// The rest of the page is guaranteed to be unchanged.
/// It is not allowed to write to the same word twice.
/// `address` and `bytes.len()` must both be multiples of `write_size()` and properly aligned.
fn try_write(&mut self, address: u32, bytes: &[u8]) -> Result<(), Self::Error>;
} This part seems okay i think? impl<'a> ReadNorFlash<1> for FlashProgramming<'a> {
type Error = Error;
fn try_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
if offset > self.capacity() as u32 {
return Err(Error::OutOfBounds);
}
...
}
fn capacity(&self) -> usize {
..
}
}
impl<'a> NorFlash<1, 8, 2048> for FlashProgramming<'a> {
fn try_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
if offset as usize + bytes.len() > self.capacity() {
return Err(Error::OutOfBounds);
}
...
}
fn try_erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
// Check that from & to is properly aligned to a proper erase resolution
if from % 2048 != 0 || to % 2048 != 0 {
return Err(Error::Programming(ProgrammingError::Alignment));
}
...
}
} Which seems okay? My issue happens when looking at new-wrapper types like ///
pub struct RmwNorFlashStorage<
S,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
const ERASE_SIZE: usize,
>(S);
impl<S, const READ_SIZE: usize, const WRITE_SIZE: usize, const ERASE_SIZE: usize>
RmwNorFlashStorage<S, READ_SIZE, WRITE_SIZE, ERASE_SIZE>
{
/// Instantiate a new generic `Storage` from a `NorFlash` peripheral
pub fn new(nor_flash: S) -> Self {
Self(nor_flash)
}
}
impl<S, const READ_SIZE: usize, const WRITE_SIZE: usize, const ERASE_SIZE: usize> ReadStorage
for RmwNorFlashStorage<S, READ_SIZE, WRITE_SIZE, ERASE_SIZE>
where
S: ReadNorFlash<READ_SIZE>,
{
type Error = S::Error;
fn try_read(&mut self, address: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
// Nothing special to be done for reads
self.0.try_read(address, bytes)
}
fn capacity(&self) -> usize {
self.0.capacity()
}
}
impl<S, const READ_SIZE: usize, const WRITE_SIZE: usize, const ERASE_SIZE: usize> Storage
for RmwNorFlashStorage<S, READ_SIZE, WRITE_SIZE, ERASE_SIZE>
where
S: NorFlash<READ_SIZE, WRITE_SIZE, ERASE_SIZE>,
{
fn try_write(&mut self, address: u32, bytes: &[u8]) -> Result<(), Self::Error> {
// Perform read/modify/write operations on the byte slice.
let last_page = (self.0.capacity() / ERASE_SIZE) - 1;
// `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, ERASE_SIZE as u32))
.overlaps(bytes, address)
{
let merge_buffer = &mut [0u8; ERASE_SIZE];
let offset_into_page = addr.saturating_sub(page.start) as usize;
self.try_read(page.start, merge_buffer)?;
// If we cannot write multiple times to the same page, we will have to erase it
self.0.try_erase(page.start, page.end())?;
merge_buffer
.iter_mut()
.skip(offset_into_page)
.zip(data)
.for_each(|(byte, input)| *byte = *input);
self.0.try_write(page.start, merge_buffer)?;
}
Ok(())
}
} This doesn't feel quite right? Ideally i would like to see the newtype wrapper not needing any const generics, with the ability of using |
The last complaint can ofc be fixed by having But that still does not allow So i guess the associated const approach is better than the EDIT
|
I updated #12 to reflect the associated const version. There is still above error with the |
To get rid of that error, add |
@Sympatron
I am a bit unsure on what your initial idea behind
type Region: NorFlashRegion
was, and how you intend it to be used?Could you come with an implementation example?
It seems like this
fn regions(&self) -> Vec<Self::Region, U4>;
becomes rather unusable with the restriction on the type above?What would these 4 regions potentially be? The only way i can see it usefull would be an enum? Just wanted to confirm with you, that it was indeed you original idea.
The text was updated successfully, but these errors were encountered: