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

Flash Read/Write Support #257

Merged
merged 3 commits into from
Sep 5, 2020
Merged

Conversation

irwineffect
Copy link
Contributor

This PR is a followup on #141.

@burrbull
Copy link
Member

Please, use git rebase instead of merge for PR.

@irwineffect irwineffect force-pushed the flash_support branch 2 times, most recently from 0a997b0 to c388e0a Compare August 21, 2020 00:39
@irwineffect
Copy link
Contributor Author

Alright, I've rebased on master, it also looks like I need to run rustfmt over the code?

@TheZoq2
Copy link
Member

TheZoq2 commented Aug 22, 2020

Yea, that would be nice. Should just require doing cargo fmt if it is installed :)

Copy link
Member

@TheZoq2 TheZoq2 left a comment

Choose a reason for hiding this comment

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

Thanks for putting in the work to finishing this!

Over all I think the changes look good on the surface though I'm not read up on the details of the flash device so these comments are mostly overall code quality. Since this has seen testing by at least 2 people, I think it's safe to merge despite this :)

src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Show resolved Hide resolved
src/flash.rs Show resolved Hide resolved
src/flash.rs Outdated Show resolved Hide resolved
src/flash.rs Show resolved Hide resolved
@irwineffect
Copy link
Contributor Author

@TheZoq2, thanks for the review feedback! I agree about squashing the warnings and the various name changes, but I did have comments/questions on two other parts, let me know what you think!

@irwineffect
Copy link
Contributor Author

I believe I've addressed all of the review findings, with the exception of handling the lock/unlock, still working on that!

src/flash.rs Outdated Show resolved Hide resolved
@thalesfragoso
Copy link
Member

Actually, I don't think we need the Guard at all, since

pub fn read(&self, offset: u32, length: usize) -> Result<&[u8]>

should be the same as

pub fn read(&'c self, offset: u32, length: usize) -> Result<&'c [u8]>

So it has to extend the borrow as long as the output lives if my understanding is correct, see this playground example:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ce7f1c92470429693d99969d87f985d5

@irwineffect
Copy link
Contributor Author

@thalesfragoso, yeah, I think you're right, we don't need to have the separate ReaderGuard, since the type system will prevent us from mutably borrowing the FlashWriter until the data returned by read() is out of scope, and we need to mutably borrow the FlashWriter before we could do any operations that would invalidate the data.

@irwineffect
Copy link
Contributor Author

So, I've been playing around with different ways to deal with the locking/unlocking of the flash, and I've found things I really don't like about each solution, I'd appreciate some input.

I started out trying to wrap the Parts inside a FlashLockGuard struct that requires calling unlock() to get access the registers that require the flash hardware to be unlocked, and then calling lock() upon drop of the FlashLockGuard. The issue with this method is that with the current API, the lock() operation can fail, and I believe you don't want things that can fail inside a drop function, right?

The trouble with the do_with_unlock() method that @TheZoq2 had suggested is that if the operation() fails we will return without calling lock(). We could modify the example you provided to always lock() even if operation() fails, but if lock also fails, which error should we return, the lock failure or the operation failure? We could make a combo failure, but that feels pretty ugly.

I think my greater issue is the fact that the current lock()/unlock()API can fail. While unlocking could reasonably fail, I don't know under what circumstances the lock()operation would actually fail, I personally have not seen this occur, and I haven't noticed anything in the flash documentation that suggests this is possible. Can we not trust that writing to the lock bit in the control register will actually cause the bit to be set? Perhaps locking always succeeds, and we can ignore the possibility of failure? I'm inclined to continue punting the possible failure upwards to the user (or panic?) until this library has been more thoroughly tested and we are more confident this can't fail.

Also, I don't know if there really is a strong need to unlock/lock the flash around each write/erase operation. This does minimize the window that the flash could get corrupted if the code were to go off the rails, but why not just unlock the flash when the FlashWriter is created, keep it unlocked the entire time the FlashWriter exists, and then provide a lock() method that consumes the FlashWriter that the user can call when they are done with the flash? The only issue I foresee with this method is that the user could forgot to call lock() to turn back on the hardware lock on the flash (although is isn't the worst thing in the world). To protect against this possibility, we could implement a custom Drop for the FlashWriter, but this still has the issue that the locking operation could fail (but perhaps we should just ignore it or panic?). Also, if the user structures the program such that the FlashWriter is alive for the entire lifetime of the program, we are in the same boat as before. I think an explicit lock is better for that reason.

I'd love to get @luctius's thoughts to know the intent behind the original design, but barring that, what do you guys think?

