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;
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
bd->blocks = NULL;
bd->readed = 0;
bd->proged = 0;
bd->erased = 0;
bd->power_cycles = bd->cfg->power_cycles;
bd->ooo_block = -1;
bd->ooo_data = 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) {
bd->disk = malloc(sizeof(lfs_emubd_disk_t));
if (!bd->disk) {
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
err = LFS_ERR_NOMEM;
goto failed;
}
bd->disk->rc = 1;
bd->disk->fd = -1;
bd->disk->scratch = NULL;
#ifdef _WIN32
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);
#endif
if (bd->disk->fd < 0) {
int err = -errno;
LFS_EMUBD_TRACE("lfs_emubd_create -> %d", err);
return err;
err = -errno;
goto failed;
}
// go ahead and erase all of the disk, otherwise the file will not
// match our internal representation
uint8_t *scratch = malloc(cfg->block_size);
if (!scratch) {
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM;
bd->disk->scratch = malloc(cfg->block_size);
if (!bd->disk->scratch) {
err = LFS_ERR_NOMEM;
goto failed;
}
memset(scratch,
memset(bd->disk->scratch,
(bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0,
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++) {
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) {
int err = -errno;
free(scratch);
LFS_EMUBD_TRACE("lfs_emubd_create -> %d", err);
return err;
err = -errno;
goto failed;
}
}
free(scratch);
}
LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", 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) {
@@ -208,10 +224,12 @@ int lfs_emubd_destroy(const struct lfs_config *cfg) {
free(bd->blocks);
// clean up other resources
lfs_emubd_decblock(bd->ooo_data);
if (bd->disk) {
bd->disk->rc -= 1;
if (bd->disk->rc == 0) {
close(bd->disk->fd);
free(bd->disk->scratch);
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
@@ -356,8 +439,11 @@ 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) {
// simulate power loss
bd->cfg->powerloss_cb(bd->cfg->powerloss_data);
int err = lfs_emubd_powerloss(cfg);
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
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
lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]);
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;
}
@@ -440,8 +533,11 @@ 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) {
// simulate power loss
bd->cfg->powerloss_cb(bd->cfg->powerloss_data);
int err = lfs_emubd_powerloss(cfg);
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) {
LFS_EMUBD_TRACE("lfs_emubd_sync(%p)", (void*)cfg);
lfs_emubd_t *bd = cfg->context;
// do nothing
(void)cfg;
// emulate out-of-order writes? reset first write, writes
// 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);
return 0;
}
/// Additional extended API for driving test features ///
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->erased = bd->erased;
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;
if (copy->disk) {
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
// may be arbitrary) value on every read.
typedef enum lfs_emubd_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_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_behavior_t;
// Mode determining how power-loss behaves during testing. For now this
// only supports a noop behavior, leaving the data on-disk untouched.
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;
// Type for measuring read/program/erase operations
@@ -126,6 +127,7 @@ typedef struct lfs_emubd_block {
typedef struct lfs_emubd_disk {
uint32_t rc;
int fd;
uint8_t *scratch;
} lfs_emubd_disk_t;
// emubd state
@@ -138,6 +140,8 @@ typedef struct lfs_emubd {
lfs_emubd_io_t proged;
lfs_emubd_io_t erased;
lfs_emubd_powercycles_t power_cycles;
lfs_ssize_t ooo_block;
lfs_emubd_block_t *ooo_data;
lfs_emubd_disk_t *disk;
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 \
.erase_value = ERASE_VALUE, \
.erase_cycles = ERASE_CYCLES, \
.badblock_behavior = BADBLOCK_BEHAVIOR,
.badblock_behavior = BADBLOCK_BEHAVIOR, \
.powerloss_behavior = POWERLOSS_BEHAVIOR,
#endif
-4
View File
@@ -1379,7 +1379,6 @@ static void run_powerloss_linear(
.power_cycles = (TEST_PLS < powerloss->cycle_count)
? TEST_PLS+1
: 0,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_longjmp,
.powerloss_data = &powerloss_jmp,
TEST_BDCFG
@@ -1457,7 +1456,6 @@ static void run_powerloss_log(
.power_cycles = (TEST_PLS < powerloss->cycle_count)
? 1 << TEST_PLS
: 0,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_longjmp,
.powerloss_data = &powerloss_jmp,
TEST_BDCFG
@@ -1535,7 +1533,6 @@ static void run_powerloss_cycles(
.power_cycles = (TEST_PLS < powerloss->cycle_count)
? powerloss->cycles[TEST_PLS]
: 0,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_longjmp,
.powerloss_data = &powerloss_jmp,
TEST_BDCFG
@@ -1708,7 +1705,6 @@ static void run_powerloss_exhaustive(
.read_sleep = test_read_sleep,
.prog_sleep = test_prog_sleep,
.erase_sleep = test_erase_sleep,
.powerloss_behavior = POWERLOSS_BEHAVIOR,
.powerloss_cb = powerloss_exhaustive_branch,
.powerloss_data = NULL,
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 \
.erase_value = ERASE_VALUE, \
.erase_cycles = ERASE_CYCLES, \
.badblock_behavior = BADBLOCK_BEHAVIOR,
.badblock_behavior = BADBLOCK_BEHAVIOR, \
.powerloss_behavior = POWERLOSS_BEHAVIOR,
#endif