Cherry-picked upstream out-of-order emubd testing

More information upstream (f2a6f45, fc2aa33, 7873d81), but this adds
LFS_EMUBD_POWERLOS_OOO for testing out-of-order block devices that
require sync to be called for things to serialize. It's a simple
implementation, just reverts the first write since last sync on
powerloss, but gets the job done.

Cherry-picking these changes required reverting emubd's scratch buffer,
but carrying around an extra ~block_size of memory isn't a big deal
here.
This commit is contained in:
Christopher Haster
2024-02-27 12:58:07 -06:00
parent 1ecb346cec
commit b5370d6001
5 changed files with 153 additions and 46 deletions
+139 -34
View File
@@ -116,28 +116,34 @@ int lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
lfs_emubd_t *bd = cfg->context; lfs_emubd_t *bd = cfg->context;
bd->cfg = bdcfg; bd->cfg = bdcfg;
// allocate our block array, all blocks start as uninitialized
bd->blocks = malloc(cfg->block_count * sizeof(lfs_emubd_block_t*));
if (!bd->blocks) {
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
}
memset(bd->blocks, 0, cfg->block_count * sizeof(lfs_emubd_block_t*));
// setup testing things // setup testing things
bd->blocks = NULL;
bd->readed = 0; bd->readed = 0;
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;
// allocate our block array, all blocks start as uninitialized
bd->blocks = malloc(cfg->block_count * sizeof(lfs_emubd_block_t*));
int err;
if (!bd->blocks) {
err = LFS_ERR_NOMEM;
goto failed;
}
memset(bd->blocks, 0, cfg->block_count * sizeof(lfs_emubd_block_t*));
if (bd->cfg->disk_path) { if (bd->cfg->disk_path) {
bd->disk = malloc(sizeof(lfs_emubd_disk_t)); bd->disk = malloc(sizeof(lfs_emubd_disk_t));
if (!bd->disk) { if (!bd->disk) {
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM); err = LFS_ERR_NOMEM;
return LFS_ERR_NOMEM; goto failed;
} }
bd->disk->rc = 1; bd->disk->rc = 1;
bd->disk->fd = -1;
bd->disk->scratch = NULL;
#ifdef _WIN32 #ifdef _WIN32
bd->disk->fd = open(bd->cfg->disk_path, bd->disk->fd = open(bd->cfg->disk_path,
@@ -147,37 +153,47 @@ int lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
O_RDWR | O_CREAT, 0666); O_RDWR | O_CREAT, 0666);
#endif #endif
if (bd->disk->fd < 0) { if (bd->disk->fd < 0) {
int err = -errno; err = -errno;
LFS_EMUBD_TRACE("lfs_emubd_create -> %d", err); goto failed;
return err;
} }
// go ahead and erase all of the disk, otherwise the file will not bd->disk->scratch = malloc(cfg->block_size);
// match our internal representation if (!bd->disk->scratch) {
uint8_t *scratch = malloc(cfg->block_size); err = LFS_ERR_NOMEM;
if (!scratch) { goto failed;
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
} }
memset(scratch, memset(bd->disk->scratch,
(bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0, (bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0,
cfg->block_size); cfg->block_size);
// go ahead and erase all of the disk, otherwise the file will not
// match our internal representation
for (size_t i = 0; i < cfg->block_count; i++) { for (size_t i = 0; i < cfg->block_count; i++) {
ssize_t res = write(bd->disk->fd, scratch, cfg->block_size); ssize_t res = write(bd->disk->fd,
bd->disk->scratch,
cfg->block_size);
if (res < 0) { if (res < 0) {
int err = -errno; err = -errno;
free(scratch); goto failed;
LFS_EMUBD_TRACE("lfs_emubd_create -> %d", err);
return err;
} }
} }
free(scratch);
} }
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", 0); LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", 0);
return 0; return 0;
failed:;
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", err);
// clean up memory
free(bd->blocks);
if (bd->disk) {
if (bd->disk->fd != -1) {
close(bd->disk->fd);
}
free(bd->disk->scratch);
free(bd->disk);
}
return err;
} }
int lfs_emubd_create(const struct lfs_config *cfg, const char *path) { int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
@@ -208,10 +224,12 @@ 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) {
close(bd->disk->fd); close(bd->disk->fd);
free(bd->disk->scratch);
free(bd->disk); free(bd->disk);
} }
} }
@@ -221,6 +239,71 @@ 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?
lfs_emubd_block_t *ooo_data = NULL;
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?
ooo_data = bd->blocks[bd->ooo_block];
bd->blocks[bd->ooo_block] = lfs_emubd_incblock(bd->ooo_data);
// mirror to disk file?
if (bd->disk) {
off_t res1 = lseek(bd->disk->fd,
(off_t)bd->ooo_block*cfg->block_size,
SEEK_SET);
if (res1 < 0) {
return -errno;
}
ssize_t res2 = write(bd->disk->fd,
(bd->blocks[bd->ooo_block])
? bd->blocks[bd->ooo_block]->data
: bd->disk->scratch,
cfg->block_size);
if (res2 < 0) {
return -errno;
}
}
}
// simulate power loss
bd->cfg->powerloss_cb(bd->cfg->powerloss_data);
// if we continue, undo out-of-order write emulation
if (bd->cfg->powerloss_behavior == LFS_EMUBD_POWERLOSS_OOO
&& bd->ooo_block != -1) {
lfs_emubd_decblock(bd->blocks[bd->ooo_block]);
bd->blocks[bd->ooo_block] = ooo_data;
// mirror to disk file?
if (bd->disk) {
off_t res1 = lseek(bd->disk->fd,
(off_t)bd->ooo_block*cfg->block_size,
SEEK_SET);
if (res1 < 0) {
return -errno;
}
ssize_t res2 = write(bd->disk->fd,
(bd->blocks[bd->ooo_block])
? bd->blocks[bd->ooo_block]->data
: bd->disk->scratch,
cfg->block_size);
if (res2 < 0) {
return -errno;
}
}
}
return 0;
}
// block device API // block device API
@@ -356,8 +439,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;
}
} }
} }
@@ -373,10 +459,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 < cfg->block_count); LFS_ASSERT(block < cfg->block_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;
} }
@@ -440,8 +533,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;
}
} }
} }
@@ -451,14 +547,21 @@ 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) {
lfs_emubd_decblock(bd->ooo_data);
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_rawcksum(const struct lfs_config *cfg, static int lfs_emubd_rawcksum(const struct lfs_config *cfg,
@@ -643,6 +746,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;
+10 -6
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
@@ -126,6 +127,7 @@ typedef struct lfs_emubd_block {
typedef struct lfs_emubd_disk { typedef struct lfs_emubd_disk {
uint32_t rc; uint32_t rc;
int fd; int fd;
uint8_t *scratch;
} lfs_emubd_disk_t; } lfs_emubd_disk_t;
// emubd state // emubd state
@@ -138,6 +140,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;
+2 -1
View File
@@ -157,7 +157,8 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size);
#define BENCH_BDCFG \ #define BENCH_BDCFG \
.erase_value = ERASE_VALUE, \ .erase_value = ERASE_VALUE, \
.erase_cycles = ERASE_CYCLES, \ .erase_cycles = ERASE_CYCLES, \
.badblock_behavior = BADBLOCK_BEHAVIOR, .badblock_behavior = BADBLOCK_BEHAVIOR, \
.powerloss_behavior = POWERLOSS_BEHAVIOR,
#endif #endif
-4
View File
@@ -1379,7 +1379,6 @@ static void run_powerloss_linear(
.power_cycles = (TEST_PLS < powerloss->cycle_count) .power_cycles = (TEST_PLS < powerloss->cycle_count)
? TEST_PLS+1 ? TEST_PLS+1
: 0, : 0,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_longjmp, .powerloss_cb = powerloss_longjmp,
.powerloss_data = &powerloss_jmp, .powerloss_data = &powerloss_jmp,
TEST_BDCFG TEST_BDCFG
@@ -1457,7 +1456,6 @@ static void run_powerloss_log(
.power_cycles = (TEST_PLS < powerloss->cycle_count) .power_cycles = (TEST_PLS < powerloss->cycle_count)
? 1 << TEST_PLS ? 1 << TEST_PLS
: 0, : 0,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_longjmp, .powerloss_cb = powerloss_longjmp,
.powerloss_data = &powerloss_jmp, .powerloss_data = &powerloss_jmp,
TEST_BDCFG TEST_BDCFG
@@ -1535,7 +1533,6 @@ static void run_powerloss_cycles(
.power_cycles = (TEST_PLS < powerloss->cycle_count) .power_cycles = (TEST_PLS < powerloss->cycle_count)
? powerloss->cycles[TEST_PLS] ? powerloss->cycles[TEST_PLS]
: 0, : 0,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_longjmp, .powerloss_cb = powerloss_longjmp,
.powerloss_data = &powerloss_jmp, .powerloss_data = &powerloss_jmp,
TEST_BDCFG TEST_BDCFG
@@ -1708,7 +1705,6 @@ static void run_powerloss_exhaustive(
.read_sleep = test_read_sleep, .read_sleep = test_read_sleep,
.prog_sleep = test_prog_sleep, .prog_sleep = test_prog_sleep,
.erase_sleep = test_erase_sleep, .erase_sleep = test_erase_sleep,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_exhaustive_branch, .powerloss_cb = powerloss_exhaustive_branch,
.powerloss_data = NULL, .powerloss_data = NULL,
TEST_BDCFG TEST_BDCFG
+2 -1
View File
@@ -142,7 +142,8 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size);
#define TEST_BDCFG \ #define TEST_BDCFG \
.erase_value = ERASE_VALUE, \ .erase_value = ERASE_VALUE, \
.erase_cycles = ERASE_CYCLES, \ .erase_cycles = ERASE_CYCLES, \
.badblock_behavior = BADBLOCK_BEHAVIOR, .badblock_behavior = BADBLOCK_BEHAVIOR, \
.powerloss_behavior = POWERLOSS_BEHAVIOR,
#endif #endif