-
Notifications
You must be signed in to change notification settings - Fork 3k
Implement BufferedBlockDevice #6757
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
Conversation
There was a problem hiding this 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 👍
|
||
|
||
/** Block device for allowing smaller read and program sizes for the underlying BD, | ||
* using a buffer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this should be extended but first question I have is probably too implementation detailed? the buffer is static, heap? I can see its on the heap.
Why only smaller ? how much smaller ? Is there a range or any limit? What if I have BD that might need bigger reads? What to use?
These might be for documentation or if we can make this more explicit here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed this line a bit. It is actually minimal and not smaller - read and program sizes are fixed to 1.
Reasoning behind such a block device adaptor is the following: Some BDs have read/program sizes that are too big. For instance, the SD card BD has the value of 512 bytes for both. This can make life very hard for users of these BDs, as they will need to add logics for caching data even when they need to read/write small amounts of it. This adaptor solves the problem. One use case (that was the trigger here) was running StoageLite over SD card.
6732ca9
to
ee7b040
Compare
int BufferedBlockDevice::flush() | ||
{ | ||
if (!_flushed) { | ||
int ret = _bd->program((const void *) _cache, _curr_aligned_addr, _bd_program_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Query: During init we set _curr_aligned_addr = _bd->size();
, if flush is called after init, it will program just the last block of the block device or go beyond the size boundary?
Nevermind... Didn't see if (!_flushed)
There was a problem hiding this 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
int BufferedBlockDevice::flush() | ||
{ | ||
if (!_flushed) { | ||
int ret = _bd->program((const void *) _cache, _curr_aligned_addr, _bd_program_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the cast is unnecessary - hope it is, or something unpleasant is happening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Probably was painted red by Eclipse at some point. Fixed.
} | ||
bd_addr_t offs_in_buf = addr - _curr_aligned_addr; | ||
bd_size_t chunk = std::min(_bd_program_size - offs_in_buf, size); | ||
memcpy(buf, (const void *)(_cache + offs_in_buf), chunk); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pointless cast
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
|
||
bd_addr_t aligned_addr = align_down(addr, _bd_program_size); | ||
|
||
uint8_t *buf = (uint8_t *) b; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be static_cast<>
to check const
and assigning to const uint8_t *
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Fixed.
// this is covered in the previous condition). | ||
prog_buf = buf; | ||
if (size == _bd_program_size) { | ||
memcpy(_cache, (const void *)buf, _bd_program_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary cast
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
_bd_program_size = _bd->get_program_size(); | ||
|
||
if (!_cache) { | ||
_cache = new uint8_t[_bd_program_size]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this alloc is being deferred to the error-returning init
, does it make sense to use (nothrow)
and return an error on memory failure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reason for this being allocated in init is the fact that it relies on the underlying BD's get_program_size
, whose value may only be valid after init. Code originally included an assertion for allocation failure, but then was told by @geky that it was redundant, as mbed-os automatically asserts on allocation failures. Is this not correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct - it just seems slightly odd to throw given that all other errors in the function return an error code.
On the other hand, it's not that you really want dynamic allocation with check - if it weren't for that get_program_size
you'd be doing a throwing new
in the constructor, right? So maybe it's just that, deferred.
I'm generally in favour of throwing for unexpected errors, rather than returning an error no-one checks. So fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I try to distinguish "fatal" errors (like not being able to allocate at init) from "non fatal" ones (errors in read/write for wrong addresses etc.). At least my code (like StorageLite) checks for error returned by the latter. Maybe I should return the assert for allocation failure just for visibility sake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine - I'll leave it up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the question is "would an application want to recover from an OOM in the buffered block device?"
|
||
int BufferedBlockDevice::deinit() | ||
{ | ||
return _bd->deinit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this free the cache for symmetry? Seems reasonable if they've explicitly called deinit
and allocation's in init
, but maybe there are practical reasons not to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in this specific block device, there's no difference between freeing the cache (or any other resource for that matter) here or in the destructor. However, tried to conform to other block devices, which let the data live through init/deinit calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! The heap block device is special in this case. (It's "pretending" the heap is non-volatile storage.) I would suggest freeing in deinit also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. Moved to deinit.
ee7b040
to
df8e360
Compare
Pushed changes following review. Feel free to re-review. |
|
||
int BufferedBlockDevice::deinit() | ||
{ | ||
if (_cache) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need the if
- always valid to delete
or free
null pointers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Removed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice hyphenation 👍
Should we have a user API page for BufferedBlockDevice in the handbook? |
I believe we should, thus this should wait until PR for the docs is up |
/morph build |
Build : SUCCESSBuild number : 1905 Triggering tests/morph test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Waiting for docs update (if any, otherwise I'll update the review state)
Will open a documentation PR when back from GEC. |
Exporter Build : SUCCESSBuild number : 1552 |
Test : SUCCESSBuild number : 1723 |
/morph uvisor-test |
Build : SUCCESSBuild number : 2008 Triggering tests/morph test |
Test : FAILUREBuild number : 1818 |
Exporter Build : FAILUREBuild number : 1660 |
/morph build |
Build : SUCCESSBuild number : 2023 Triggering tests/morph test |
/morph build |
Grrr... I hate that GitHub pages don't auto-refresh... |
Build : SUCCESSBuild number : 2025 Triggering tests/morph test |
|
||
static inline uint32_t align_down(bd_size_t val, bd_size_t size) | ||
{ | ||
return val / size * size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, this guy should definitely have parenthesis
*/ | ||
virtual bd_size_t size() const; | ||
|
||
protected: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nonblocker, but is there a reason this is protected?
There was a problem hiding this 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 👍
Test : FAILUREBuild number : 1831 |
Exporter Build : SUCCESSBuild number : 1674 |
/morph test |
2 similar comments
/morph test |
/morph test |
Test : FAILUREBuild number : 1850 |
/morph test |
Test : SUCCESSBuild number : 1869 |
@kjbracey-arm @0xc0170 Are you guys happy now with this ? |
Description
Block device allowing smaller read and program sizes for the underlying BD, using a cache.
Pull request type