src/flash.rs Outdated
// reference to this FlashWriter, and any operation that would
// invalidate the data returned would first require taking a mutable
// reference to this FlashWriter.
unsafe { core::slice::from_raw_parts::<'static, u8>(address, length) },
Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't be 'static, it should have the same lifetime of &self. Not sure if you need to strictly annotate it in order for the compiler to be happy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like the compiler can infer the lifetime, as this compiles:

 unsafe { core::slice::from_raw_parts(address, length) }

However, I decided to explicitly annotate the lifetimes around read() to make the relationship more obvious.

Copy link
Member

@thalesfragoso thalesfragoso Aug 31, 2020

Choose a reason for hiding this comment

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

Hmm, but I don't think the 'a lifetime was the best idea, since it's the same lifetime as Parts like in the other case.
To be honest I think we should just let the compiler do the job and use just

pub fn read(&self, offset: u32, length: usize) -> Result<&[u8]> {
    //...
    unsafe { core::slice::from_raw_parts(address, length) }
}

The lifetime elisions rules seem to be very clear in this case anyway, so it's best to leave the compiler to choose the appropriate lifetime I think, maybe add a comment. Sorry for all the nitpicking .-.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha don't be sorry, glad to get feedback! I didn't realize annotating the lifetime would change the behavior, I thought I was just making something that was implicit explicit.

Copy link
Member

Choose a reason for hiding this comment

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

It was just because it was the same lifetime you used at

impl<'a> FlashWriter<'a>

@thalesfragoso
Copy link
Member

So, I've been playing around with different ways to deal with the locking/unlocking of the flash, and I've found things I really don't like about each solution, I'd appreciate some input.

I think we should hold on this change, better to leave for another PR, since the path isn't clear yet and the way it's now isn't that bad.

@irwineffect
Copy link
Contributor Author

So, I've been playing around with different ways to deal with the locking/unlocking of the flash, and I've found things I really don't like about each solution, I'd appreciate some input.

I think we should hold on this change, better to leave for another PR, since the path isn't clear yet and the way it's now isn't that bad.

@TheZoq2, if you agree, we can leave reworking the unlock/locking logic for a future PR?

@TheZoq2
Copy link
Member

TheZoq2 commented Aug 31, 2020

Sounds like a plan to me!

@irwineffect
Copy link
Contributor Author

If everything looks good now, should I squash-rebase before we merge?

@burrbull
Copy link
Member

burrbull commented Sep 1, 2020

Yes, please.

Incorporated Comments

 - Added FlashReaderGuard
 - Removed modulo operations
 - Added option to avoid verify operations
 - Cleanup

Added more detailed notes for usage of unsafe in flash functions.

ran "cargo fmt" to cleanup flash code

Suppressed unused warnings for flash code, enum naming tweaks

Added prefix '_' to registers and constants that aren't currently used,
and renamed enum values based on review feedback

added more descriptive comments where requested

moved initialization of hword closer to where it's used

Removed FlashReaderGuard, not necessary

We can rely on the lifetime system to protect us here, the guard
structure is unnecessary (see the unsafe comments for details).

removed lifetimes from FlashWriter::read()

Better to let the compiler determine the lifetime
Copy link
Member

@TheZoq2 TheZoq2 left a comment

Choose a reason for hiding this comment

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

Looks great to me, just 2 things remain: a changelog entry and a tiny issue with one of the doc comments :)

src/flash.rs Outdated
Ok(())
}

/// Retrieve an slice of data from FLASH_START +offset
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: should be a slice. Also, would be nice to have the math in a code block, and a space after the +

src/flash.rs Outdated
Comment on lines 172 to 173
let end = start + self.sector_sz.kbytes() as u32 - 1;
for idx in start..end {
Copy link
Member

Choose a reason for hiding this comment

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

Should this range be inclusive ? i.e. start..=end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are absolutely right, good catch. Do you think it should be start..=end, or remove the -1 on the line above when end is calculated?

Copy link
Member

Choose a reason for hiding this comment

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

remove -1 is better

Copy link
Member

Choose a reason for hiding this comment

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

Wait, I did not see previous line.

src/flash.rs Outdated
Comment on lines 166 to 173
// By subtracting 1 from the sector size and masking with
// start_offset, we make 'start' point to the beginning of the
// page, and similarly 'end' at the end of the page. We do this
// because the entire page should have been erased, regardless
// of where in the page the given 'start_offset' was.
let start = start_offset & !(self.sector_sz.kbytes() as u32 - 1);
let end = start + self.sector_sz.kbytes() as u32 - 1;
for idx in start..end {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// By subtracting 1 from the sector size and masking with
// start_offset, we make 'start' point to the beginning of the
// page, and similarly 'end' at the end of the page. We do this
// because the entire page should have been erased, regardless
// of where in the page the given 'start_offset' was.
let start = start_offset & !(self.sector_sz.kbytes() as u32 - 1);
let end = start + self.sector_sz.kbytes() as u32 - 1;
for idx in start..end {
// By subtracting 1 from the sector size and masking with
// start_offset, we make 'start' point to the beginning of the
// page. We do this
// because the entire page should have been erased, regardless
// of where in the page the given 'start_offset' was.
let size = self.sector_sz.kbytes() as u32;
let start = start_offset & !(size - 1);
for idx in start..start+size {

Copy link
Member

Choose a reason for hiding this comment

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

Is this what you want to do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like that, I think it is more readable than the previous version.

@TheZoq2
Copy link
Member

TheZoq2 commented Sep 5, 2020

Looks like all standing issues are resolved, thanks everyone who helped out!

@TheZoq2 TheZoq2 merged commit 563c5d3 into stm32-rs:master Sep 5, 2020
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.

5 participants