Added out-of-order write testing to emubd

Some forms of storage, mainly anything with an FTL, eMMC, SD, etc, do
not guarantee a strict write order for writes to different blocks. It
would be good to test that this doesn't break littlefs.

This adds LFS_EMUBD_POWERLOSS_OOO to lfs_emubd, which tells lfs_emubd to
try to break any order-dependent code on powerloss.

The behavior right now is a bit simple, but does result in test
breakage:

1. Save the state of the block on first write (erase really) after
   sync/init.

2. On powerloss, revert the first write to its original state.

This might be a bit confusing when debugging, since the block will
appear to time-travel, but doing anything fancier would make emubd quite
a bit more complicated.

You could also get a bit fancier with which/how many blocks to revert,
but this should at least be sufficient to make sure bd sync calls are in
the right place.
This commit is contained in:
Christopher Haster
2024-02-27 12:58:07 -06:00
parent f53a0cc961
commit f2a6f45eef
9 changed files with 125 additions and 13 deletions

View File

@@ -129,6 +129,8 @@ int lfs_emubd_create(const struct lfs_config *cfg,
bd->proged = 0; bd->proged = 0;
bd->erased = 0; bd->erased = 0;
bd->power_cycles = bd->cfg->power_cycles; bd->power_cycles = bd->cfg->power_cycles;
bd->ooo_block = -1;
bd->ooo_data = NULL;
bd->disk = NULL; bd->disk = NULL;
if (bd->cfg->disk_path) { if (bd->cfg->disk_path) {
@@ -195,6 +197,7 @@ int lfs_emubd_destroy(const struct lfs_config *cfg) {
free(bd->blocks); free(bd->blocks);
// clean up other resources // clean up other resources
lfs_emubd_decblock(bd->ooo_data);
if (bd->disk) { if (bd->disk) {
bd->disk->rc -= 1; bd->disk->rc -= 1;
if (bd->disk->rc == 0) { if (bd->disk->rc == 0) {
@@ -209,6 +212,47 @@ int lfs_emubd_destroy(const struct lfs_config *cfg) {
} }
// powerloss hook
static int lfs_emubd_powerloss(const struct lfs_config *cfg) {
lfs_emubd_t *bd = cfg->context;
// emulate out-of-order writes?
if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_OOO
&& bd->ooo_block != -1) {
// since writes between syncs are allowed to be out-of-order, it
// shouldn't hurt to restore the first write on powerloss, right?
lfs_emubd_decblock(bd->blocks[bd->ooo_block]);
bd->blocks[bd->ooo_block] = bd->ooo_data;
// mirror to disk file?
if (bd->disk && (bd->ooo_data || bd->cfg->erase_value != -1)) {
off_t res1 = lseek(bd->disk->fd,
(off_t)bd->ooo_block*bd->cfg->erase_size,
SEEK_SET);
if (res1 < 0) {
return -errno;
}
ssize_t res2 = write(bd->disk->fd,
(bd->ooo_data)
? bd->ooo_data->data
: bd->disk->scratch,
bd->cfg->erase_size);
if (res2 < 0) {
return -errno;
}
}
bd->ooo_block = -1;
bd->ooo_data = NULL;
}
// simulate power loss
bd->cfg->powerloss_cb(bd->cfg->powerloss_data);
return 0;
}
// block device API // block device API
@@ -344,8 +388,11 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
if (bd->power_cycles > 0) { if (bd->power_cycles > 0) {
bd->power_cycles -= 1; bd->power_cycles -= 1;
if (bd->power_cycles == 0) { if (bd->power_cycles == 0) {
// simulate power loss int err = lfs_emubd_powerloss(cfg);
bd->cfg->powerloss_cb(bd->cfg->powerloss_data); if (err) {
LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", err);
return err;
}
} }
} }
@@ -361,10 +408,17 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
// check if erase is valid // check if erase is valid
LFS_ASSERT(block < bd->cfg->erase_count); LFS_ASSERT(block < bd->cfg->erase_count);
// emulate out-of-order writes? save first write
if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_OOO
&& bd->ooo_block == -1) {
bd->ooo_block = block;
bd->ooo_data = lfs_emubd_incblock(bd->blocks[block]);
}
// get the block // get the block
lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]); lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]);
if (!b) { if (!b) {
LFS_EMUBD_TRACE("lfs_emubd_prog -> %d", LFS_ERR_NOMEM); LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
@@ -430,8 +484,11 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
if (bd->power_cycles > 0) { if (bd->power_cycles > 0) {
bd->power_cycles -= 1; bd->power_cycles -= 1;
if (bd->power_cycles == 0) { if (bd->power_cycles == 0) {
// simulate power loss int err = lfs_emubd_powerloss(cfg);
bd->cfg->powerloss_cb(bd->cfg->powerloss_data); if (err) {
LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", err);
return err;
}
} }
} }
@@ -441,14 +498,20 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
int lfs_emubd_sync(const struct lfs_config *cfg) { int lfs_emubd_sync(const struct lfs_config *cfg) {
LFS_EMUBD_TRACE("lfs_emubd_sync(%p)", (void*)cfg); LFS_EMUBD_TRACE("lfs_emubd_sync(%p)", (void*)cfg);
lfs_emubd_t *bd = cfg->context;
// do nothing // emulate out-of-order writes? reset first write, writes
(void)cfg; // cannot be out-of-order across sync
if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_OOO) {
bd->ooo_block = -1;
bd->ooo_data = NULL;
}
LFS_EMUBD_TRACE("lfs_emubd_sync -> %d", 0); LFS_EMUBD_TRACE("lfs_emubd_sync -> %d", 0);
return 0; return 0;
} }
/// Additional extended API for driving test features /// /// Additional extended API for driving test features ///
static int lfs_emubd_crc_(const struct lfs_config *cfg, static int lfs_emubd_crc_(const struct lfs_config *cfg,
@@ -633,6 +696,8 @@ int lfs_emubd_copy(const struct lfs_config *cfg, lfs_emubd_t *copy) {
copy->proged = bd->proged; copy->proged = bd->proged;
copy->erased = bd->erased; copy->erased = bd->erased;
copy->power_cycles = bd->power_cycles; copy->power_cycles = bd->power_cycles;
copy->ooo_block = bd->ooo_block;
copy->ooo_data = lfs_emubd_incblock(bd->ooo_data);
copy->disk = bd->disk; copy->disk = bd->disk;
if (copy->disk) { if (copy->disk) {
copy->disk->rc += 1; copy->disk->rc += 1;

View File

@@ -36,17 +36,18 @@ extern "C"
// Not that read-noop is not allowed. Read _must_ return a consistent (but // Not that read-noop is not allowed. Read _must_ return a consistent (but
// may be arbitrary) value on every read. // may be arbitrary) value on every read.
typedef enum lfs_emubd_badblock_behavior { typedef enum lfs_emubd_badblock_behavior {
LFS_EMUBD_BADBLOCK_PROGERROR, LFS_EMUBD_BADBLOCK_PROGERROR = 0, // Error on prog
LFS_EMUBD_BADBLOCK_ERASEERROR, LFS_EMUBD_BADBLOCK_ERASEERROR = 1, // Error on erase
LFS_EMUBD_BADBLOCK_READERROR, LFS_EMUBD_BADBLOCK_READERROR = 2, // Error on read
LFS_EMUBD_BADBLOCK_PROGNOOP, LFS_EMUBD_BADBLOCK_PROGNOOP = 3, // Prog does nothing silently
LFS_EMUBD_BADBLOCK_ERASENOOP, LFS_EMUBD_BADBLOCK_ERASENOOP = 4, // Erase does nothing silently
} lfs_emubd_badblock_behavior_t; } lfs_emubd_badblock_behavior_t;
// Mode determining how power-loss behaves during testing. For now this // Mode determining how power-loss behaves during testing. For now this
// only supports a noop behavior, leaving the data on-disk untouched. // only supports a noop behavior, leaving the data on-disk untouched.
typedef enum lfs_emubd_powerloss_behavior { typedef enum lfs_emubd_powerloss_behavior {
LFS_EMUBD_POWERLOSS_NOOP, LFS_EMUBD_POWERLOSS_NOOP = 0, // Progs are atomic
LFS_EMUBD_POWERLOSS_OOO = 1, // Blocks are written out-of-order
} lfs_emubd_powerloss_behavior_t; } lfs_emubd_powerloss_behavior_t;
// Type for measuring read/program/erase operations // Type for measuring read/program/erase operations
@@ -152,6 +153,8 @@ typedef struct lfs_emubd {
lfs_emubd_io_t proged; lfs_emubd_io_t proged;
lfs_emubd_io_t erased; lfs_emubd_io_t erased;
lfs_emubd_powercycles_t power_cycles; lfs_emubd_powercycles_t power_cycles;
lfs_ssize_t ooo_block;
lfs_emubd_block_t *ooo_data;
lfs_emubd_disk_t *disk; lfs_emubd_disk_t *disk;
const struct lfs_emubd_config *cfg; const struct lfs_emubd_config *cfg;

View File

@@ -181,6 +181,10 @@ code = '''
defines.N = [5, 11] defines.N = [5, 11]
if = 'BLOCK_COUNT >= 4*N' if = 'BLOCK_COUNT >= 4*N'
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);
@@ -439,6 +443,10 @@ code = '''
defines.N = [5, 25] defines.N = [5, 25]
if = 'N < BLOCK_COUNT/2' if = 'N < BLOCK_COUNT/2'
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);

View File

@@ -310,6 +310,10 @@ defines.SIZE = [32, 0, 7, 2049]
defines.CHUNKSIZE = [31, 16, 65] defines.CHUNKSIZE = [31, 16, 65]
defines.INLINE_MAX = [0, -1, 8] defines.INLINE_MAX = [0, -1, 8]
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);
@@ -500,6 +504,10 @@ code = '''
[cases.test_files_many_power_loss] [cases.test_files_many_power_loss]
defines.N = 300 defines.N = 300
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);

View File

@@ -195,6 +195,10 @@ code = '''
defines.SIZE = [10, 100] defines.SIZE = [10, 100]
defines.FILES = [4, 10, 26] defines.FILES = [4, 10, 26]
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
lfs_file_t files[FILES]; lfs_file_t files[FILES];

View File

@@ -357,6 +357,10 @@ code = '''
[cases.test_move_reentrant_file] [cases.test_move_reentrant_file]
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);
@@ -839,6 +843,10 @@ code = '''
[cases.test_reentrant_dir] [cases.test_reentrant_dir]
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);

View File

@@ -329,6 +329,10 @@ code = '''
# must be power-of-2 for quadratic probing to be exhaustive # must be power-of-2 for quadratic probing to be exhaustive
defines.COUNT = [4, 64, 128] defines.COUNT = [4, 64, 128]
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);

View File

@@ -32,6 +32,10 @@ code = '''
# reentrant format # reentrant format
[cases.test_superblocks_reentrant_format] [cases.test_superblocks_reentrant_format]
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);
@@ -174,6 +178,10 @@ code = '''
defines.BLOCK_CYCLES = [2, 1] defines.BLOCK_CYCLES = [2, 1]
defines.N = 24 defines.N = 24
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);

View File

@@ -231,6 +231,10 @@ defines.SMALLSIZE = [4, 512]
defines.MEDIUMSIZE = [0, 3, 4, 5, 31, 32, 33, 511, 512, 513, 1023, 1024, 1025] defines.MEDIUMSIZE = [0, 3, 4, 5, 31, 32, 33, 511, 512, 513, 1023, 1024, 1025]
defines.LARGESIZE = 2048 defines.LARGESIZE = 2048
reentrant = true reentrant = true
defines.POWERLOSS_BEHAVIOR = [
'LFS_EMUBD_POWERLOSS_NOOP',
'LFS_EMUBD_POWERLOSS_OOO',
]
code = ''' code = '''
lfs_t lfs; lfs_t lfs;
int err = lfs_mount(&lfs, cfg); int err = lfs_mount(&lfs, cfg);