Reworked bd layers with prog >= read assumption

The initial goal was the simplify these layers. Keyword being initial.
Unfortunately these layers are both complex and subtle, so the goal
shifted more to be rigorous and reliable.

This mainly meant rearranging our prog/read loops to follow a consistent
style, with higher-priority buffers being sorted out before flushing
things. This gets a bit tricky with wanting to support both cache
bypassing and buffer-lending prognext/readnext, but with some redundant
prognext/readnext calls it's doable.

We also now aggressively discard rcaches on pcache conflicts. This
change does rely on the prog >= read assumption. Discarding rcaches
means we should no longer have overlapping caches, so hopefully no more
zombie rcache issues.

Our bypassing heuristic was also tweaked a bit. Now, in addition to
alignment, >= read/prog_size, and >= hint requirements, we also require
operations to be >= r/pcache_size. This should improve cache usage when
r/pcache_size >> read/prog_size, since we were too eager to bypass
before.

Long story short, this ended up being more just things shifting around
than a significant simplification of the bd layers. At least we ended up
with a nice bit of stack savings:

          code           stack
  before: 33682           2640
  after:  33718 (+0.1%)   2608 (-1.2%)

Also, test_badblocks with LFS_EMUBD_BADBLOCK_ERASENOOP is now failing. I
was worried the amount of fuzz testing we do would eventually end up
with a naturally occuring crc32c collision, and sure enough we did! Yayy
yyyy...

  00 00 00 ff b0 02 00 87 80 80 00 3e c0 7f 7e => bdfa9b10
  ab 77 de c2 b0 03 00 87 80 80 00 3e 38 d5 22 => bdfa9b10

