From 458fe16f38890842a141d87b9fc2421a3e95c13d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 10 Aug 2024 17:41:51 -0500 Subject: [PATCH] Extended emubd to test metastability, added ckprog/ckread tests Metastability is a rather nasty error condition where successive reads to a memory location may return different values, either due to bus issues or a failed prog. It's a tricky error condition to detect, and one that ckreads was, in theory, supposed to help with. To help test metastability (and other single-bit errors), emubd gained several new features: - LFS_EMUBD_BADBLOCK_PROGFLIP - Prog flips a bit - LFS_EMUBD_BADBLOCK_READFLIP - Read flips a bit sometimes - LFS_EMUBD_POWERLOSS_METASTABLE - Reads may flip a bit These only affect a single bit in a given block, but by randomizing which bit during every erase (and exhaustive bit testing in test_ck) we should still see some fairly interesting bit-error patterns over time. It's a bit difficult to test with more than a single bit error because you can quickly find checksum/parity collisions when fuzz testing. But there may be other interesting error patterns to look at in the future? Also the erase_cycles implementation got a bit of a rework since it was lopsided previously (progs/reads would always error before erases). And since I was messing with emubd's internals I added lfs_emubd_markbad/ markgood and a few other convenience functions that seem useful: - lfs_emubd_seed - Manually set the prng, needed in test_ck actually - lfs_emubd_markbad - Mark block as bad, same as wear=-1 - lfs_emubd_markgood - Mark block as good, same as wear=0 - lfs_emubd_badbit - Get which big failed - lfs_emubd_setbadbit - Set which bit will fail - lfs_emubd_randomizebadbit - Randomize bad bit on erase - lfs_emubd_markbadbit - Mark bit as bad, same as setbadbit+markbad --- The intention of this new metastability emulation was to extend test_ck to test ckreads/ckprogs. This went... interestingly. The good news, the new emulation and tests worked quite well. They were able to quite quickly show that ckreads is fundamentally not able to detect all single-bit errors in our current design. The problem boils down to the fact that the location of our parity bits depends on the tag's leb128-encoded size. If a bit flip changes this size field, we end up with a new parity bit, which 50/50 may or may not detect the error. For example, one bit flip: 40 0c 00 12 80 0d ff ff '----.----' ^--------------------. '- altble 0xc w0 -18 parity=1 40 0c 80 12 80 0d ff ff '-------.-------' ^----------------------. '- altble 0xc w2304 -1664 parity=1 This doesn't make ckreads _completely_ useless, just mostly useless. We can still use it to check parity bits, but without a systematic proof. But there's enough problems with ckreads: performance, RAM, code, etc, that I think it may just be an interesting proof-of-concept and not something users should actually use. Checking reads in the bd-layer solves all of these problems... --- At the very least ckprogs gets better testing, thanks to new tests in test_ck and the addition of LFS_EMUBD_BADBLOCK_PROGFLIP in test_badblocks. The extra testing also found a ckprog/ckread hole in that we don't ckprog/ckread during lfsr_format! I fixed this by making lfsr_format always use ckprogs/ckreads if available, but maybe lfsr_format should take its own set of flags? Funnily enough this had no impact on code size since it probably just changed the constant in a constant pool: code stack before: 37872 3048 after: 37872 (+0.0%) 3048 (+0.0%) --- bd/lfs_emubd.c | 379 ++++++++++++++++++-- bd/lfs_emubd.h | 59 +++- lfs.c | 5 +- tests/test_badblocks.toml | 154 ++++----- tests/test_ck.toml | 712 +++++++++++++++++++++++++++++++++++++- 5 files changed, 1174 insertions(+), 135 deletions(-) diff --git a/bd/lfs_emubd.c b/bd/lfs_emubd.c index 458ac17a..a7517553 100644 --- a/bd/lfs_emubd.c +++ b/bd/lfs_emubd.c @@ -77,6 +77,8 @@ static lfs_emubd_block_t *lfs_emubd_mutblock( block_->rc = 1; block_->wear = 0; + block_->metastable = false; + block_->bad_bit = 0; // zero for consistency lfs_emubd_t *bd = cfg->context; @@ -312,14 +314,29 @@ int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, const lfs_emubd_block_t *b = bd->blocks[block]; if (b) { // block bad? - if (bd->cfg->erase_cycles && b->wear >= bd->cfg->erase_cycles && - bd->cfg->badblock_behavior == LFS_EMUBD_BADBLOCK_READERROR) { - LFS_EMUBD_TRACE("lfs_emubd_read -> %d", LFS_ERR_CORRUPT); - return LFS_ERR_CORRUPT; + if (b->wear > bd->cfg->erase_cycles) { + // erroring reads? error + if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_READERROR) { + LFS_EMUBD_TRACE("lfs_emubd_read -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + } } // read data memcpy(buffer, &b->data[off], size); + + // metastable? randomly decide if our bad bit flips + if (b->metastable) { + lfs_size_t bit = b->bad_bit & 0x7fffffff; + if (bit/8 >= off + && bit/8 < off+size + && (lfs_emubd_prng(&bd->prng) & 1)) { + ((uint8_t*)buffer)[(bit/8) - off] ^= 1 << (bit%8); + } + } + + // no block yet } else { // zero for consistency memset(buffer, @@ -361,8 +378,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, // were we erased properly? LFS_ASSERT(bd->blocks[block]); if (bd->cfg->erase_value != -1 - && !(bd->cfg->erase_cycles - && bd->blocks[block]->wear >= bd->cfg->erase_cycles)) { + && bd->blocks[block]->wear <= bd->cfg->erase_cycles) { for (lfs_off_t i = 0; i < size; i++) { LFS_ASSERT(bd->blocks[block]->data[off+i] == bd->cfg->erase_value); } @@ -372,7 +388,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, if (bd->power_cycles > 0) { bd->power_cycles -= 1; if (bd->power_cycles == 0) { - // if emulating some bits, choose a random bit to flip + // emulating some bits? choose a random bit to flip if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_SOMEBITS) { // mutate the block @@ -387,7 +403,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, // flip bit lfs_size_t bit = lfs_emubd_prng(&bd->prng) % (cfg->prog_size*8); - b->data[off + (bit/8)] ^= (1 << (bit%8)); + b->data[off + (bit/8)] ^= 1 << (bit%8); // mirror to disk file? if (bd->disk) { @@ -408,7 +424,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, } } - // if emulating most bits, prog data and choose a random bit + // emulating most bits? prog data and choose a random bit // to flip } else if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_MOSTBITS) { @@ -427,7 +443,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, // flip bit lfs_size_t bit = lfs_emubd_prng(&bd->prng) % (cfg->prog_size*8); - b->data[off + (bit/8)] ^= (1 << (bit%8)); + b->data[off + (bit/8)] ^= 1 << (bit%8); // mirror to disk file? if (bd->disk) { @@ -448,7 +464,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, } } - // if emulating out-of-order writes, revert everything unsynced + // emulating out-of-order writes? revert everything unsynced // except for our current block } else if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_OOO) { @@ -484,6 +500,50 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, } } } + + // emulating metastability? prog data, choose a random bad bit, + // and mark as metastable + } else if (bd->cfg->powerloss_behavior + == LFS_EMUBD_POWERLOSS_METASTABLE) { + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, + bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // prog data + memcpy(&b->data[off], buffer, size); + + // choose a new bad bit unless overridden + if (!(0x80000000 & b->bad_bit)) { + b->bad_bit = lfs_emubd_prng(&bd->prng) + % (cfg->block_size*8); + } + + // mark as metastable + b->metastable = true; + + // mirror to disk file? + if (bd->disk) { + off_t res1 = lseek(bd->disk->fd, + (off_t)block*cfg->block_size + (off_t)off, + SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", err); + return err; + } + + ssize_t res2 = write(bd->disk->fd, &b->data[off], size); + if (res2 < 0) { + int err = -errno; + LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", err); + return err; + } + } } // powerloss! @@ -533,23 +593,47 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, bd->blocks[block] = b; // block bad? - if (bd->cfg->erase_cycles && b->wear >= bd->cfg->erase_cycles) { - if (bd->cfg->badblock_behavior == - LFS_EMUBD_BADBLOCK_PROGERROR) { + if (b->wear > bd->cfg->erase_cycles) { + // erroring progs? error + if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_PROGERROR) { LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", LFS_ERR_CORRUPT); return LFS_ERR_CORRUPT; - } else if (bd->cfg->badblock_behavior == - LFS_EMUBD_BADBLOCK_PROGNOOP || - bd->cfg->badblock_behavior == - LFS_EMUBD_BADBLOCK_ERASENOOP) { - LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", 0); - return 0; + + // noop progs? skip + } else if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_PROGNOOP + || bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_ERASENOOP) { + goto progged; + + // progs flipping bits? flip our bad bit, exactly which bit + // is chosen during erase + } else if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_PROGFLIP) { + lfs_size_t bit = b->bad_bit & 0x7fffffff; + if (bit/8 >= off && bit/8 < off+size) { + memcpy(&b->data[off], buffer, size); + b->data[bit/8] ^= 1 << (bit%8); + goto progged; + } + + // reads flipping bits? prog as normal but mark as metastable + } else if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_READFLIP) { + memcpy(&b->data[off], buffer, size); + b->metastable = true; + goto progged; } } // prog data memcpy(&b->data[off], buffer, size); + // clear any metastability + b->metastable = false; + +progged:; // mirror to disk file? if (bd->disk) { off_t res1 = lseek(bd->disk->fd, @@ -599,7 +683,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { if (bd->power_cycles > 0) { bd->power_cycles -= 1; if (bd->power_cycles == 0) { - // if emulating some bits, choose a random bit to flip + // emulating some bits? choose a random bit to flip if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_SOMEBITS) { // mutate the block @@ -614,7 +698,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { // flip bit lfs_size_t bit = lfs_emubd_prng(&bd->prng) % (cfg->block_size*8); - b->data[(bit/8)] ^= (1 << (bit%8)); + b->data[(bit/8)] ^= 1 << (bit%8); // mirror to disk file? if (bd->disk) { @@ -636,7 +720,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { } } - // if emulating most bits, erase data and choose a random bit + // emulating most bits? erase data and choose a random bit // to flip } else if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_MOSTBITS) { @@ -657,7 +741,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { // flip bit lfs_size_t bit = lfs_emubd_prng(&bd->prng) % (cfg->block_size*8); - b->data[(bit/8)] ^= (1 << (bit%8)); + b->data[(bit/8)] ^= 1 << (bit%8); // mirror to disk file? if (bd->disk) { @@ -679,7 +763,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { } } - // if emulating out-of-order writes, revert everything unsynced + // emulating out-of-order writes? revert everything unsynced // except for our current block } else if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_OOO) { @@ -712,6 +796,53 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { } } } + + // emulating metastability? erase data, choose a random bad bit, + // and mark as metastable + } else if (bd->cfg->powerloss_behavior + == LFS_EMUBD_POWERLOSS_METASTABLE) { + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, + bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // emulate an erase value? + if (bd->cfg->erase_value != -1) { + memset(b->data, bd->cfg->erase_value, cfg->block_size); + } + + // choose a new bad bit unless overridden + if (!(0x80000000 & b->bad_bit)) { + b->bad_bit = lfs_emubd_prng(&bd->prng) + % (cfg->block_size*8); + } + + // mark as metastable + b->metastable = true; + + // mirror to disk file? + if (bd->disk) { + off_t res1 = lseek(bd->disk->fd, + (off_t)block*cfg->block_size, + SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", err); + return err; + } + + ssize_t res2 = write(bd->disk->fd, + b->data, cfg->block_size); + if (res2 < 0) { + int err = -errno; + LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", err); + return err; + } + } } // powerloss! @@ -760,21 +891,35 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { } bd->blocks[block] = b; + // keep track of wear + if (bd->cfg->erase_cycles && b->wear <= bd->cfg->erase_cycles) { + b->wear += 1; + } + // block bad? - if (bd->cfg->erase_cycles) { - if (b && b->wear >= bd->cfg->erase_cycles) { - if (bd->cfg->badblock_behavior == - LFS_EMUBD_BADBLOCK_ERASEERROR) { - LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", LFS_ERR_CORRUPT); - return LFS_ERR_CORRUPT; - } else if (bd->cfg->badblock_behavior == - LFS_EMUBD_BADBLOCK_ERASENOOP) { - LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", 0); - return 0; + if (b->wear > bd->cfg->erase_cycles) { + // erroring erases? error + if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_ERASEERROR) { + LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", LFS_ERR_CORRUPT); + return LFS_ERR_CORRUPT; + + // noop erases? skip + } else if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_ERASENOOP) { + goto erased; + + // flipping bits? if we're not manually overridden, choose a + // new bad bit on erase, this makes it more likely to + // eventually cause problems + } else if (bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_PROGFLIP + || bd->cfg->badblock_behavior + == LFS_EMUBD_BADBLOCK_READFLIP) { + if (!(0x80000000 & b->bad_bit)) { + b->bad_bit = lfs_emubd_prng(&bd->prng) + % (cfg->block_size*8); } - } else { - // mark wear - b->wear += 1; } } @@ -802,6 +947,10 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { } } + // clear any metastability + b->metastable = false; + +erased:; // track erases bd->erased += cfg->block_size; if (bd->cfg->erase_sleep) { @@ -839,6 +988,17 @@ int lfs_emubd_sync(const struct lfs_config *cfg) { /// Additional extended API for driving test features /// +int lfs_emubd_seed(const struct lfs_config *cfg, uint32_t seed) { + LFS_EMUBD_TRACE("lfs_emubd_seed(%p, 0x%08"PRIx32")", + (void*)cfg, seed); + lfs_emubd_t *bd = cfg->context; + + bd->prng = seed; + + LFS_EMUBD_TRACE("lfs_emubd_seed -> %d", 0); + return 0; +} + static int lfs_emubd_rawcksum(const struct lfs_config *cfg, lfs_block_t block, uint32_t *cksum) { lfs_emubd_t *bd = cfg->context; @@ -983,6 +1143,149 @@ int lfs_emubd_setwear(const struct lfs_config *cfg, return 0; } +int lfs_emubd_markbad(const struct lfs_config *cfg, + lfs_block_t block) { + LFS_EMUBD_TRACE("lfs_emubd_markbad(%p, %"PRIu32")", + (void*)cfg, block); + lfs_emubd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(block < cfg->block_count); + + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_markbad -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // set the wear + b->wear = -1; + + LFS_EMUBD_TRACE("lfs_emubd_markbad -> %d", 0); + return 0; +} + +int lfs_emubd_markgood(const struct lfs_config *cfg, + lfs_block_t block) { + LFS_EMUBD_TRACE("lfs_emubd_markgood(%p, %"PRIu32")", + (void*)cfg, block); + lfs_emubd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(block < cfg->block_count); + + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_markgood -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // set the wear + b->wear = 0; + + LFS_EMUBD_TRACE("lfs_emubd_markgood -> %d", 0); + return 0; +} + +lfs_ssize_t lfs_emubd_badbit(const struct lfs_config *cfg, + lfs_block_t block) { + LFS_EMUBD_TRACE("lfs_emubd_badbit(%p, %"PRIu32")", (void*)cfg, block); + lfs_emubd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(block < cfg->block_count); + + // get the bad bit + lfs_size_t bad_bit; + const lfs_emubd_block_t *b = bd->blocks[block]; + if (b) { + bad_bit = 0x7fffffff & b->bad_bit; + } else { + bad_bit = 0; + } + + LFS_EMUBD_TRACE("lfs_emubd_badbit -> %"PRIi32, bad_bit); + return bad_bit; +} + +int lfs_emubd_setbadbit(const struct lfs_config *cfg, + lfs_block_t block, lfs_size_t bit) { + LFS_EMUBD_TRACE("lfs_emubd_setbadbit(%p, %"PRIu32", %"PRIu32")", + (void*)cfg, block, bit); + lfs_emubd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(block < cfg->block_count); + + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_setbadbit -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // set the bad bit and mark as fixed + b->bad_bit = 0x80000000 | bit; + + LFS_EMUBD_TRACE("lfs_emubd_setbadbit -> %d", 0); + return 0; +} + +int lfs_emubd_randomizebadbit(const struct lfs_config *cfg, + lfs_block_t block) { + LFS_EMUBD_TRACE("lfs_emubd_randomizebadbit(%p, %"PRIu32")", + (void*)cfg, block); + lfs_emubd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(block < cfg->block_count); + + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_randomizebadbit -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // mark the bad bit as randomized + b->bad_bit &= ~0x80000000; + + LFS_EMUBD_TRACE("lfs_emubd_randomizebadbit -> %d", 0); + return 0; +} + +int lfs_emubd_markbadbit(const struct lfs_config *cfg, + lfs_block_t block, lfs_size_t bit) { + LFS_EMUBD_TRACE("lfs_emubd_markbadbit(%p, %"PRIu32", %"PRIu32")", + (void*)cfg, block, bit); + lfs_emubd_t *bd = cfg->context; + + // check if block is valid + LFS_ASSERT(block < cfg->block_count); + + // mutate the block + lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, bd->blocks[block]); + if (!b) { + LFS_EMUBD_TRACE("lfs_emubd_markbadbit -> %d", LFS_ERR_NOMEM); + return LFS_ERR_NOMEM; + } + bd->blocks[block] = b; + + // set the wear + b->wear = -1; + // set the bad bit and mark as fixed + b->bad_bit = 0x80000000 | bit; + + LFS_EMUBD_TRACE("lfs_emubd_markbadbit -> %d", 0); + return 0; +} + lfs_emubd_spowercycles_t lfs_emubd_powercycles( const struct lfs_config *cfg) { LFS_EMUBD_TRACE("lfs_emubd_powercycles(%p)", (void*)cfg); diff --git a/bd/lfs_emubd.h b/bd/lfs_emubd.h index dfffe08a..aadb843a 100644 --- a/bd/lfs_emubd.h +++ b/bd/lfs_emubd.h @@ -31,25 +31,25 @@ extern "C" // Mode determining how "bad-blocks" behave during testing. This simulates // some real-world circumstances such as progs not sticking (prog-noop), -// a readonly disk (erase-noop), and ECC failures (read-error). -// -// Not that read-noop is not allowed. Read _must_ return a consistent (but -// may be arbitrary) value on every read. +// a readonly disk (erase-noop), ECC failures (read-error), and of course, +// random bit failures (prog-flip, read-flip) typedef enum lfs_emubd_badblock_behavior { - LFS_EMUBD_BADBLOCK_PROGERROR = 0, // Error on prog - LFS_EMUBD_BADBLOCK_ERASEERROR = 1, // Error on erase - LFS_EMUBD_BADBLOCK_READERROR = 2, // Error on read - LFS_EMUBD_BADBLOCK_PROGNOOP = 3, // Prog does nothing silently - LFS_EMUBD_BADBLOCK_ERASENOOP = 4, // Erase does nothing silently + LFS_EMUBD_BADBLOCK_PROGERROR = 0, // Error on prog + LFS_EMUBD_BADBLOCK_ERASEERROR = 1, // Error on erase + LFS_EMUBD_BADBLOCK_READERROR = 2, // Error on read + LFS_EMUBD_BADBLOCK_PROGNOOP = 3, // Prog does nothing silently + LFS_EMUBD_BADBLOCK_ERASENOOP = 4, // Erase does nothing silently + LFS_EMUBD_BADBLOCK_PROGFLIP = 5, // Prog flips a bit + LFS_EMUBD_BADBLOCK_READFLIP = 6, // Read flips a bit sometimes } lfs_emubd_badblock_behavior_t; -// Mode determining how power-loss behaves during testing. For now this -// only supports a noop behavior, leaving the data on-disk untouched. +// Mode determining how power-loss behaves during testing. typedef enum lfs_emubd_powerloss_behavior { - LFS_EMUBD_POWERLOSS_NOOP = 0, // Progs are atomic - LFS_EMUBD_POWERLOSS_SOMEBITS = 1, // One bit is progged - LFS_EMUBD_POWERLOSS_MOSTBITS = 2, // All-but-one bit is progged - LFS_EMUBD_POWERLOSS_OOO = 3, // Blocks are written out-of-order + LFS_EMUBD_POWERLOSS_NOOP = 0, // Progs are atomic + LFS_EMUBD_POWERLOSS_SOMEBITS = 1, // One bit is progged + LFS_EMUBD_POWERLOSS_MOSTBITS = 2, // All-but-one bit is progged + LFS_EMUBD_POWERLOSS_OOO = 3, // Blocks are written out-of-order + LFS_EMUBD_POWERLOSS_METASTABLE = 4, // Reads may flip a bit } lfs_emubd_powerloss_behavior_t; // Type for measuring read/program/erase operations @@ -121,6 +121,10 @@ struct lfs_emubd_config { typedef struct lfs_emubd_block { uint32_t rc; lfs_emubd_wear_t wear; + bool metastable; + // sign(bad_bit)=0 => randomized on erase + // sign(bad_bit)=1 => fixed + lfs_size_t bad_bit; uint8_t data[]; } lfs_emubd_block_t; @@ -186,6 +190,9 @@ int lfs_emubd_sync(const struct lfs_config *cfg); /// Additional extended API for driving test features /// +// Set the current prng state +int lfs_emubd_seed(const struct lfs_config *cfg, uint32_t seed); + // A checksum of a block for debugging purposes int lfs_emubd_cksum(const struct lfs_config *cfg, lfs_block_t block, uint32_t *cksum); @@ -219,6 +226,28 @@ lfs_emubd_swear_t lfs_emubd_wear(const struct lfs_config *cfg, int lfs_emubd_setwear(const struct lfs_config *cfg, lfs_block_t block, lfs_emubd_wear_t wear); +// Mark a block as bad, this is equivalent to setting wear to maximum +int lfs_emubd_markbad(const struct lfs_config *cfg, lfs_block_t block); + +// Clear any simulated wear on a given block +int lfs_emubd_markgood(const struct lfs_config *cfg, lfs_block_t block); + +// Get which bit failed, this changes on erase/power-loss unless manually set +lfs_ssize_t lfs_emubd_badbit(const struct lfs_config *cfg, + lfs_block_t block); + +// Set which bit should fail in a given block +int lfs_emubd_setbadbit(const struct lfs_config *cfg, + lfs_block_t block, lfs_size_t bit); + +// Randomize the bad bit on erase (the default) +int lfs_emubd_randomizebadbit(const struct lfs_config *cfg, + lfs_block_t block); + +// Mark a block as bad and which bit should fail +int lfs_emubd_markbadbit(const struct lfs_config *cfg, + lfs_block_t block, lfs_size_t bit); + // Get the remaining power-cycles lfs_emubd_spowercycles_t lfs_emubd_powercycles( const struct lfs_config *cfg); diff --git a/lfs.c b/lfs.c index 88613b8d..4aff7e50 100644 --- a/lfs.c +++ b/lfs.c @@ -12968,7 +12968,7 @@ static int lfsr_mountinited(lfs_t *lfs) { if (mdir->mid == -1) { // check for the magic string, all mroot should have this lfsr_data_t data; - int err = lfsr_mdir_lookup(lfs, mdir, LFSR_TAG_MAGIC, + err = lfsr_mdir_lookup(lfs, mdir, LFSR_TAG_MAGIC, &data); if (err) { if (err == LFS_ERR_NOENT) { @@ -13274,7 +13274,8 @@ static int lfsr_formatinited(lfs_t *lfs) { } int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) { - int err = lfs_init(lfs, LFS_M_RDWR, cfg); + // TODO hmmm, should lfsr_format take flags? + int err = lfs_init(lfs, LFS_M_RDWR | LFS_M_CKPROGS, cfg); if (err) { return err; } diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index 9aca08ef..bdca735a 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -19,7 +19,6 @@ after = [ # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly [cases.test_badblocks_single_btree_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -27,6 +26,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -43,7 +43,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test creating a btree @@ -124,13 +124,12 @@ code = ''' lfs_deinit(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # with dirs [cases.test_badblocks_single_dir_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -138,6 +137,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -149,7 +149,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test creating directories @@ -234,13 +234,12 @@ code = ''' lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # fuzz dirs [cases.test_badblocks_single_dir_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -248,6 +247,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -262,7 +262,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test fuzz with dirs @@ -415,13 +415,12 @@ code = ''' lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # with files [cases.test_badblocks_single_file_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -429,6 +428,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -450,7 +450,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test creating files @@ -519,13 +519,12 @@ code = ''' lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # fuzz files [cases.test_badblocks_single_file_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -533,6 +532,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -557,7 +557,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test fuzz with files @@ -762,13 +762,12 @@ code = ''' lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # with more complex file writes [cases.test_badblocks_single_fwrite_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -776,6 +775,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -809,7 +809,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test with complex file writes @@ -927,13 +927,12 @@ code = ''' lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # with orphans, zombies, etc [cases.test_badblocks_single_orphanzombie_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -941,6 +940,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -965,7 +965,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test with orphans, zombies, etc @@ -1293,13 +1293,12 @@ code = ''' free(sim_files); lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' # with orphans, zombies, dirs, etc [cases.test_badblocks_single_orphanzombiedir_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -1307,6 +1306,7 @@ defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1331,7 +1331,7 @@ code = ''' i++) { lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; // mark our badblock as bad - lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, badblock) => 0; printf("--- badblock: 0x%x ---\n", badblock); // test with orphans, zombies, dirs, etc @@ -1739,7 +1739,7 @@ code = ''' lfsr_unmount(&lfs) => 0; // reset badblock - lfs_emubd_setwear(CFG, badblock, 0) => 0; + lfs_emubd_markgood(CFG, badblock) => 0; } ''' @@ -1753,13 +1753,13 @@ code = ''' # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly [cases.test_badblocks_region_btree_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1775,9 +1775,9 @@ code = ''' for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } else { - lfs_emubd_setwear(CFG, i + BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i + BLOCK_COUNT/2) => 0; } } @@ -1861,13 +1861,13 @@ code = ''' # with dirs [cases.test_badblocks_region_dir_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1879,11 +1879,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -1972,13 +1972,13 @@ code = ''' # fuzz dirs [cases.test_badblocks_region_dir_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -1993,11 +1993,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -2154,13 +2154,13 @@ code = ''' # with files [cases.test_badblocks_region_file_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2182,11 +2182,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -2259,13 +2259,13 @@ code = ''' # fuzz files [cases.test_badblocks_region_file_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2290,11 +2290,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -2503,13 +2503,13 @@ code = ''' # with more complex file writes [cases.test_badblocks_region_fwrite_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2543,11 +2543,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -2669,13 +2669,13 @@ code = ''' # with orphans, zombies, etc [cases.test_badblocks_region_orphanzombie_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -2700,11 +2700,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -3036,13 +3036,13 @@ code = ''' # with orphans, zombies, dirs, etc [cases.test_badblocks_region_orphanzombiedir_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3067,11 +3067,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (i >= 2) { - lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { - lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } @@ -3488,13 +3488,13 @@ code = ''' # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly [cases.test_badblocks_alternating_btree_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3510,9 +3510,9 @@ code = ''' for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } else { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } @@ -3596,13 +3596,13 @@ code = ''' # with dirs [cases.test_badblocks_alternating_dir_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3614,11 +3614,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -3707,13 +3707,13 @@ code = ''' # fuzz dirs [cases.test_badblocks_alternating_dir_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3728,11 +3728,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -3889,13 +3889,13 @@ code = ''' # with files [cases.test_badblocks_alternating_file_many] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -3917,11 +3917,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -3994,13 +3994,13 @@ code = ''' # fuzz files [cases.test_badblocks_alternating_file_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4025,11 +4025,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -4238,13 +4238,13 @@ code = ''' # with more complex file writes [cases.test_badblocks_alternating_fwrite_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4278,11 +4278,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -4402,13 +4402,13 @@ code = ''' # with orphans, zombies, etc [cases.test_badblocks_alternating_orphanzombie_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4433,11 +4433,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -4769,13 +4769,13 @@ code = ''' # with orphans, zombies, dirs, etc [cases.test_badblocks_alternating_orphanzombiedir_fuzz] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', 'LFS_EMUBD_BADBLOCK_ERASEERROR', 'LFS_EMUBD_BADBLOCK_READERROR', 'LFS_EMUBD_BADBLOCK_PROGNOOP', 'LFS_EMUBD_BADBLOCK_ERASENOOP', + 'LFS_EMUBD_BADBLOCK_PROGFLIP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS_EMUBD_BADBLOCK_READERROR' @@ -4800,11 +4800,11 @@ code = ''' // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { - lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { - lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 2*i+1) => 0; } } } @@ -5218,7 +5218,6 @@ code = ''' # test formatting with 0 or 1 bad, this should just error [cases.test_badblocks_mrootanchor_format] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCKS = [0x1, 0x2, 0x3] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -5229,10 +5228,10 @@ defines.BADBLOCK_BEHAVIOR = [ ] code = ''' if (BADBLOCKS & 0x1) { - lfs_emubd_setwear(CFG, 0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 0) => 0; } if (BADBLOCKS & 0x2) { - lfs_emubd_setwear(CFG, 1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 1) => 0; } lfs_t lfs; @@ -5241,7 +5240,6 @@ code = ''' # test blocks 0 or 1 going bad, this should just error [cases.test_badblocks_mrootanchor_wear] -defines.ERASE_CYCLES = 0xffffffff defines.BADBLOCKS = [0x1, 0x2] defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGERROR', @@ -5257,10 +5255,10 @@ code = ''' lfsr_format(&lfs, CFG) => 0; if (BADBLOCKS & 0x1) { - lfs_emubd_setwear(CFG, 0, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 0) => 0; } if (BADBLOCKS & 0x2) { - lfs_emubd_setwear(CFG, 1, 0xffffffff) => 0; + lfs_emubd_markbad(CFG, 1) => 0; } lfsr_mount(&lfs, diff --git a/tests/test_ck.toml b/tests/test_ck.toml index 0beaa5d6..f5c44d64 100644 --- a/tests/test_ck.toml +++ b/tests/test_ck.toml @@ -104,7 +104,7 @@ code = ''' for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); - + struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_CORRUPT); @@ -233,7 +233,7 @@ code = ''' for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); - + struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_CORRUPT); @@ -462,3 +462,711 @@ code = ''' } done:; ''' + + + +# Some simple ckprog tests +# +# We test these much more aggressively in test_badblocks + +# test every single-bit error in block 0/1 +[cases.test_ck_ckprogs_mroot] +defines.BADBLOCK = [0, 1] +defines.BADBIT = -1 +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +# this should stay inlined +defines.SIZE = 'BLOCK_SIZE/16' +code = ''' + // test all bad bits in the mroot + for (lfs_size_t i = 0; + i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); + i++) { + lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + + // mark our badbit as bad + lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); + + // formatting the filesystem may already find the bit error + lfs_t lfs; + int err = lfsr_format(&lfs, CFG); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto corrupt; + } + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + + { + // create a file + lfsr_file_t file; + err = lfsr_file_open(&lfs, &file, "stygiomedusa", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + err = lfsr_file_close(&lfs, &file); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + + // if we made it here without erroring we should be able to + // read our file + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + } + + lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + corrupt_mounted:; + lfsr_unmount(&lfs) => 0; + + corrupt:; + // reset badbit + lfs_emubd_markgood(CFG, BADBLOCK) => 0; + } +''' + +# test every single-bit error in a file's data block +[cases.test_ck_ckprogs_data] +defines.BADBIT = -1 +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +# this should create a single block file +defines.SIZE = 'BLOCK_SIZE' +code = ''' + // first we need to figure out where the data block will actually + // end up, fortunately our block randomization is intentionally + // consistent + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "stygiomedusa", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // find the data block + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t badblock; + while (true) { + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + if (tinfo.btype == LFS_BTYPE_DATA) { + badblock = tinfo.block; + break; + } + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + + // now test all bad bits in the data block + for (lfs_size_t i = 0; + i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); + i++) { + lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + + // mark our badbit as bad + lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + + { + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "stygiomedusa", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + int err = lfsr_file_close(&lfs, &file); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + + // if we made it here without erroring we should be able to + // read our file + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + } + + lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + corrupt_mounted:; + lfsr_unmount(&lfs) => 0; + + // reset badbit + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# test every single-bit error in a file's btree node +[cases.test_ck_ckprogs_btree] +defines.BADBIT = -1 +defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' +# force the file to create a btree +defines.INLINE_SIZE = 0 +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = '2*FRAGMENT_SIZE' +code = ''' + // first we need to figure out where the btree block will actually + // end up, fortunately our block randomization is intentionally + // consistent + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "stygiomedusa", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // find the btree block + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t badblock; + while (true) { + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + if (tinfo.btype == LFS_BTYPE_BTREE) { + badblock = tinfo.block; + break; + } + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + + // now test all bad bits in the btree block + for (lfs_size_t i = 0; + i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); + i++) { + lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + + // mark our badbit as bad + lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + + { + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "stygiomedusa", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + int err = lfsr_file_close(&lfs, &file); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + + // if we made it here without erroring we should be able to + // read our file + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; + } + + lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + corrupt_mounted:; + lfsr_unmount(&lfs) => 0; + + // reset badbit + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + + + +# Some simple ckread tests +# +# We test these much more aggressively in test_badblocks + +# These tests were originally intended to test all single-bit +# metastability errors with ckreads, however they quickly found that +# ckreads can't actually guarantee single-bit error-detection since +# the bit flip may alter the leb128 encoded size field and find a new, +# erronous, parity bit. +# +# For example, one bit flip: +# +# 40 0c 00 12 80 0d ff ff +# '----.----' ^--------------------. +# '- altble 0xc w0 -18 parity=1 +# +# 40 0c 80 12 80 0d ff ff +# '-------.-------' ^----------------------. +# '- altble 0xc w2304 -1664 parity=1 +# +# This doesn't make ckreads _completely_ useless, just mostly useless. +# We can still use it to check parity bits, but without a systematic +# proof. +# +# So for now these tests are sort of in stasis, limited to testing +# metastability in areas we know we can detect (revision counts, raw +# data blocks, etc). Maybe future features will make them more useful. +# + +# test every single-bit error in block 0/1 +[cases.test_ck_ckreads_mroot] +defines.BADBLOCK = [0, 1] +defines.BADBIT = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGFLIP', + 'LFS_EMUBD_BADBLOCK_READFLIP', +] +# this should stay inlined +defines.SIZE = 'BLOCK_SIZE/16' +code = ''' + // test all bad bits in the mroot + for (lfs_size_t i = 0; + // we can't detect metastable tags, so limit read-flips + // to our revision count + i < ((BADBIT == -1) ? 8*4 : 1); + i++) { + lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42) => 0; + + // mark our badbit as bad + lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); + + // With metastability, basically any filesystem operation can + // return LFS_ERR_CORRUPT. This is ok, what we're really testing + // for is no internal/external asserts failing. + + // format + lfs_t lfs; + int err = lfsr_format(&lfs, CFG); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto corrupt; + } + err = lfsr_mount(&lfs, LFS_M_RDWR, CFG); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto corrupt; + } + + { + // create a file + lfsr_file_t file; + err = lfsr_file_open(&lfs, &file, "bathykorus", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + err = lfsr_file_close(&lfs, &file); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + + // try to read our file + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + err = lfsr_mount(&lfs, LFS_M_RDWR, CFG); + if (err == LFS_ERR_CORRUPT) { + goto corrupt; + } + } + + // yes reads can fail here + err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY); + assert(!err + || err == LFS_ERR_CORRUPT + // metastability can also cause our fs state to "rollback", + // which is not great but we can't solve this with ckreads + // alone + || err == LFS_ERR_NOENT); + if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) { + goto corrupt_mounted; + } + uint8_t rbuf[SIZE]; + lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + corrupt_mounted:; + lfsr_unmount(&lfs) => 0; + + corrupt:; + // reset badbit + lfs_emubd_markgood(CFG, BADBLOCK) => 0; + } +''' + +# test every single-bit error in a file's data block +[cases.test_ck_ckreads_data] +defines.BADBIT = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGFLIP', + 'LFS_EMUBD_BADBLOCK_READFLIP', +] +# this should create a single block file +defines.SIZE = 'BLOCK_SIZE' +code = ''' + // first we need to figure out where the data block will actually + // end up, fortunately our block randomization is intentionally + // consistent + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "bathykorus", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // find the data block + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t badblock; + while (true) { + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + if (tinfo.btype == LFS_BTYPE_DATA) { + badblock = tinfo.block; + break; + } + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + + // now test all bad bits in the data block + for (lfs_size_t i = 0; + i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); + i++) { + lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42) => 0; + + // mark our badbit as bad + lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); + + // With metastability, basically any filesystem operation can + // return LFS_ERR_CORRUPT. This is ok, what we're really testing + // for is no internal/external asserts failing. + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + { + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "bathykorus", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + int err = lfsr_file_close(&lfs, &file); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + + // try to read our file + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + } + + // yes reads can fail here + err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY); + assert(!err + || err == LFS_ERR_CORRUPT + // metastability can also cause our fs state to "rollback", + // which is not great but we can't solve this with ckreads + // alone + || err == LFS_ERR_NOENT); + if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) { + goto corrupt_mounted; + } + uint8_t rbuf[SIZE]; + lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + corrupt_mounted:; + lfsr_unmount(&lfs) => 0; + + // reset badbit + lfs_emubd_markgood(CFG, badblock) => 0; + } +''' + +# test every single-bit error in a file's btree node +[cases.test_ck_ckreads_btree] +defines.BADBIT = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGFLIP', + 'LFS_EMUBD_BADBLOCK_READFLIP', +] +# force the file to create a btree +defines.INLINE_SIZE = 0 +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = '2*FRAGMENT_SIZE' +code = ''' + // first we need to figure out where the btree block will actually + // end up, fortunately our block randomization is intentionally + // consistent + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "bathykorus", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // find the btree block + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t badblock; + while (true) { + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + if (tinfo.btype == LFS_BTYPE_BTREE) { + badblock = tinfo.block; + break; + } + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + + // now test all bad bits in the btree block + for (lfs_size_t i = 0; + // we can't detect metastable tags, so limit read-flips + // to our revision count + i < ((BADBIT == -1) ? 8*4 : 1); + i++) { + lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; + + // reset the bd prng every run for reproducibility + lfs_emubd_seed(CFG, 42) => 0; + + // mark our badbit as bad + lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; + printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", + badblock, badbit/8, badbit, badbit/8, badbit%8); + + // With metastability, basically any filesystem operation can + // return LFS_ERR_CORRUPT. This is ok, what we're really testing + // for is no internal/external asserts failing. + + // format + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + { + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "bathykorus", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint32_t prng = 42; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + int err = lfsr_file_close(&lfs, &file); + if (err == LFS_ERR_CORRUPT) { + goto corrupt_mounted; + } + + // try to read our file + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + } + + // yes reads can fail here + err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY); + assert(!err + || err == LFS_ERR_CORRUPT + // metastability can also cause our fs state to "rollback", + // which is not great but we can't solve this with ckreads + // alone + || err == LFS_ERR_NOENT); + if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) { + goto corrupt_mounted; + } + uint8_t rbuf[SIZE]; + lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE); + assert(res == SIZE || res == LFS_ERR_CORRUPT); + if (res == LFS_ERR_CORRUPT) { + lfsr_file_close(&lfs, &file) => 0; + goto corrupt_mounted; + } + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + corrupt_mounted:; + lfsr_unmount(&lfs) => 0; + + // reset badbit + lfs_emubd_markgood(CFG, badblock) => 0; + } +'''