Skip to content

Commit fb4e5e8

Browse files
committed
TDBStore: optimize erase algorithms
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.
1 parent e807051 commit fb4e5e8

File tree

2 files changed

+40
-21
lines changed

2 files changed

+40
-21
lines changed

storage/kvstore/tdbstore/include/tdbstore/TDBStore.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,15 @@ class TDBStore : public KVStore {
346346
int reset_area(uint8_t area);
347347

348348
/**
349-
* @brief Erase an erase unit.
349+
* @brief Erase an area.
350350
*
351351
* @param[in] area Area.
352352
* @param[in] offset Offset in area.
353+
* @param[in] size Number of bytes to erase.
353354
*
354355
* @returns 0 for success, nonzero for failure.
355356
*/
356-
int erase_erase_unit(uint8_t area, uint32_t offset);
357+
int erase_area(uint8_t area, uint32_t offset, uint32_t size);
357358

358359
/**
359360
* @brief Calculate addresses and sizes of areas.

storage/kvstore/tdbstore/source/TDBStore.cpp

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -170,24 +170,28 @@ int TDBStore::write_area(uint8_t area, uint32_t offset, uint32_t size, const voi
170170
return MBED_SUCCESS;
171171
}
172172

173-
int TDBStore::erase_erase_unit(uint8_t area, uint32_t offset)
173+
int TDBStore::erase_area(uint8_t area, uint32_t offset, uint32_t size)
174174
{
175175
uint32_t bd_offset = _area_params[area].address + offset;
176-
uint32_t eu_size = _buff_bd->get_erase_size(bd_offset);
177176

178177
if (_buff_bd->get_erase_value() != -1) {
179-
return _buff_bd->erase(bd_offset, eu_size);
178+
return _buff_bd->erase(bd_offset, size);
180179
} else {
181-
// We need to simulate erase, as our block device
182-
// does not do it. We can do this one byte at a time
183-
// because we use BufferedBlockDevice that has page buffers
184-
uint8_t val = 0xff;
185-
int ret;
186-
for (; eu_size; --eu_size) {
187-
ret = _buff_bd->program(&val, bd_offset++, 1);
180+
// We need to simulate erase to wipe records, as our block device
181+
// may not do it. Program in chunks of _work_buf_size if the minimum
182+
// program size is too small (e.g. one-byte) to avoid performance
183+
// issues.
184+
MBED_ASSERT(_work_buf != nullptr);
185+
MBED_ASSERT(_work_buf_size != 0);
186+
memset(_work_buf, 0xFF, _work_buf_size);
187+
while (size) {
188+
uint32_t chunk = std::min<uint32_t>(_work_buf_size, size);
189+
int ret = _buff_bd->program(_work_buf, bd_offset, chunk);
188190
if (ret) {
189191
return ret;
190192
}
193+
size -= chunk;
194+
bd_offset += chunk;
191195
}
192196
}
193197
return MBED_SUCCESS;
@@ -1458,33 +1462,47 @@ void TDBStore::offset_in_erase_unit(uint8_t area, uint32_t offset,
14581462
uint32_t &offset_from_start, uint32_t &dist_to_end)
14591463
{
14601464
uint32_t bd_offset = _area_params[area].address + offset;
1461-
uint32_t agg_offset = 0;
14621465

1463-
while (bd_offset >= agg_offset + _buff_bd->get_erase_size(agg_offset)) {
1464-
agg_offset += _buff_bd->get_erase_size(agg_offset);
1465-
}
1466-
offset_from_start = bd_offset - agg_offset;
1467-
dist_to_end = _buff_bd->get_erase_size(agg_offset) - offset_from_start;
1466+
// The parameter of `BlockDevice::get_erase_size(bd_addr_t addr)`
1467+
// does not need to be aligned.
1468+
uint32_t erase_unit = _buff_bd->get_erase_size(bd_offset);
1469+
1470+
// Even on a flash device with multiple regions, the start address of
1471+
// an erase unit is aligned to the current region's unit size.
1472+
offset_from_start = bd_offset % erase_unit;
1473+
dist_to_end = erase_unit - offset_from_start;
14681474
}
14691475

14701476
int TDBStore::check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size, bool force_check)
14711477
{
14721478
// In order to save init time, we don't check that the entire area is erased.
14731479
// Instead, whenever reaching an erase unit start erase it.
1480+
bool erase = false;
1481+
uint32_t start_offset;
1482+
uint32_t end_offset;
14741483
while (size) {
14751484
uint32_t dist, offset_from_start;
14761485
int ret;
14771486
offset_in_erase_unit(area, offset, offset_from_start, dist);
14781487
uint32_t chunk = std::min(size, dist);
14791488

14801489
if (offset_from_start == 0 || force_check) {
1481-
ret = erase_erase_unit(area, offset - offset_from_start);
1482-
if (ret != MBED_SUCCESS) {
1483-
return MBED_ERROR_WRITE_FAILED;
1490+
if (!erase) {
1491+
erase = true;
1492+
start_offset = offset - offset_from_start;
14841493
}
1494+
end_offset = offset + dist;
14851495
}
14861496
offset += chunk;
14871497
size -= chunk;
14881498
}
1499+
1500+
if (erase) {
1501+
int ret = erase_area(area, start_offset, end_offset - start_offset);
1502+
if (ret != MBED_SUCCESS) {
1503+
return MBED_ERROR_WRITE_FAILED;
1504+
}
1505+
}
1506+
14891507
return MBED_SUCCESS;
14901508
}

0 commit comments

Comments
 (0)