From b5370d60014f8652c8e662a0d7d452b9f9d710ff Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 27 Feb 2024 12:58:07 -0600 Subject: [PATCH] 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. --- bd/lfs_emubd.c | 173 +++++++++++++++++++++++++++++++++-------- bd/lfs_emubd.h | 16 ++-- runners/bench_runner.h | 3 +- runners/test_runner.c | 4 - runners/test_runner.h | 3 +- 5 files changed, 153 insertions(+), 46 deletions(-) diff --git a/bd/lfs_emubd.c b/bd/lfs_emubd.c index 376cc586..1949685b 100644 --- a/bd/lfs_emubd.c +++ b/bd/lfs_emubd.c @@ -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; diff --git a/bd/lfs_emubd.h b/bd/lfs_emubd.h index c7bfa2a7..f4afcdeb 100644 --- a/bd/lfs_emubd.h +++ b/bd/lfs_emubd.h @@ -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; diff --git a/runners/bench_runner.h b/runners/bench_runner.h index bbc0acf3..2c1f1036 100644 --- a/runners/bench_runner.h +++ b/runners/bench_runner.h @@ -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 diff --git a/runners/test_runner.c b/runners/test_runner.c index 4cbba41a..33094427 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -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 diff --git a/runners/test_runner.h b/runners/test_runner.h index 63fc141e..77246c80 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -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