-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Improve HeapBlockDevice, TDBStore and tests #14483
Conversation
@@ -93,8 +95,6 @@ class HeapBlockDevice : public BlockDevice { | |||
virtual int read(void *buffer, bd_addr_t addr, bd_size_t size); | |||
|
|||
/** Program blocks to a block device | |||
* | |||
* The blocks must have been erased prior to being programmed |
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 removed this because it's definitely not true - the heap buffer is only allocated when a block is programmed, not when it's erased.
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 read this as being about checks the class method will do to enforce that the block is erased before being programmed; policy you must follow as a programmer. I don't see code to check this, but maybe there ought to be some code here if we want to ensure programs written to use the HeapBlockDevice will continue to work when used with actual flash-based devices.
I checked the implementation of is_valid_program()
, but it doesn't appear to check that a block was erased before it can be programmed. If this policy is enforced anywhere, I'm not clear on where yet.
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.
Good point. I think it's a real inconsistency in our existing API.
Mbed OS allows a BlockDevice
to be either flash-based or not.
- For flash-based ones (e.g.
SPIFBlockDevice
), I think the actual erase requirement is set by the hardware (if I understand correctly). - For non-flash-based ones, "erase" should in theory be unsupported. One example is SD. From its documentation
Because an SD card doesn't need preparation, erase is not applicable on SDBlockDevice; it's a no-op operation.
So there's no single policy for all BlockDevice
s.
HeapBlockDevice
has always been quite self-contradicting:
- It was intended to be non-flash-based. This was why we used to use a flash simulation layer
FlashSimBlockDevice
on top of it, to simulate erase operations when used forTDBStore
which used to required erase (but not anymore). - However, a partially implemented & partially lacking
HeapBlockDevice::erase()
always existed - it performs size/alignment check, but doesn't do the actual erase. - And this line of Doxygen we are talking about suggests erase is required before program.
In my opinion, the proper solution for best consistency is remove ::erase
and any mentions of "erase" from HeapBlockDevice
. This would change the API for users who do like to use ::erase
.
Any preference?
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 SD example I used above, its Doxygen contradicts with its documentation.
Documentation:
Because an SD card doesn't need preparation, erase is not applicable on SDBlockDevice; it's a no-op operation.
Doxygen:
* @note The blocks must be erased prior to programming |
So HeapBlockDevice is just another one.
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.
Could you update SDBlockDevice too, please?
If we remove erase from HeapBlockDevice, it might no longer be swappable for other BlockDevice implementations. HeapBlockDevice::erase() may need to be a no-op for compatibility reasons (with perhaps things like LittleFS layered on top). Also, if HeapBlockDevice::program() allocates memory, what should free the allocated memory? In an earlier iteration of this PR, you had HeapBlockDevice::erase() free memory. If HeapBlockDevice::erase() is instead a no-op, should we move the freeing of allocated memory to HeapBlockDevice::~HeapBlockDevice() instead?
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.
The base class's virtual BlockDevice::erase()
is already a no-op. The freeing of allocated memory is also done in HeapBlockDevice::~HeapBlockDevice()
already. So removing HeapBlockDevice::erase()
and updating Doxygen should be enough.
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.
Following discussions below, I kept HeapBlockDevice::erase()
because it doesn't break compatibility/consistency with the definition of the BlockDevice
API.
@LDong-Arm, thank you for your changes. |
a245c95
to
6d077d9
Compare
c3604a5
to
055168f
Compare
Looking at this, it seems existing and new TDBStore code is inconsistent with BlockDevice documentation. TDBStore is taking Am I missing something? Presumably all of this works now, suggesting we have no "erase-needed-with-indeterminate-erase-value" devices, suggesting maybe just BlockDevice doxygen could be modified to drop that possibility. |
@kjbracey-arm Thanks, it's a really good point. It looks to me that there's quite a lot of ambiguity around flash vs non-flash I'll look in more details and try to align things. |
According to the BlockDevice API as written, erase can certainly be a no-op, as long as the erase_value is -1 so that the caller doesn't expect any particular result from the erase. But it appears the user contract is that users of the API must always call erase on a location before programming it. Which means there must still be an "erase size", even if erase is a no-op. And it appears BlockDevice has a default implementation which makes that the program size, which is sensible. So I think the BlockDevice API is consistent, but TDBStore doesn't quite fit to it. I think I'm mildly in favour of making TDBStore follow BlockDevice docs, rather than changing those docs, but I don't know how complex that would be. It may well not be worth the effort. If you did want to fix TDBStore, you'd want to make a test-bed BlockDevice that did require erase but had erase_value -1. I guess we don't currently have one. |
@kjbracey-arm Thanks for the explanation. For block devices that's don't actually support erase, it makes sense for them to inherit the default
I agree erase value being -1 doesn't necessarily mean erase not supported, by definition. In my opinion, users of the |
055168f
to
2a56915
Compare
2a56915
to
4da2392
Compare
Please keep review on hold. I'm still seeing a number of issues, trying to debug them... |
4da2392
to
e4dd420
Compare
if (fs->mount(&heap) != MBED_SUCCESS) { | ||
EXPECT_EQ(fs->reformat(&heap), MBED_SUCCESS); | ||
} |
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.
See #12652 (comment)
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.
TODO: Remove this test case as per #12652 (comment)
62f0fe6
to
22bb52d
Compare
Finally ready for review again, after solving multiple test failures caused by the previous iterations of my changes. |
CI started |
There's still a todo: #14483 (comment) |
Jenkins CI Test : ✔️ SUCCESSBuild Number: 1 | 🔒 Jenkins CI Job | 🌐 Logs & ArtifactsCLICK for Detailed Summary
|
Mbed OS does not enable C++ exceptions, so we should call `new` with `std::nothrow` which returns a C-style NULL pointer when allocation fails to allow error handling. For consistency of style within the same file, this commit also replaces `malloc()` and `free()` to `new (std::nothrow)` and `delete`.
`HeapBlockDevice::erase()` previously performed a range and alignment check only. This commit adds freeing of heap memory.
22bb52d
to
7400281
Compare
The "erased_set_get" test case deinits `FileSystemStore`, erases the block device, and reinits `FileSystemStore`. This of course fails, because `BlockDevice::erase()` removes all stored data including the format of the `FileSystem` (middle layer), unless the particular type of block device has a no-op erase implementation. Note: Previously `HeapBlockDevice::erase()` was essentially a no-op so this test case did not fail. We recently added the freeing of heap memory and it uncovered the problem.
Currently `TDBStore::offset_in_erase_unit()` and `TDBStore::check_erase_before_write()` loop through erase units one-by-one, until the entire range is covered. This is very inefficient when the erase size is tiny, e.g. one-byte on a non-flash device for which we use program as erase. This commit reworks the algorithms, based on the fact that a block device can erase or program as many units as needed in one go.
From the documentations of `BlockDevice::get_erase_value()`: -1 if you can't rely on the value of the erased storage and `BlockDevice::program()`: The blocks must have been erased prior to being programmed So, `BlockDevice::erase()` should always be called regardless of erase value.
Each block of HeapBlockDevice is only allocated from the heap when that block is programmed. And erasing a block frees the associated buffer. To decide if there is enough heap to run the TDBStore Whitebox tests, we need to perform a trial program() instead of erase().
7400281
to
7b763be
Compare
CI started |
Jenkins CI Test : ✔️ SUCCESSBuild Number: 2 | 🔒 Jenkins CI Job | 🌐 Logs & ArtifactsCLICK for Detailed Summary
|
Summary of changes
Fixes: #14128, #14157
The PR does the following:
FlashSimBlockDevice
from TDBStore tests and passed the realHeapBlockDevice
directly to TDBStore, because flash simulation was no longer needed after TDBStore refactoring #11987. This caused TDBStore tests to fail (instead of skip properly) on low-RAM targets, becauseHeapBlockDevice::erase()
does not trigger any heap allocations, unlikeFlashSimBlockDevice::erase()
which we used before.To fix this issue, use
HeapBlockDevice::program()
instead which allocates heap memory on the fly for blocks being programmed. If this fails, skip the test for not having enough heap.HeapBlockDevice
: Usenew (std::nothrow)
for all allocations, instead of a mixture ofmalloc
and uncheckednew
(exceptions are not enabled by Mbed OS).HeapBlockDevice::erase()
: Deallocate memory of erased blocks.FileSystemStore
unit test: add missing reformat step after erase.TDBStore
's erase operations. It also always callsBlockDevice::erase()
regardless of erase value, as erase-before-program is required byBlockDevice
's documentation.Impact of changes
Migration actions required
Documentation
None.
Pull request type
Test results
Manual testing on K64F:
Reviewers
@ARMmbed/mbed-os-core @geky @davidsaada