emubd: Added optional nor-masking emulation

This adds NOR-style masking emulation to emubd when erase_value is set
to -2:

  erase     => 0xff
  prog 0xf0 => 0xf0
  prog 0xcc => 0xc0

We do _not_ rely on this property in littlefs, and so this feature will
probably go unused in our tests, but it's useful for running other
filesystems (SPIFFS) on top of emubd.

It may be a bit of a scope violation to merge this into littlefs's core
repo, but it's useful to centralize emubd's features somewhere...
This commit is contained in:
Christopher Haster
2025-08-06 13:30:14 -05:00
parent 6a57258558
commit 92af5de3ca
2 changed files with 71 additions and 22 deletions

View File

@ -24,9 +24,61 @@
#endif
// low-level flash memory emulation
// read data
static inline void lfs3_emubd_memread(const struct lfs3_cfg *cfg,
void *restrict dst, const void *restrict src, size_t size) {
(void)cfg;
memcpy(dst, src, size);
}
static inline void lfs3_emubd_memprog(const struct lfs3_cfg *cfg,
void *restrict dst, const void *restrict src, size_t size) {
lfs3_emubd_t *bd = cfg->context;
// emulating nor-masking?
if (bd->cfg->erase_value == -2) {
uint8_t *dst_ = dst;
const uint8_t *src_ = src;
for (size_t i = 0; i < size; i++) {
dst_[i] &= src_[i];
}
} else {
memcpy(dst, src, size);
}
}
static inline void lfs3_emubd_memerase(const struct lfs3_cfg *cfg,
void *restrict dst, size_t size) {
lfs3_emubd_t *bd = cfg->context;
// emulating erase value?
if (bd->cfg->erase_value != -1) {
memset(dst,
(bd->cfg->erase_value >= 0)
? bd->cfg->erase_value
: 0xff,
size);
}
}
// this is slightly different from lfs3_emubd_memerase in that we use
// lfs3_emubd_memzero when we need to unconditionally zero memory
static inline void lfs3_emubd_memzero(const struct lfs3_cfg *cfg,
void *restrict dst, size_t size) {
lfs3_emubd_t *bd = cfg->context;
memset(dst,
(bd->cfg->erase_value == -1) ? 0
: (bd->cfg->erase_value >= 0) ? bd->cfg->erase_value
: (bd->cfg->erase_value == -2) ? 0xff
: 0,
size);
}
// access to lazily-allocated/copy-on-write blocks
//
// Note we can only modify a block if we have exclusive access to it (rc == 1)
// note we can only modify a block if we have exclusive access to
// it (rc == 1)
//
static lfs3_emubd_block_t *lfs3_emubd_incblock(lfs3_emubd_block_t *block) {
@ -81,10 +133,7 @@ static lfs3_emubd_block_t *lfs3_emubd_mutblock(
block_->bad_bit = 0;
// zero for consistency
lfs3_emubd_t *bd = cfg->context;
memset(block_->data,
(bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0,
cfg->block_size);
lfs3_emubd_memzero(cfg, block_->data, cfg->block_size);
return block_;
}
@ -203,9 +252,7 @@ int lfs3_emubd_createcfg(const struct lfs3_cfg *cfg, const char *path,
err = LFS3_ERR_NOMEM;
goto failed;
}
memset(bd->disk->scratch,
(bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0,
cfg->block_size);
lfs3_emubd_memzero(cfg, bd->disk->scratch, cfg->block_size);
// go ahead and erase all of the disk, otherwise the file will not
// match our internal representation
@ -324,7 +371,7 @@ int lfs3_emubd_read(const struct lfs3_cfg *cfg, lfs3_block_t block,
}
// read data
memcpy(buffer, &b->data[off], size);
lfs3_emubd_memread(cfg, buffer, &b->data[off], size);
// metastable? randomly decide if our bad bit flips
if (b->metastable) {
@ -339,9 +386,7 @@ int lfs3_emubd_read(const struct lfs3_cfg *cfg, lfs3_block_t block,
// no block yet
} else {
// zero for consistency
memset(buffer,
(bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0,
size);
lfs3_emubd_memzero(cfg, buffer, size);
}
// track reads
@ -377,7 +422,7 @@ int lfs3_emubd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
// were we erased properly?
LFS3_ASSERT(bd->blocks[block]);
if (bd->cfg->erase_value != -1
if (bd->cfg->erase_value >= 0
&& bd->blocks[block]->wear <= bd->cfg->erase_cycles) {
for (lfs3_off_t i = 0; i < size; i++) {
LFS3_ASSERT(bd->blocks[block]->data[off+i] == bd->cfg->erase_value);
@ -438,7 +483,7 @@ int lfs3_emubd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
bd->blocks[block] = b;
// prog data
memcpy(&b->data[off], buffer, size);
lfs3_emubd_memprog(cfg, &b->data[off], buffer, size);
// flip bit
lfs3_size_t bit = lfs3_emubd_prng_(&bd->prng)
@ -515,7 +560,7 @@ int lfs3_emubd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
bd->blocks[block] = b;
// prog data
memcpy(&b->data[off], buffer, size);
lfs3_emubd_memprog(cfg, &b->data[off], buffer, size);
// choose a new bad bit unless overridden
if (!(0x80000000 & b->bad_bit)) {
@ -613,7 +658,8 @@ int lfs3_emubd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
== LFS3_EMUBD_BADBLOCK_PROGFLIP) {
lfs3_size_t bit = b->bad_bit & 0x7fffffff;
if (bit/8 >= off && bit/8 < off+size) {
memcpy(&b->data[off], buffer, size);
// prog data
lfs3_emubd_memprog(cfg, &b->data[off], buffer, size);
b->data[bit/8] ^= 1 << (bit%8);
goto progged;
}
@ -621,14 +667,15 @@ int lfs3_emubd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
// reads flipping bits? prog as normal but mark as metastable
} else if (bd->cfg->badblock_behavior
== LFS3_EMUBD_BADBLOCK_READFLIP) {
memcpy(&b->data[off], buffer, size);
// prog data
lfs3_emubd_memprog(cfg, &b->data[off], buffer, size);
b->metastable = true;
goto progged;
}
}
// prog data
memcpy(&b->data[off], buffer, size);
lfs3_emubd_memprog(cfg, &b->data[off], buffer, size);
// clear any metastability
b->metastable = false;
@ -735,7 +782,7 @@ int lfs3_emubd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block) {
// emulate an erase value?
if (bd->cfg->erase_value != -1) {
memset(b->data, bd->cfg->erase_value, cfg->block_size);
lfs3_emubd_memerase(cfg, b->data, cfg->block_size);
}
// flip bit
@ -812,7 +859,7 @@ int lfs3_emubd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block) {
// emulate an erase value?
if (bd->cfg->erase_value != -1) {
memset(b->data, bd->cfg->erase_value, cfg->block_size);
lfs3_emubd_memerase(cfg, b->data, cfg->block_size);
}
// choose a new bad bit unless overridden
@ -922,7 +969,7 @@ int lfs3_emubd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block) {
// emulate an erase value?
if (bd->cfg->erase_value != -1) {
memset(b->data, bd->cfg->erase_value, cfg->block_size);
lfs3_emubd_memerase(cfg, b->data, cfg->block_size);
// mirror to disk file?
if (bd->disk) {

View File

@ -67,7 +67,9 @@ typedef int64_t lfs3_emubd_ssleep_t;
// emubd config, this is required for testing
struct lfs3_emubd_cfg {
// 8-bit erase value to use for simulating erases. -1 simulates a noop
// erase, which is faster than simulating a fixed erase value.
// erase, which is faster than simulating a fixed erase value. -2 emulates
// nor-masking, which is useful for testing other filesystems (littlefs
// does _not_ rely on this!).
int32_t erase_value;
// Number of erase cycles before a block becomes "bad". The exact behavior