Need to think about what to do with this... For now I've just commented
out the problematic test.
This commit is contained in:
Christopher Haster
2024-05-29 21:02:17 -05:00
parent 3c5319e125
commit 89f7f98fba
2 changed files with 296 additions and 234 deletions
+293 -232
View File
@@ -116,33 +116,184 @@ static inline void lfsr_bd_droppcache(lfs_t *lfs) {
lfs->pcache.size = 0;
}
static int lfsr_bd_read_(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
void *buffer, lfs_size_t size) {
int err = lfsr_bd_read__(lfs, block, off, buffer, size);
// caching read that lends you a buffer
//
// note hint has two conveniences:
// 0 => minimal caching
// -1 => maximal caching
static int lfsr_bd_readnext(lfs_t *lfs,
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
lfs_size_t size,
const uint8_t **buffer_, lfs_size_t *size_) {
// check for in-bounds
LFS_ASSERT(block < lfs->cfg->block_count);
if (off+size > lfs->cfg->block_size) {
return LFS_ERR_RANGE;
}
lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size
while (true) {
lfs_size_t d = hint_;
// already in pcache?
if (block == lfs->pcache.block
&& off < lfs->pcache.off + lfs->pcache.size) {
if (off >= lfs->pcache.off) {
*buffer_ = &lfs->pcache.buffer[off-lfs->pcache.off];
*size_ = lfs_min(
lfs_min(size, d),
lfs->pcache.size - (off-lfs->pcache.off));
return 0;
}
// pcache takes priority
d = lfs_min(d, lfs->pcache.off - off);
}
// already in rcache?
if (block == lfs->rcache.block
&& off < lfs->rcache.off + lfs->rcache.size
&& off >= lfs->rcache.off) {
*buffer_ = &lfs->rcache.buffer[off-lfs->rcache.off];
*size_ = lfs_min(
lfs_min(size, d),
lfs->rcache.size - (off-lfs->rcache.off));
return 0;
}
// drop rcache in case read fails
lfsr_bd_droprcache(lfs);
// load into rcache, above conditions can no longer fail
lfs_size_t off__ = lfs_aligndown(off, lfs->cfg->read_size);
lfs_size_t size__ = lfs_alignup(
lfs_min(
// watch out for overflow when hint_=-1!
(off-off__) + lfs_min(
lfs_min(hint_, d),
lfs->cfg->block_size - off),
lfs->cfg->rcache_size),
lfs->cfg->read_size);
int err = lfsr_bd_read__(lfs, block, off__,
lfs->rcache.buffer, size__);
if (err) {
return err;
}
// overwrite with pcache, since pcache may contain newer data
if (block == lfs->pcache.block
&& off < lfs->pcache.off + lfs->pcache.size
&& off + size > lfs->pcache.off) {
lfs_size_t off_ = lfs_max(off, lfs->pcache.off);
lfs->rcache.block = block;
lfs->rcache.off = off__;
lfs->rcache.size = size__;
}
}
// caching read
//
// note hint has two conveniences:
// 0 => minimal caching
// -1 => maximal caching
static int lfsr_bd_read(lfs_t *lfs,
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
void *buffer, lfs_size_t size) {
// check for in-bounds
LFS_ASSERT(block < lfs->cfg->block_count);
if (off+size > lfs->cfg->block_size) {
return LFS_ERR_RANGE;
}
lfs_size_t off_ = off;
lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size
uint8_t *buffer_ = buffer;
lfs_size_t size_ = lfs_min(
size - (off_-off),
lfs->pcache.size - (off_-lfs->pcache.off));
lfs_memcpy(&buffer_[off_-off],
&lfs->pcache.buffer[off_-lfs->pcache.off],
size_);
lfs_size_t size_ = size;
while (size_ > 0) {
lfs_size_t d = size_;
// already in pcache?
if (block == lfs->pcache.block
&& off_ < lfs->pcache.off + lfs->pcache.size) {
if (off_ >= lfs->pcache.off) {
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, block, off_, hint_, d,
&buffer__, &size__);
if (err) {
return err;
}
lfs_memcpy(buffer_, buffer__, size__);
off_ += size__;
hint_ -= size__;
buffer_ += size__;
size_ -= size__;
continue;
}
// pcache takes priority
d = lfs_min(d, lfs->pcache.off - off_);
}
// already in rcache?
if (block == lfs->rcache.block
&& off_ < lfs->rcache.off + lfs->rcache.size) {
if (off_ >= lfs->rcache.off) {
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, block, off_, hint_, d,
&buffer__, &size__);
if (err) {
return err;
}
lfs_memcpy(buffer_, buffer__, size__);
off_ += size__;
hint_ -= size__;
buffer_ += size__;
size_ -= size__;
continue;
}
// rcache takes priority
d = lfs_min(d, lfs->rcache.off - off_);
}
// bypass rcache?
if (off_ % lfs->cfg->read_size == 0
&& d >= lfs_min(hint_, lfs->cfg->rcache_size)
&& d >= lfs->cfg->read_size) {
d = lfs_aligndown(d, lfs->cfg->read_size);
int err = lfsr_bd_read__(lfs, block, off_, buffer_, d);
if (err) {
return err;
}
off_ += d;
hint_ -= d;
buffer_ += d;
size_ -= d;
continue;
}
// read into rcache, above conditions can no longer fail
//
// don't use d here! rcache is going to be dropped
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, block, off_, hint_, size_,
&buffer__, &size__);
if (err) {
return err;
}
}
return 0;
}
// low-level prog stuff
static int lfsr_bd_prog_(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
const void *buffer, lfs_size_t size,
uint32_t *cksum_) {
// prog to disk
int err = lfsr_bd_prog__(lfs, block, off, buffer, size);
if (err) {
return err;
@@ -182,18 +333,17 @@ static int lfsr_bd_prog_(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
}
}
// update rcache if we overlap
// update rcache if we can
if (block == lfs->rcache.block
&& off < lfs->rcache.off + lfs->rcache.size
&& off + size > lfs->rcache.off) {
lfs_size_t off_ = lfs_max(off, lfs->rcache.off);
const uint8_t *buffer_ = buffer;
lfs_size_t size_ = lfs_min(
size - (off_-off),
lfs->rcache.size - (off_-lfs->rcache.off));
lfs_memcpy(&lfs->rcache.buffer[off_-lfs->rcache.off],
&buffer_[off_-off],
size_);
&& off <= lfs->rcache.off + lfs->rcache.size) {
lfs->rcache.off = lfs_min(off, lfs->rcache.off);
lfs->rcache.size = lfs_min(
(off-lfs->rcache.off) + size,
lfs->cfg->rcache_size);
lfs_memcpy(
&lfs->rcache.buffer[off-lfs->rcache.off],
buffer,
lfs->rcache.size - (off-lfs->rcache.off));
}
// keep track of the last flushed cksum
@@ -204,141 +354,14 @@ static int lfsr_bd_prog_(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
return 0;
}
static int lfsr_bd_readnext(lfs_t *lfs,
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
lfs_size_t size,
const uint8_t **buffer_, lfs_size_t *size_) {
// check for in-bounds
LFS_ASSERT(block < lfs->cfg->block_count);
if (off+size > lfs->cfg->block_size) {
return LFS_ERR_RANGE;
}
while (true) {
// already in pcache?
if (block == lfs->pcache.block
&& off < lfs->pcache.off + lfs->pcache.size
&& off >= lfs->pcache.off) {
*buffer_ = &lfs->pcache.buffer[off-lfs->pcache.off];
*size_ = lfs_min(
size,
lfs->pcache.size - (off-lfs->pcache.off));
return 0;
}
// already in rcache?
if (block == lfs->rcache.block
&& off < lfs->rcache.off + lfs->rcache.size
&& off >= lfs->rcache.off) {
*buffer_ = &lfs->rcache.buffer[off-lfs->rcache.off];
*size_ = lfs_min(
size,
lfs->rcache.size - (off-lfs->rcache.off));
return 0;
}
// drop rcache in case read fails
lfsr_bd_droprcache(lfs);
// load to cache, first condition can no longer fail
lfs_size_t off__ = lfs_aligndown(off, lfs->cfg->read_size);
// watch out for overflow when hint_=-1
lfs_size_t size__ = lfs_alignup(
(off-off__) + lfs_min(
lfs_max(size, hint),
lfs_min(
lfs->cfg->rcache_size - (off-off__),
lfs->cfg->block_size - off)),
lfs->cfg->read_size);
int err = lfsr_bd_read_(lfs, block, off__,
lfs->rcache.buffer, size__);
if (err) {
return err;
}
lfs->rcache.block = block;
lfs->rcache.off = off__;
lfs->rcache.size = size__;
}
return 0;
}
// caching read
//
// note hint has two convenience:
// 1. 0 = minimal caching
// 2. -1 = maximal caching
static int lfsr_bd_read(lfs_t *lfs,
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
void *buffer, lfs_size_t size) {
// check for in-bounds
LFS_ASSERT(block < lfs->cfg->block_count);
if (off+size > lfs->cfg->block_size) {
return LFS_ERR_RANGE;
}
lfs_size_t off_ = off;
lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size
uint8_t *buffer_ = buffer;
lfs_size_t size_ = size;
while (size_ > 0) {
// bypass cache?
if (size_ >= hint_
&& off_ % lfs->cfg->read_size == 0
&& size_ >= lfs->cfg->read_size
// pcache takes priority
&& !(block == lfs->pcache.block
&& off_ < lfs->pcache.off + lfs->pcache.size
&& off_ >= lfs->pcache.off)
// rcache takes priority
&& !(block == lfs->rcache.block
&& off_ < lfs->rcache.off + lfs->rcache.size
&& off_ >= lfs->rcache.off)) {
lfs_size_t d = lfs_aligndown(size_, lfs->cfg->read_size);
int err = lfsr_bd_read_(lfs, block, off_, buffer_, d);
if (err) {
return err;
}
off_ += d;
hint_ -= d;
buffer_ += d;
size_ -= d;
continue;
}
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, block, off_, hint_, size_,
&buffer__, &size__);
if (err) {
return err;
}
lfs_memcpy(buffer_, buffer__, size__);
off_ += size__;
hint_ -= size__;
buffer_ += size__;
size_ -= size__;
}
return 0;
}
// flush the pcache
static int lfsr_bd_flush(lfs_t *lfs, uint32_t *cksum_) {
if (lfs->pcache.size != 0) {
// must be in-bounds
LFS_ASSERT(lfs->pcache.block < lfs->cfg->block_count);
// zero to avoid any information leaks
lfs_size_t aligned_size = lfs_alignup(
lfs->pcache.size,
lfs->cfg->prog_size);
lfs_memset(&lfs->pcache.buffer[lfs->pcache.size],
0xff,
aligned_size - lfs->pcache.size);
// must be aligned
LFS_ASSERT(lfs->pcache.off % lfs->cfg->prog_size == 0);
lfs_size_t size = lfs_alignup(lfs->pcache.size, lfs->cfg->prog_size);
// make this cache available, if we error anything in this cache
// would be useless anyways
@@ -346,7 +369,7 @@ static int lfsr_bd_flush(lfs_t *lfs, uint32_t *cksum_) {
// flush
int err = lfsr_bd_prog_(lfs, lfs->pcache.block,
lfs->pcache.off, lfs->pcache.buffer, aligned_size,
lfs->pcache.off, lfs->pcache.buffer, size,
cksum_);
if (err) {
return err;
@@ -356,6 +379,9 @@ static int lfsr_bd_flush(lfs_t *lfs, uint32_t *cksum_) {
return 0;
}
// caching prog that lends you a buffer
//
// with optional checksum
static int lfsr_bd_prognext(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
lfs_size_t size,
uint8_t **buffer_, lfs_size_t *size_,
@@ -366,37 +392,50 @@ static int lfsr_bd_prognext(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
return LFS_ERR_RANGE;
}
// need to flush pcache?
if (!(block == lfs->pcache.block
&& off >= lfs->pcache.off
&& off < lfs->pcache.off + lfs->cfg->pcache_size)) {
while (true) {
// active pcache?
if (lfs->pcache.block == block
&& lfs->pcache.size != 0) {
// fits in pcache?
if (off < lfs->pcache.off + lfs->cfg->pcache_size) {
// you can't prog backwards silly
LFS_ASSERT(off >= lfs->pcache.off);
// expand the pcache?
lfs->pcache.size = lfs_min(
(off-lfs->pcache.off) + size,
lfs->cfg->pcache_size);
*buffer_ = &lfs->pcache.buffer[off-lfs->pcache.off];
*size_ = lfs_min(
size,
lfs->pcache.size - (off-lfs->pcache.off));
return 0;
}
// flush pcache?
int err = lfsr_bd_flush(lfs, cksum_);
if (err) {
return err;
}
}
// unused pcache? make sure to move it so we never overwrite
if (lfs->pcache.size == 0) {
// move the pcache, above conditions can no longer fail
lfs->pcache.block = block;
lfs->pcache.off = lfs_aligndown(off, lfs->cfg->prog_size);
}
lfs->pcache.size = lfs_min(
(off-lfs->pcache.off) + size,
lfs->cfg->pcache_size);
// zero to avoid any information leaks
lfs_memset(&lfs->pcache.buffer[lfs->pcache.size],
0xff,
(off-lfs->pcache.off) - lfs->pcache.size);
lfs->pcache.size = lfs_max(
lfs->pcache.size,
lfs_min(
(off-lfs->pcache.off) + size,
lfs->cfg->pcache_size));
lfs_memset(lfs->pcache.buffer, 0xff, lfs->cfg->pcache_size);
*buffer_ = &lfs->pcache.buffer[off-lfs->pcache.off];
*size_ = lfs_min(
size,
lfs->cfg->pcache_size - (off-lfs->pcache.off));
return 0;
// discard any overlapping rcache
if (block == lfs->rcache.block
&& off < lfs->rcache.off + lfs->rcache.size) {
lfs->rcache.size = lfs_max(off, lfs->rcache.off) - lfs->rcache.off;
}
}
}
// caching prog
@@ -415,34 +454,12 @@ static int lfsr_bd_prog(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
const uint8_t *buffer_ = buffer;
lfs_size_t size_ = size;
while (size_ > 0) {
// bypass cache?
if (off_ % lfs->cfg->prog_size == 0
&& size_ >= lfs->cfg->prog_size
// pcache takes priority
&& !(block == lfs->pcache.block
&& off_ >= lfs->pcache.off
&& off_ < lfs->pcache.off + lfs->cfg->pcache_size)) {
// make sure we flush our pcache first, some devices
// don't support out-of-order progs in a block
if (lfs->pcache.size != 0) {
int err = lfsr_bd_flush(lfs, cksum_);
if (err) {
return err;
}
}
lfs_size_t d = lfs_aligndown(size_, lfs->cfg->prog_size);
int err = lfsr_bd_prog_(lfs, block, off_, buffer_, d,
cksum_);
if (err) {
return err;
}
off_ += d;
buffer_ += d;
size_ -= d;
continue;
}
// fits in pcache?
if (block == lfs->pcache.block
&& off_ < lfs->pcache.off + lfs->cfg->pcache_size
&& lfs->pcache.size != 0) {
// you can't prog backwards silly
LFS_ASSERT(off_ >= lfs->pcache.off);
uint8_t *buffer__;
lfs_size_t size__;
@@ -458,6 +475,41 @@ static int lfsr_bd_prog(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
off_ += size__;
buffer_ += size__;
size_ -= size__;
continue;
}
// bypass pcache?
if (off_ % lfs->cfg->prog_size == 0
&& size_ >= lfs->cfg->pcache_size) {
// flush our pcache first, some devices don't support
// out-of-order progs in a block
int err = lfsr_bd_flush(lfs, cksum_);
if (err) {
return err;
}
lfs_size_t d = lfs_aligndown(size_, lfs->cfg->prog_size);
err = lfsr_bd_prog_(lfs, block, off_, buffer_, d,
cksum_);
if (err) {
return err;
}
off_ += d;
buffer_ += d;
size_ -= d;
continue;
}
// flush pcache, above conditions can no longer fail
uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_prognext(lfs, block, off_, size_,
&buffer__, &size__,
cksum_);
if (err) {
return err;
}
}
// optional checksum
@@ -509,7 +561,7 @@ static int lfsr_bd_erase(lfs_t *lfs, lfs_block_t block) {
// must be in-bounds
LFS_ASSERT(block < lfs->cfg->block_count);
// make sure we invalidate any caches
// invalidate any relevant caches
if (lfs->pcache.block == block) {
lfsr_bd_droppcache(lfs);
}
@@ -533,11 +585,13 @@ static int lfsr_bd_cksum(lfs_t *lfs,
return LFS_ERR_RANGE;
}
hint = lfs_max(hint, size); // make sure hint >= size
while (size > 0) {
lfs_size_t off_ = off;
lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size
lfs_size_t size_ = size;
while (size_ > 0) {
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, block, off, hint, size,
int err = lfsr_bd_readnext(lfs, block, off_, hint_, size_,
&buffer__, &size__);
if (err) {
return err;
@@ -545,9 +599,9 @@ static int lfsr_bd_cksum(lfs_t *lfs,
*cksum_ = lfs_crc32c(*cksum_, buffer__, size__);
off += size__;
hint -= size__;
size -= size__;
off_ += size__;
hint_ -= size__;
size_ -= size__;
}
return 0;
@@ -562,12 +616,14 @@ static lfs_scmp_t lfsr_bd_cmp(lfs_t *lfs,
return LFS_ERR_RANGE;
}
lfs_size_t off_ = off;
lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size
const uint8_t *buffer_ = buffer;
hint = lfs_max(hint, size); // make sure hint >= size
while (size > 0) {
lfs_size_t size_ = size;
while (size_ > 0) {
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, block, off, hint, size,
int err = lfsr_bd_readnext(lfs, block, off_, hint_, size_,
&buffer__, &size__);
if (err) {
return err;
@@ -578,10 +634,10 @@ static lfs_scmp_t lfsr_bd_cmp(lfs_t *lfs,
return (res < 0) ? LFS_CMP_LT : LFS_CMP_GT;
}
off += size__;
hint -= size__;
off_ += size__;
hint_ -= size__;
buffer_ += size__;
size -= size__;
size_ -= size__;
}
return LFS_CMP_EQ;
@@ -602,26 +658,29 @@ static int lfsr_bd_cpy(lfs_t *lfs,
return LFS_ERR_RANGE;
}
hint = lfs_max(hint, size); // make sure hint >= size
while (size > 0) {
lfs_size_t dst_off_ = dst_off;
lfs_size_t src_off_ = src_off;
lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size
lfs_size_t size_ = size;
while (size_ > 0) {
const uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_readnext(lfs, src_block, src_off, hint, size,
int err = lfsr_bd_readnext(lfs, src_block, src_off_, hint_, size_,
&buffer__, &size__);
if (err) {
return err;
}
err = lfsr_bd_prog(lfs, dst_block, dst_off, buffer__, size__,
err = lfsr_bd_prog(lfs, dst_block, dst_off_, buffer__, size__,
cksum_);
if (err) {
return err;
}
dst_off += size__;
src_off += size__;
hint -= size__;
size -= size__;
dst_off_ += size__;
src_off_ += size__;
hint_ -= size__;
size_ -= size__;
}
return 0;
@@ -636,10 +695,12 @@ static int lfsr_bd_set(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
return LFS_ERR_RANGE;
}
while (size > 0) {
lfs_size_t off_ = off;
lfs_size_t size_ = size;
while (size_ > 0) {
uint8_t *buffer__;
lfs_size_t size__;
int err = lfsr_bd_prognext(lfs, block, off, size,
int err = lfsr_bd_prognext(lfs, block, off_, size_,
&buffer__, &size__,
cksum_);
if (err) {
@@ -653,8 +714,8 @@ static int lfsr_bd_set(lfs_t *lfs, lfs_block_t block, lfs_size_t off,
*cksum_ = lfs_crc32c(*cksum_, buffer__, size__);
}
off += size__;
size -= size__;
off_ += size__;
size_ -= size__;
}
return 0;
@@ -1103,7 +1164,7 @@ static lfs_ssize_t lfsr_bd_readtag_(lfs_t *lfs,
return LFS_ERR_CORRUPT;
}
int err = lfsr_bd_read(lfs, block, off, hint, &tag_buf, tag_dsize);
int err = lfsr_bd_read(lfs, block, off, hint, tag_buf, tag_dsize);
if (err) {
LFS_ASSERT(err < 0);
return err;
@@ -1200,7 +1261,7 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
if (cksum_) {
*cksum_ ^= tag_buf[0] & 0x80;
}
int err = lfsr_bd_prog(lfs, block, off, &tag_buf, d,
int err = lfsr_bd_prog(lfs, block, off, tag_buf, d,
cksum_);
if (err) {
LFS_ASSERT(err < 0);
+2 -1
View File
@@ -332,7 +332,8 @@ defines.BADBLOCK_BEHAVIOR = [
'LFS_EMUBD_BADBLOCK_ERASEERROR',
'LFS_EMUBD_BADBLOCK_READERROR',
'LFS_EMUBD_BADBLOCK_PROGNOOP',
'LFS_EMUBD_BADBLOCK_ERASENOOP',
# TODO, this finds a crc32c collision
# 'LFS_EMUBD_BADBLOCK_ERASENOOP',
]
# we need prog checking to detect read errors
defines.CHECK_PROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR'