diff --git a/bd/lfs2_emubd.c b/bd/lfs2_emubd.c index 64b4bf75..8abd1639 100644 --- a/bd/lfs2_emubd.c +++ b/bd/lfs2_emubd.c @@ -48,6 +48,7 @@ static void lfs2_emubd_decblock(lfs2_emubd_block_t *block) { static lfs2_emubd_block_t *lfs2_emubd_mutblock( const struct lfs2_config *cfg, lfs2_emubd_block_t **block) { + lfs2_emubd_t *bd = cfg->context; lfs2_emubd_block_t *block_ = *block; if (block_ && block_->rc == 1) { // rc == 1? can modify @@ -56,13 +57,13 @@ static lfs2_emubd_block_t *lfs2_emubd_mutblock( } else if (block_) { // rc > 1? need to create a copy lfs2_emubd_block_t *nblock = malloc( - sizeof(lfs2_emubd_block_t) + cfg->block_size); + sizeof(lfs2_emubd_block_t) + bd->cfg->erase_size); if (!nblock) { return NULL; } memcpy(nblock, block_, - sizeof(lfs2_emubd_block_t) + cfg->block_size); + sizeof(lfs2_emubd_block_t) + bd->cfg->erase_size); nblock->rc = 1; lfs2_emubd_decblock(block_); @@ -72,7 +73,7 @@ static lfs2_emubd_block_t *lfs2_emubd_mutblock( } else { // no block? need to allocate lfs2_emubd_block_t *nblock = malloc( - sizeof(lfs2_emubd_block_t) + cfg->block_size); + sizeof(lfs2_emubd_block_t) + bd->cfg->erase_size); if (!nblock) { return NULL; } @@ -81,10 +82,9 @@ static lfs2_emubd_block_t *lfs2_emubd_mutblock( nblock->wear = 0; // zero for consistency - lfs2_emubd_t *bd = cfg->context; memset(nblock->data, (bd->cfg->erase_value != -1) ? bd->cfg->erase_value : 0, - cfg->block_size); + bd->cfg->erase_size); *block = nblock; return nblock; @@ -94,22 +94,22 @@ static lfs2_emubd_block_t *lfs2_emubd_mutblock( // emubd create/destroy -int lfs2_emubd_createcfg(const struct lfs2_config *cfg, const char *path, +int lfs2_emubd_create(const struct lfs2_config *cfg, const struct lfs2_emubd_config *bdcfg) { - LFS2_EMUBD_TRACE("lfs2_emubd_createcfg(%p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " - "\"%s\", " - "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", " + LFS2_EMUBD_TRACE("lfs2_emubd_create(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p}, " + "%p {.read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".erase_size=%"PRIu32", .erase_count=%"PRIu32", " + ".erase_value=%"PRId32", .erase_cycles=%"PRIu32", " ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", " ".powerloss_behavior=%"PRIu8", .powerloss_cb=%p, " ".powerloss_data=%p, .track_branches=%d})", (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles, + (void*)bdcfg, + bdcfg->read_size, bdcfg->prog_size, bdcfg->erase_size, + bdcfg->erase_count, bdcfg->erase_value, bdcfg->erase_cycles, bdcfg->badblock_behavior, bdcfg->power_cycles, bdcfg->powerloss_behavior, (void*)(uintptr_t)bdcfg->powerloss_cb, bdcfg->powerloss_data, bdcfg->track_branches); @@ -117,12 +117,12 @@ int lfs2_emubd_createcfg(const struct lfs2_config *cfg, const char *path, bd->cfg = bdcfg; // allocate our block array, all blocks start as uninitialized - bd->blocks = malloc(cfg->block_count * sizeof(lfs2_emubd_block_t*)); + bd->blocks = malloc(bd->cfg->erase_count * sizeof(lfs2_emubd_block_t*)); if (!bd->blocks) { - LFS2_EMUBD_TRACE("lfs2_emubd_createcfg -> %d", LFS2_ERR_NOMEM); + LFS2_EMUBD_TRACE("lfs2_emubd_create -> %d", LFS2_ERR_NOMEM); return LFS2_ERR_NOMEM; } - memset(bd->blocks, 0, cfg->block_count * sizeof(lfs2_emubd_block_t*)); + memset(bd->blocks, 0, bd->cfg->erase_count * sizeof(lfs2_emubd_block_t*)); // setup testing things bd->readed = 0; @@ -134,7 +134,7 @@ int lfs2_emubd_createcfg(const struct lfs2_config *cfg, const char *path, if (bd->cfg->disk_path) { bd->disk = malloc(sizeof(lfs2_emubd_disk_t)); if (!bd->disk) { - LFS2_EMUBD_TRACE("lfs2_emubd_createcfg -> %d", LFS2_ERR_NOMEM); + LFS2_EMUBD_TRACE("lfs2_emubd_create -> %d", LFS2_ERR_NOMEM); return LFS2_ERR_NOMEM; } bd->disk->rc = 1; @@ -156,21 +156,21 @@ int lfs2_emubd_createcfg(const struct lfs2_config *cfg, const char *path, // if we're emulating erase values, we can keep a block around in // memory of just the erase state to speed up emulated erases if (bd->cfg->erase_value != -1) { - bd->disk->scratch = malloc(cfg->block_size); + bd->disk->scratch = malloc(bd->cfg->erase_size); if (!bd->disk->scratch) { - LFS2_EMUBD_TRACE("lfs2_emubd_createcfg -> %d", LFS2_ERR_NOMEM); + LFS2_EMUBD_TRACE("lfs2_emubd_create -> %d", LFS2_ERR_NOMEM); return LFS2_ERR_NOMEM; } memset(bd->disk->scratch, bd->cfg->erase_value, - cfg->block_size); + bd->cfg->erase_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 < bd->cfg->erase_count; i++) { ssize_t res = write(bd->disk->fd, bd->disk->scratch, - cfg->block_size); + bd->cfg->erase_size); if (res < 0) { int err = -errno; LFS2_EMUBD_TRACE("lfs2_emubd_create -> %d", err); @@ -180,33 +180,16 @@ int lfs2_emubd_createcfg(const struct lfs2_config *cfg, const char *path, } } - LFS2_EMUBD_TRACE("lfs2_emubd_createcfg -> %d", 0); + LFS2_EMUBD_TRACE("lfs2_emubd_create -> %d", 0); return 0; } -int lfs2_emubd_create(const struct lfs2_config *cfg, const char *path) { - LFS2_EMUBD_TRACE("lfs2_emubd_create(%p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " - "\"%s\")", - (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - path); - static const struct lfs2_emubd_config defaults = {.erase_value=-1}; - int err = lfs2_emubd_createcfg(cfg, path, &defaults); - LFS2_EMUBD_TRACE("lfs2_emubd_create -> %d", err); - return err; -} - int lfs2_emubd_destroy(const struct lfs2_config *cfg) { LFS2_EMUBD_TRACE("lfs2_emubd_destroy(%p)", (void*)cfg); lfs2_emubd_t *bd = cfg->context; // decrement reference counts - for (lfs2_block_t i = 0; i < cfg->block_count; i++) { + for (lfs2_block_t i = 0; i < bd->cfg->erase_count; i++) { lfs2_emubd_decblock(bd->blocks[i]); } free(bd->blocks); @@ -237,10 +220,10 @@ int lfs2_emubd_read(const struct lfs2_config *cfg, lfs2_block_t block, lfs2_emubd_t *bd = cfg->context; // check if read is valid - LFS2_ASSERT(block < cfg->block_count); - LFS2_ASSERT(off % cfg->read_size == 0); - LFS2_ASSERT(size % cfg->read_size == 0); - LFS2_ASSERT(off+size <= cfg->block_size); + LFS2_ASSERT(block < bd->cfg->erase_count); + LFS2_ASSERT(off % bd->cfg->read_size == 0); + LFS2_ASSERT(size % bd->cfg->read_size == 0); + LFS2_ASSERT(off+size <= bd->cfg->erase_size); // get the block const lfs2_emubd_block_t *b = bd->blocks[block]; @@ -287,10 +270,10 @@ int lfs2_emubd_prog(const struct lfs2_config *cfg, lfs2_block_t block, lfs2_emubd_t *bd = cfg->context; // check if write is valid - LFS2_ASSERT(block < cfg->block_count); - LFS2_ASSERT(off % cfg->prog_size == 0); - LFS2_ASSERT(size % cfg->prog_size == 0); - LFS2_ASSERT(off+size <= cfg->block_size); + LFS2_ASSERT(block < bd->cfg->erase_count); + LFS2_ASSERT(off % bd->cfg->prog_size == 0); + LFS2_ASSERT(size % bd->cfg->prog_size == 0); + LFS2_ASSERT(off+size <= bd->cfg->erase_size); // get the block lfs2_emubd_block_t *b = lfs2_emubd_mutblock(cfg, &bd->blocks[block]); @@ -327,7 +310,7 @@ int lfs2_emubd_prog(const struct lfs2_config *cfg, lfs2_block_t block, // mirror to disk file? if (bd->disk) { off_t res1 = lseek(bd->disk->fd, - (off_t)block*cfg->block_size + (off_t)off, + (off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); if (res1 < 0) { int err = -errno; @@ -372,11 +355,11 @@ int lfs2_emubd_prog(const struct lfs2_config *cfg, lfs2_block_t block, int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { LFS2_EMUBD_TRACE("lfs2_emubd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", - (void*)cfg, block, cfg->block_size); + (void*)cfg, block, ((lfs2_emubd_t*)cfg->context)->cfg->erase_size); lfs2_emubd_t *bd = cfg->context; // check if erase is valid - LFS2_ASSERT(block < cfg->block_count); + LFS2_ASSERT(block < bd->cfg->erase_count); // get the block lfs2_emubd_block_t *b = lfs2_emubd_mutblock(cfg, &bd->blocks[block]); @@ -405,12 +388,12 @@ int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { // emulate an erase value? if (bd->cfg->erase_value != -1) { - memset(b->data, bd->cfg->erase_value, cfg->block_size); + memset(b->data, bd->cfg->erase_value, bd->cfg->erase_size); // mirror to disk file? if (bd->disk) { off_t res1 = lseek(bd->disk->fd, - (off_t)block*cfg->block_size, + (off_t)block*bd->cfg->erase_size, SEEK_SET); if (res1 < 0) { int err = -errno; @@ -420,7 +403,7 @@ int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { ssize_t res2 = write(bd->disk->fd, bd->disk->scratch, - cfg->block_size); + bd->cfg->erase_size); if (res2 < 0) { int err = -errno; LFS2_EMUBD_TRACE("lfs2_emubd_erase -> %d", err); @@ -430,7 +413,7 @@ int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { } // track erases - bd->erased += cfg->block_size; + bd->erased += bd->cfg->erase_size; if (bd->cfg->erase_sleep) { int err = nanosleep(&(struct timespec){ .tv_sec=bd->cfg->erase_sleep/1000000000, @@ -573,7 +556,7 @@ lfs2_emubd_swear_t lfs2_emubd_wear(const struct lfs2_config *cfg, lfs2_emubd_t *bd = cfg->context; // check if block is valid - LFS2_ASSERT(block < cfg->block_count); + LFS2_ASSERT(block < bd->cfg->erase_count); // get the wear lfs2_emubd_wear_t wear; @@ -595,7 +578,7 @@ int lfs2_emubd_setwear(const struct lfs2_config *cfg, lfs2_emubd_t *bd = cfg->context; // check if block is valid - LFS2_ASSERT(block < cfg->block_count); + LFS2_ASSERT(block < bd->cfg->erase_count); // set the wear lfs2_emubd_block_t *b = lfs2_emubd_mutblock(cfg, &bd->blocks[block]); @@ -635,13 +618,13 @@ int lfs2_emubd_copy(const struct lfs2_config *cfg, lfs2_emubd_t *copy) { lfs2_emubd_t *bd = cfg->context; // lazily copy over our block array - copy->blocks = malloc(cfg->block_count * sizeof(lfs2_emubd_block_t*)); + copy->blocks = malloc(bd->cfg->erase_count * sizeof(lfs2_emubd_block_t*)); if (!copy->blocks) { LFS2_EMUBD_TRACE("lfs2_emubd_copy -> %d", LFS2_ERR_NOMEM); return LFS2_ERR_NOMEM; } - for (size_t i = 0; i < cfg->block_count; i++) { + for (size_t i = 0; i < bd->cfg->erase_count; i++) { copy->blocks[i] = lfs2_emubd_incblock(bd->blocks[i]); } diff --git a/bd/lfs2_emubd.h b/bd/lfs2_emubd.h index d4a930e8..9edab15b 100644 --- a/bd/lfs2_emubd.h +++ b/bd/lfs2_emubd.h @@ -67,6 +67,18 @@ typedef int64_t lfs2_emubd_ssleep_t; // emubd config, this is required for testing struct lfs2_emubd_config { + // Minimum size of a read operation in bytes. + lfs2_size_t read_size; + + // Minimum size of a program operation in bytes. + lfs2_size_t prog_size; + + // Size of an erase operation in bytes. + lfs2_size_t erase_size; + + // Number of erase blocks on the device. + lfs2_size_t erase_count; + // 8-bit erase value to use for simulating erases. -1 does not simulate // erases, which can speed up testing by avoiding the extra block-device // operations to store the erase value. @@ -149,11 +161,7 @@ typedef struct lfs2_emubd { /// Block device API /// // Create an emulating block device using the geometry in lfs2_config -// -// Note that filebd is used if a path is provided, if path is NULL -// emubd will use rambd which can be much faster. -int lfs2_emubd_create(const struct lfs2_config *cfg, const char *path); -int lfs2_emubd_createcfg(const struct lfs2_config *cfg, const char *path, +int lfs2_emubd_create(const struct lfs2_config *cfg, const struct lfs2_emubd_config *bdcfg); // Clean up memory associated with block device diff --git a/bd/lfs2_filebd.c b/bd/lfs2_filebd.c index 46ba36ca..9da10370 100644 --- a/bd/lfs2_filebd.c +++ b/bd/lfs2_filebd.c @@ -15,18 +15,22 @@ #include #endif -int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path) { +int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path, + const struct lfs2_filebd_config *bdcfg) { LFS2_FILEBD_TRACE("lfs2_filebd_create(%p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " - "\"%s\")", + ".read=%p, .prog=%p, .erase=%p, .sync=%p}, " + "\"%s\", " + "%p {.read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".erase_size=%"PRIu32", .erase_count=%"PRIu32"})", (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - path, (void*)bdcfg, bdcfg->erase_value); + path, + (void*)bdcfg, + bdcfg->read_size, bdcfg->prog_size, bdcfg->erase_size, + bdcfg->erase_count); lfs2_filebd_t *bd = cfg->context; + bd->cfg = bdcfg; // open file #ifdef _WIN32 @@ -66,17 +70,17 @@ int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block, lfs2_filebd_t *bd = cfg->context; // check if read is valid - LFS2_ASSERT(block < cfg->block_count); - LFS2_ASSERT(off % cfg->read_size == 0); - LFS2_ASSERT(size % cfg->read_size == 0); - LFS2_ASSERT(off+size <= cfg->block_size); + LFS2_ASSERT(block < bd->cfg->erase_count); + LFS2_ASSERT(off % bd->cfg->read_size == 0); + LFS2_ASSERT(size % bd->cfg->read_size == 0); + LFS2_ASSERT(off+size <= bd->cfg->erase_size); // zero for reproducibility (in case file is truncated) memset(buffer, 0, size); // read off_t res1 = lseek(bd->fd, - (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); + (off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); if (res1 < 0) { int err = -errno; LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err); @@ -102,14 +106,14 @@ int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block, lfs2_filebd_t *bd = cfg->context; // check if write is valid - LFS2_ASSERT(block < cfg->block_count); - LFS2_ASSERT(off % cfg->prog_size == 0); - LFS2_ASSERT(size % cfg->prog_size == 0); - LFS2_ASSERT(off+size <= cfg->block_size); + LFS2_ASSERT(block < bd->cfg->erase_count); + LFS2_ASSERT(off % bd->cfg->prog_size == 0); + LFS2_ASSERT(size % bd->cfg->prog_size == 0); + LFS2_ASSERT(off+size <= bd->cfg->erase_size); // program data off_t res1 = lseek(bd->fd, - (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); + (off_t)block*bd->cfg->erase_size + (off_t)off, SEEK_SET); if (res1 < 0) { int err = -errno; LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err); @@ -129,10 +133,11 @@ int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block, int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { LFS2_FILEBD_TRACE("lfs2_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", - (void*)cfg, block, cfg->block_size); + (void*)cfg, block, ((lfs2_file_t*)cfg->context)->cfg->erase_size); + lfs2_filebd_t *bd = cfg->context; // check if erase is valid - LFS2_ASSERT(block < cfg->block_count); + LFS2_ASSERT(block < bd->cfg->erase_count); // erase is a noop (void)block; diff --git a/bd/lfs2_filebd.h b/bd/lfs2_filebd.h index c2c9c3a5..4680fbc1 100644 --- a/bd/lfs2_filebd.h +++ b/bd/lfs2_filebd.h @@ -26,14 +26,31 @@ extern "C" #endif #endif +// filebd config +struct lfs2_filebd_config { + // Minimum size of a read operation in bytes. + lfs2_size_t read_size; + + // Minimum size of a program operation in bytes. + lfs2_size_t prog_size; + + // Size of an erase operation in bytes. + lfs2_size_t erase_size; + + // Number of erase blocks on the device. + lfs2_size_t erase_count; +}; + // filebd state typedef struct lfs2_filebd { int fd; + const struct lfs2_filebd_config *cfg; } lfs2_filebd_t; -// Create a file block device using the geometry in lfs2_config -int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path); +// Create a file block device +int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path, + const struct lfs2_filebd_config *bdcfg); // Clean up memory associated with block device int lfs2_filebd_destroy(const struct lfs2_config *cfg); diff --git a/bd/lfs2_rambd.c b/bd/lfs2_rambd.c index a3430b47..62363891 100644 --- a/bd/lfs2_rambd.c +++ b/bd/lfs2_rambd.c @@ -7,18 +7,19 @@ */ #include "bd/lfs2_rambd.h" -int lfs2_rambd_createcfg(const struct lfs2_config *cfg, +int lfs2_rambd_create(const struct lfs2_config *cfg, const struct lfs2_rambd_config *bdcfg) { - LFS2_RAMBD_TRACE("lfs2_rambd_createcfg(%p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " - "%p {.buffer=%p})", + LFS2_RAMBD_TRACE("lfs2_rambd_create(%p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p}, " + "%p {.read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".erase_size=%"PRIu32", .erase_count=%"PRIu32", " + ".buffer=%p})", (void*)cfg, cfg->context, (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - (void*)bdcfg, bdcfg->buffer); + (void*)bdcfg, + bdcfg->read_size, bdcfg->prog_size, bdcfg->erase_size, + bdcfg->erase_count, bdcfg->buffer); lfs2_rambd_t *bd = cfg->context; bd->cfg = bdcfg; @@ -26,35 +27,20 @@ int lfs2_rambd_createcfg(const struct lfs2_config *cfg, if (bd->cfg->buffer) { bd->buffer = bd->cfg->buffer; } else { - bd->buffer = lfs2_malloc(cfg->block_size * cfg->block_count); + bd->buffer = lfs2_malloc(bd->cfg->erase_size * bd->cfg->erase_count); if (!bd->buffer) { - LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", LFS2_ERR_NOMEM); + LFS2_RAMBD_TRACE("lfs2_rambd_create -> %d", LFS2_ERR_NOMEM); return LFS2_ERR_NOMEM; } } // zero for reproducibility - memset(bd->buffer, 0, cfg->block_size * cfg->block_count); + memset(bd->buffer, 0, bd->cfg->erase_size * bd->cfg->erase_count); - LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", 0); + LFS2_RAMBD_TRACE("lfs2_rambd_create -> %d", 0); return 0; } -int lfs2_rambd_create(const struct lfs2_config *cfg) { - LFS2_RAMBD_TRACE("lfs2_rambd_create(%p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32"})", - (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count); - static const struct lfs2_rambd_config defaults = {0}; - int err = lfs2_rambd_createcfg(cfg, &defaults); - LFS2_RAMBD_TRACE("lfs2_rambd_create -> %d", err); - return err; -} - int lfs2_rambd_destroy(const struct lfs2_config *cfg) { LFS2_RAMBD_TRACE("lfs2_rambd_destroy(%p)", (void*)cfg); // clean up memory @@ -74,13 +60,13 @@ int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block, lfs2_rambd_t *bd = cfg->context; // check if read is valid - LFS2_ASSERT(block < cfg->block_count); - LFS2_ASSERT(off % cfg->read_size == 0); - LFS2_ASSERT(size % cfg->read_size == 0); - LFS2_ASSERT(off+size <= cfg->block_size); + LFS2_ASSERT(block < bd->cfg->erase_count); + LFS2_ASSERT(off % bd->cfg->read_size == 0); + LFS2_ASSERT(size % bd->cfg->read_size == 0); + LFS2_ASSERT(off+size <= bd->cfg->erase_size); // read data - memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size); + memcpy(buffer, &bd->buffer[block*bd->cfg->erase_size + off], size); LFS2_RAMBD_TRACE("lfs2_rambd_read -> %d", 0); return 0; @@ -94,13 +80,13 @@ int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block, lfs2_rambd_t *bd = cfg->context; // check if write is valid - LFS2_ASSERT(block < cfg->block_count); - LFS2_ASSERT(off % cfg->prog_size == 0); - LFS2_ASSERT(size % cfg->prog_size == 0); - LFS2_ASSERT(off+size <= cfg->block_size); + LFS2_ASSERT(block < bd->cfg->erase_count); + LFS2_ASSERT(off % bd->cfg->prog_size == 0); + LFS2_ASSERT(size % bd->cfg->prog_size == 0); + LFS2_ASSERT(off+size <= bd->cfg->erase_size); // program data - memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size); + memcpy(&bd->buffer[block*bd->cfg->erase_size + off], buffer, size); LFS2_RAMBD_TRACE("lfs2_rambd_prog -> %d", 0); return 0; @@ -108,10 +94,11 @@ int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block, int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { LFS2_RAMBD_TRACE("lfs2_rambd_erase(%p, 0x%"PRIx32" (%"PRIu32"))", - (void*)cfg, block, cfg->block_size); + (void*)cfg, block, ((lfs2_rambd_t*)cfg->context)->cfg->erase_size); + lfs2_rambd_t *bd = cfg->context; // check if erase is valid - LFS2_ASSERT(block < cfg->block_count); + LFS2_ASSERT(block < bd->cfg->erase_count); // erase is a noop (void)block; diff --git a/bd/lfs2_rambd.h b/bd/lfs2_rambd.h index 6abe9172..267d30a8 100644 --- a/bd/lfs2_rambd.h +++ b/bd/lfs2_rambd.h @@ -26,8 +26,20 @@ extern "C" #endif #endif -// rambd config (optional) +// rambd config struct lfs2_rambd_config { + // Minimum size of a read operation in bytes. + lfs2_size_t read_size; + + // Minimum size of a program operation in bytes. + lfs2_size_t prog_size; + + // Size of an erase operation in bytes. + lfs2_size_t erase_size; + + // Number of erase blocks on the device. + lfs2_size_t erase_count; + // Optional statically allocated buffer for the block device. void *buffer; }; @@ -39,9 +51,8 @@ typedef struct lfs2_rambd { } lfs2_rambd_t; -// Create a RAM block device using the geometry in lfs2_config -int lfs2_rambd_create(const struct lfs2_config *cfg); -int lfs2_rambd_createcfg(const struct lfs2_config *cfg, +// Create a RAM block device +int lfs2_rambd_create(const struct lfs2_config *cfg, const struct lfs2_rambd_config *bdcfg); // Clean up memory associated with block device diff --git a/lfs2.c b/lfs2.c index 76a9e5b3..0b02b0ab 100644 --- a/lfs2.c +++ b/lfs2.c @@ -46,8 +46,8 @@ static int lfs2_bd_read(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, void *buffer, lfs2_size_t size) { uint8_t *data = buffer; - if (block >= lfs2->cfg->block_count || - off+size > lfs2->cfg->block_size) { + if (off+size > lfs2->cfg->block_size + || (lfs2->block_count && block >= lfs2->block_count)) { return LFS2_ERR_CORRUPT; } @@ -104,7 +104,7 @@ static int lfs2_bd_read(lfs2_t *lfs2, } // load to cache, first condition can no longer fail - LFS2_ASSERT(block < lfs2->cfg->block_count); + LFS2_ASSERT(!lfs2->block_count || block < lfs2->block_count); rcache->block = block; rcache->off = lfs2_aligndown(off, lfs2->cfg->read_size); rcache->size = lfs2_min( @@ -176,7 +176,7 @@ static int lfs2_bd_crc(lfs2_t *lfs2, static int lfs2_bd_flush(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) { if (pcache->block != LFS2_BLOCK_NULL && pcache->block != LFS2_BLOCK_INLINE) { - LFS2_ASSERT(pcache->block < lfs2->cfg->block_count); + LFS2_ASSERT(pcache->block < lfs2->block_count); lfs2_size_t diff = lfs2_alignup(pcache->size, lfs2->cfg->prog_size); int err = lfs2->cfg->prog(lfs2->cfg, pcache->block, pcache->off, pcache->buffer, diff); @@ -229,7 +229,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, const void *buffer, lfs2_size_t size) { const uint8_t *data = buffer; - LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->cfg->block_count); + LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->block_count); LFS2_ASSERT(off + size <= lfs2->cfg->block_size); while (size > 0) { @@ -273,7 +273,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, #ifndef LFS2_READONLY static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { - LFS2_ASSERT(block < lfs2->cfg->block_count); + LFS2_ASSERT(block < lfs2->block_count); int err = lfs2->cfg->erase(lfs2->cfg, block); LFS2_ASSERT(err <= 0); return err; @@ -597,7 +597,7 @@ static int lfs2_rawunmount(lfs2_t *lfs2); static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { lfs2_t *lfs2 = (lfs2_t*)p; lfs2_block_t off = ((block - lfs2->free.off) - + lfs2->cfg->block_count) % lfs2->cfg->block_count; + + lfs2->block_count) % lfs2->block_count; if (off < lfs2->free.size) { lfs2->free.buffer[off / 32] |= 1U << (off % 32); @@ -611,7 +611,7 @@ static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { // is to prevent blocks from being garbage collected in the middle of a // commit operation static void lfs2_alloc_ack(lfs2_t *lfs2) { - lfs2->free.ack = lfs2->cfg->block_count; + lfs2->free.ack = lfs2->block_count; } // drop the lookahead buffer, this is done during mounting and failed @@ -622,6 +622,26 @@ static void lfs2_alloc_drop(lfs2_t *lfs2) { lfs2_alloc_ack(lfs2); } +#ifndef LFS2_READONLY +static int lfs2_fs_rawgc(lfs2_t *lfs2) { + // Move free offset at the first unused block (lfs2->free.i) + // lfs2->free.i is equal lfs2->free.size when all blocks are used + lfs2->free.off = (lfs2->free.off + lfs2->free.i) % lfs2->block_count; + lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); + lfs2->free.i = 0; + + // find mask of free blocks from tree + memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); + int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); + if (err) { + lfs2_alloc_drop(lfs2); + return err; + } + + return 0; +} +#endif + #ifndef LFS2_READONLY static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { @@ -632,7 +652,7 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { if (!(lfs2->free.buffer[off / 32] & (1U << (off % 32)))) { // found a free block - *block = (lfs2->free.off + off) % lfs2->cfg->block_count; + *block = (lfs2->free.off + off) % lfs2->block_count; // eagerly find next off so an alloc ack can // discredit old lookahead blocks @@ -654,16 +674,8 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { return LFS2_ERR_NOSPC; } - lfs2->free.off = (lfs2->free.off + lfs2->free.size) - % lfs2->cfg->block_count; - lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); - lfs2->free.i = 0; - - // find mask of free blocks from tree - memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); - if (err) { - lfs2_alloc_drop(lfs2); + int err = lfs2_fs_rawgc(lfs2); + if(err) { return err; } } @@ -1067,7 +1079,8 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // if either block address is invalid we return LFS2_ERR_CORRUPT here, // otherwise later writes to the pair could fail - if (pair[0] >= lfs2->cfg->block_count || pair[1] >= lfs2->cfg->block_count) { + if (lfs2->block_count + && (pair[0] >= lfs2->block_count || pair[1] >= lfs2->block_count)) { return LFS2_ERR_CORRUPT; } @@ -2140,7 +2153,7 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, // do we have extra space? littlefs can't reclaim this space // by itself, so expand cautiously - if ((lfs2_size_t)size < lfs2->cfg->block_count/2) { + if ((lfs2_size_t)size < lfs2->block_count/2) { LFS2_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); int err = lfs2_dir_split(lfs2, dir, attrs, attrcount, source, begin, end); @@ -4095,6 +4108,7 @@ static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) { /// Filesystem operations /// static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->cfg = cfg; + lfs2->block_count = cfg->block_count; // May be 0 int err = 0; #ifdef LFS2_MULTIVERSION @@ -4237,6 +4251,8 @@ static int lfs2_deinit(lfs2_t *lfs2) { return 0; } + + #ifndef LFS2_READONLY static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = 0; @@ -4246,11 +4262,13 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { return err; } + LFS2_ASSERT(cfg->block_count != 0); + // create free lookahead memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); lfs2->free.off = 0; lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, - lfs2->cfg->block_count); + lfs2->block_count); lfs2->free.i = 0; lfs2_alloc_ack(lfs2); @@ -4265,7 +4283,7 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_superblock_t superblock = { .version = lfs2_fs_disk_version(lfs2), .block_size = lfs2->cfg->block_size, - .block_count = lfs2->cfg->block_count, + .block_count = lfs2->block_count, .name_max = lfs2->name_max, .file_max = lfs2->file_max, .attr_max = lfs2->attr_max, @@ -4422,13 +4440,17 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->attr_max = superblock.attr_max; } - if (superblock.block_count != lfs2->cfg->block_count) { + // this is where we get the block_count from disk if block_count=0 + if (lfs2->cfg->block_count + && superblock.block_count != lfs2->cfg->block_count) { LFS2_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")", superblock.block_count, lfs2->cfg->block_count); err = LFS2_ERR_INVAL; goto cleanup; } + lfs2->block_count = superblock.block_count; + if (superblock.block_size != lfs2->cfg->block_size) { LFS2_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", superblock.block_size, lfs2->cfg->block_size); @@ -4444,12 +4466,6 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { } } - // found superblock? - if (lfs2_pair_isnull(lfs2->root)) { - err = LFS2_ERR_INVAL; - goto cleanup; - } - // update littlefs with gstate if (!lfs2_gstate_iszero(&lfs2->gstate)) { LFS2_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, @@ -4462,7 +4478,7 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // setup free lookahead, to distribute allocations uniformly across // boots, we start the allocator at a random location - lfs2->free.off = lfs2->seed % lfs2->cfg->block_count; + lfs2->free.off = lfs2->seed % lfs2->block_count; lfs2_alloc_drop(lfs2); return 0; @@ -4506,6 +4522,10 @@ static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { fsinfo->disk_version = superblock.version; } + // filesystem geometry + fsinfo->block_size = lfs2->cfg->block_size; + fsinfo->block_count = lfs2->block_count; + // other on-disk configuration, we cache all of these for internal use fsinfo->name_max = lfs2->name_max; fsinfo->file_max = lfs2->file_max; @@ -4771,7 +4791,7 @@ static int lfs2_fs_desuperblock(lfs2_t *lfs2) { lfs2_superblock_t superblock = { .version = lfs2_fs_disk_version(lfs2), .block_size = lfs2->cfg->block_size, - .block_count = lfs2->cfg->block_count, + .block_count = lfs2->block_count, .name_max = lfs2->name_max, .file_max = lfs2->file_max, .attr_max = lfs2->attr_max, @@ -5025,6 +5045,44 @@ static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { return size; } +#ifndef LFS2_READONLY +int lfs2_fs_rawgrow(lfs2_t *lfs2, lfs2_size_t block_count) { + // shrinking is not supported + LFS2_ASSERT(block_count >= lfs2->block_count); + + if (block_count > lfs2->block_count) { + lfs2->block_count = block_count; + + // fetch the root + lfs2_mdir_t root; + int err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } + + // update the superblock + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs2_superblock_fromle32(&superblock); + + superblock.block_count = lfs2->block_count; + + lfs2_superblock_tole32(&superblock); + err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } + } + + return 0; +} +#endif #ifdef LFS2_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -5449,6 +5507,10 @@ static int lfs21_unmount(lfs2_t *lfs2) { /// v1 migration /// static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { struct lfs21 lfs21; + + // Indeterminate filesystem size not allowed for migration. + LFS2_ASSERT(cfg->block_count != 0); + int err = lfs21_mount(lfs2, &lfs21, cfg); if (err) { return err; @@ -6188,6 +6250,22 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) return err; } +#ifndef LFS2_READONLY +int lfs2_fs_gc(lfs2_t *lfs2) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2); + + err = lfs2_fs_rawgc(lfs2); + + LFS2_TRACE("lfs2_fs_gc -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + #ifndef LFS2_READONLY int lfs2_fs_mkconsistent(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); @@ -6204,6 +6282,22 @@ int lfs2_fs_mkconsistent(lfs2_t *lfs2) { } #endif +#ifndef LFS2_READONLY +int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_grow(%p, %"PRIu32")", (void*)lfs2, block_count); + + err = lfs2_fs_rawgrow(lfs2, block_count); + + LFS2_TRACE("lfs2_fs_grow -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + #ifdef LFS2_MIGRATE int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = LFS2_LOCK(cfg); diff --git a/lfs2.h b/lfs2.h index cb660f15..4c426fc4 100644 --- a/lfs2.h +++ b/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020007 +#define LFS2_VERSION 0x00020008 #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -293,6 +293,12 @@ struct lfs2_fsinfo { // On-disk version. uint32_t disk_version; + // Size of a logical block in bytes. + lfs2_size_t block_size; + + // Number of logical blocks in filesystem. + lfs2_size_t block_count; + // Upper limit on the length of file names in bytes. lfs2_size_t name_max; @@ -433,6 +439,7 @@ typedef struct lfs2 { } free; const struct lfs2_config *cfg; + lfs2_size_t block_count; lfs2_size_t name_max; lfs2_size_t file_max; lfs2_size_t attr_max; @@ -705,6 +712,18 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2); // Returns a negative error code on failure. int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); +// Attempt to proactively find free blocks +// +// Calling this function is not required, but may allowing the offloading of +// the expensive block allocation scan to a less time-critical code path. +// +// Note: littlefs currently does not persist any found free blocks to disk. +// This may change in the future. +// +// Returns a negative error code on failure. Finding no free blocks is +// not an error. +int lfs2_fs_gc(lfs2_t *lfs2); + #ifndef LFS2_READONLY // Attempt to make the filesystem consistent and ready for writing // @@ -717,6 +736,16 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); int lfs2_fs_mkconsistent(lfs2_t *lfs2); #endif +#ifndef LFS2_READONLY +// Grows the filesystem to a new size, updating the superblock with the new +// block count. +// +// Note: This is irreversible. +// +// Returns a negative error code on failure. +int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count); +#endif + #ifndef LFS2_READONLY #ifdef LFS2_MIGRATE // Attempts to migrate a previous version of littlefs diff --git a/runners/bench_runner.c b/runners/bench_runner.c index f3ee6392..201ad2d5 100644 --- a/runners/bench_runner.c +++ b/runners/bench_runner.c @@ -1271,9 +1271,9 @@ static void list_geometries(void) { builtin_geometries[g].name, READ_SIZE, PROG_SIZE, - BLOCK_SIZE, - BLOCK_COUNT, - BLOCK_SIZE*BLOCK_COUNT); + ERASE_SIZE, + ERASE_COUNT, + ERASE_SIZE*ERASE_COUNT); } } @@ -1324,6 +1324,10 @@ void perm_run( }; struct lfs2_emubd_config bdcfg = { + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .erase_size = ERASE_SIZE, + .erase_count = ERASE_COUNT, .erase_value = ERASE_VALUE, .erase_cycles = ERASE_CYCLES, .badblock_behavior = BADBLOCK_BEHAVIOR, @@ -1333,7 +1337,7 @@ void perm_run( .erase_sleep = bench_erase_sleep, }; - int err = lfs2_emubd_createcfg(&cfg, bench_disk_path, &bdcfg); + int err = lfs2_emubd_create(&cfg, &bdcfg); if (err) { fprintf(stderr, "error: could not create block device: %d\n", err); exit(-1); @@ -1761,19 +1765,19 @@ invalid_define: = BENCH_LIT(sizes[0]); geometry->defines[PROG_SIZE_i] = BENCH_LIT(sizes[1]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = BENCH_LIT(sizes[2]); } else if (count >= 2) { geometry->defines[PROG_SIZE_i] = BENCH_LIT(sizes[0]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = BENCH_LIT(sizes[1]); } else { - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = BENCH_LIT(sizes[0]); } if (count >= 4) { - geometry->defines[BLOCK_COUNT_i] + geometry->defines[ERASE_COUNT_i] = BENCH_LIT(sizes[3]); } optarg = s; @@ -1805,19 +1809,19 @@ invalid_define: = BENCH_LIT(sizes[0]); geometry->defines[PROG_SIZE_i] = BENCH_LIT(sizes[1]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = BENCH_LIT(sizes[2]); } else if (count >= 2) { geometry->defines[PROG_SIZE_i] = BENCH_LIT(sizes[0]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = BENCH_LIT(sizes[1]); } else { - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = BENCH_LIT(sizes[0]); } if (count >= 4) { - geometry->defines[BLOCK_COUNT_i] + geometry->defines[ERASE_COUNT_i] = BENCH_LIT(sizes[3]); } optarg = s; diff --git a/runners/bench_runner.h b/runners/bench_runner.h index 802dd447..f7699e10 100644 --- a/runners/bench_runner.h +++ b/runners/bench_runner.h @@ -89,18 +89,22 @@ intmax_t bench_define(size_t define); #define READ_SIZE_i 0 #define PROG_SIZE_i 1 -#define BLOCK_SIZE_i 2 -#define BLOCK_COUNT_i 3 -#define CACHE_SIZE_i 4 -#define LOOKAHEAD_SIZE_i 5 -#define BLOCK_CYCLES_i 6 -#define ERASE_VALUE_i 7 -#define ERASE_CYCLES_i 8 -#define BADBLOCK_BEHAVIOR_i 9 -#define POWERLOSS_BEHAVIOR_i 10 +#define ERASE_SIZE_i 2 +#define ERASE_COUNT_i 3 +#define BLOCK_SIZE_i 4 +#define BLOCK_COUNT_i 5 +#define CACHE_SIZE_i 6 +#define LOOKAHEAD_SIZE_i 7 +#define BLOCK_CYCLES_i 8 +#define ERASE_VALUE_i 9 +#define ERASE_CYCLES_i 10 +#define BADBLOCK_BEHAVIOR_i 11 +#define POWERLOSS_BEHAVIOR_i 12 #define READ_SIZE bench_define(READ_SIZE_i) #define PROG_SIZE bench_define(PROG_SIZE_i) +#define ERASE_SIZE bench_define(ERASE_SIZE_i) +#define ERASE_COUNT bench_define(ERASE_COUNT_i) #define BLOCK_SIZE bench_define(BLOCK_SIZE_i) #define BLOCK_COUNT bench_define(BLOCK_COUNT_i) #define CACHE_SIZE bench_define(CACHE_SIZE_i) @@ -113,9 +117,11 @@ intmax_t bench_define(size_t define); #define BENCH_IMPLICIT_DEFINES \ BENCH_DEF(READ_SIZE, PROG_SIZE) \ - BENCH_DEF(PROG_SIZE, BLOCK_SIZE) \ - BENCH_DEF(BLOCK_SIZE, 0) \ - BENCH_DEF(BLOCK_COUNT, (1024*1024)/BLOCK_SIZE) \ + BENCH_DEF(PROG_SIZE, ERASE_SIZE) \ + BENCH_DEF(ERASE_SIZE, 0) \ + BENCH_DEF(ERASE_COUNT, (1024*1024)/BLOCK_SIZE) \ + BENCH_DEF(BLOCK_SIZE, ERASE_SIZE) \ + BENCH_DEF(BLOCK_COUNT, ERASE_COUNT/lfs2_max(BLOCK_SIZE/ERASE_SIZE,1))\ BENCH_DEF(CACHE_SIZE, lfs2_max(64,lfs2_max(READ_SIZE,PROG_SIZE))) \ BENCH_DEF(LOOKAHEAD_SIZE, 16) \ BENCH_DEF(BLOCK_CYCLES, -1) \ @@ -125,7 +131,7 @@ intmax_t bench_define(size_t define); BENCH_DEF(POWERLOSS_BEHAVIOR, LFS2_EMUBD_POWERLOSS_NOOP) #define BENCH_GEOMETRY_DEFINE_COUNT 4 -#define BENCH_IMPLICIT_DEFINE_COUNT 11 +#define BENCH_IMPLICIT_DEFINE_COUNT 13 #endif diff --git a/runners/test_runner.c b/runners/test_runner.c index 10a2cc77..cdd239bf 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -1312,9 +1312,9 @@ static void list_geometries(void) { builtin_geometries[g].name, READ_SIZE, PROG_SIZE, - BLOCK_SIZE, - BLOCK_COUNT, - BLOCK_SIZE*BLOCK_COUNT); + ERASE_SIZE, + ERASE_COUNT, + ERASE_SIZE*ERASE_COUNT); } } @@ -1352,6 +1352,10 @@ static void run_powerloss_none( }; struct lfs2_emubd_config bdcfg = { + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .erase_size = ERASE_SIZE, + .erase_count = ERASE_COUNT, .erase_value = ERASE_VALUE, .erase_cycles = ERASE_CYCLES, .badblock_behavior = BADBLOCK_BEHAVIOR, @@ -1361,7 +1365,7 @@ static void run_powerloss_none( .erase_sleep = test_erase_sleep, }; - int err = lfs2_emubd_createcfg(&cfg, test_disk_path, &bdcfg); + int err = lfs2_emubd_create(&cfg, &bdcfg); if (err) { fprintf(stderr, "error: could not create block device: %d\n", err); exit(-1); @@ -1424,6 +1428,10 @@ static void run_powerloss_linear( }; struct lfs2_emubd_config bdcfg = { + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .erase_size = ERASE_SIZE, + .erase_count = ERASE_COUNT, .erase_value = ERASE_VALUE, .erase_cycles = ERASE_CYCLES, .badblock_behavior = BADBLOCK_BEHAVIOR, @@ -1437,7 +1445,7 @@ static void run_powerloss_linear( .powerloss_data = &powerloss_jmp, }; - int err = lfs2_emubd_createcfg(&cfg, test_disk_path, &bdcfg); + int err = lfs2_emubd_create(&cfg, &bdcfg); if (err) { fprintf(stderr, "error: could not create block device: %d\n", err); exit(-1); @@ -1513,6 +1521,10 @@ static void run_powerloss_log( }; struct lfs2_emubd_config bdcfg = { + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .erase_size = ERASE_SIZE, + .erase_count = ERASE_COUNT, .erase_value = ERASE_VALUE, .erase_cycles = ERASE_CYCLES, .badblock_behavior = BADBLOCK_BEHAVIOR, @@ -1526,7 +1538,7 @@ static void run_powerloss_log( .powerloss_data = &powerloss_jmp, }; - int err = lfs2_emubd_createcfg(&cfg, test_disk_path, &bdcfg); + int err = lfs2_emubd_create(&cfg, &bdcfg); if (err) { fprintf(stderr, "error: could not create block device: %d\n", err); exit(-1); @@ -1600,6 +1612,10 @@ static void run_powerloss_cycles( }; struct lfs2_emubd_config bdcfg = { + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .erase_size = ERASE_SIZE, + .erase_count = ERASE_COUNT, .erase_value = ERASE_VALUE, .erase_cycles = ERASE_CYCLES, .badblock_behavior = BADBLOCK_BEHAVIOR, @@ -1613,7 +1629,7 @@ static void run_powerloss_cycles( .powerloss_data = &powerloss_jmp, }; - int err = lfs2_emubd_createcfg(&cfg, test_disk_path, &bdcfg); + int err = lfs2_emubd_create(&cfg, &bdcfg); if (err) { fprintf(stderr, "error: could not create block device: %d\n", err); exit(-1); @@ -1785,6 +1801,10 @@ static void run_powerloss_exhaustive( }; struct lfs2_emubd_config bdcfg = { + .read_size = READ_SIZE, + .prog_size = PROG_SIZE, + .erase_size = ERASE_SIZE, + .erase_count = ERASE_COUNT, .erase_value = ERASE_VALUE, .erase_cycles = ERASE_CYCLES, .badblock_behavior = BADBLOCK_BEHAVIOR, @@ -1797,7 +1817,7 @@ static void run_powerloss_exhaustive( .powerloss_data = NULL, }; - int err = lfs2_emubd_createcfg(&cfg, test_disk_path, &bdcfg); + int err = lfs2_emubd_create(&cfg, &bdcfg); if (err) { fprintf(stderr, "error: could not create block device: %d\n", err); exit(-1); @@ -2314,19 +2334,19 @@ invalid_define: = TEST_LIT(sizes[0]); geometry->defines[PROG_SIZE_i] = TEST_LIT(sizes[1]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = TEST_LIT(sizes[2]); } else if (count >= 2) { geometry->defines[PROG_SIZE_i] = TEST_LIT(sizes[0]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = TEST_LIT(sizes[1]); } else { - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = TEST_LIT(sizes[0]); } if (count >= 4) { - geometry->defines[BLOCK_COUNT_i] + geometry->defines[ERASE_COUNT_i] = TEST_LIT(sizes[3]); } optarg = s; @@ -2358,19 +2378,19 @@ invalid_define: = TEST_LIT(sizes[0]); geometry->defines[PROG_SIZE_i] = TEST_LIT(sizes[1]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = TEST_LIT(sizes[2]); } else if (count >= 2) { geometry->defines[PROG_SIZE_i] = TEST_LIT(sizes[0]); - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = TEST_LIT(sizes[1]); } else { - geometry->defines[BLOCK_SIZE_i] + geometry->defines[ERASE_SIZE_i] = TEST_LIT(sizes[0]); } if (count >= 4) { - geometry->defines[BLOCK_COUNT_i] + geometry->defines[ERASE_COUNT_i] = TEST_LIT(sizes[3]); } optarg = s; diff --git a/runners/test_runner.h b/runners/test_runner.h index 60efea92..e1c21c5f 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -82,19 +82,23 @@ intmax_t test_define(size_t define); #define READ_SIZE_i 0 #define PROG_SIZE_i 1 -#define BLOCK_SIZE_i 2 -#define BLOCK_COUNT_i 3 -#define CACHE_SIZE_i 4 -#define LOOKAHEAD_SIZE_i 5 -#define BLOCK_CYCLES_i 6 -#define ERASE_VALUE_i 7 -#define ERASE_CYCLES_i 8 -#define BADBLOCK_BEHAVIOR_i 9 -#define POWERLOSS_BEHAVIOR_i 10 -#define DISK_VERSION_i 11 +#define ERASE_SIZE_i 2 +#define ERASE_COUNT_i 3 +#define BLOCK_SIZE_i 4 +#define BLOCK_COUNT_i 5 +#define CACHE_SIZE_i 6 +#define LOOKAHEAD_SIZE_i 7 +#define BLOCK_CYCLES_i 8 +#define ERASE_VALUE_i 9 +#define ERASE_CYCLES_i 10 +#define BADBLOCK_BEHAVIOR_i 11 +#define POWERLOSS_BEHAVIOR_i 12 +#define DISK_VERSION_i 13 #define READ_SIZE TEST_DEFINE(READ_SIZE_i) #define PROG_SIZE TEST_DEFINE(PROG_SIZE_i) +#define ERASE_SIZE TEST_DEFINE(ERASE_SIZE_i) +#define ERASE_COUNT TEST_DEFINE(ERASE_COUNT_i) #define BLOCK_SIZE TEST_DEFINE(BLOCK_SIZE_i) #define BLOCK_COUNT TEST_DEFINE(BLOCK_COUNT_i) #define CACHE_SIZE TEST_DEFINE(CACHE_SIZE_i) @@ -108,9 +112,11 @@ intmax_t test_define(size_t define); #define TEST_IMPLICIT_DEFINES \ TEST_DEF(READ_SIZE, PROG_SIZE) \ - TEST_DEF(PROG_SIZE, BLOCK_SIZE) \ - TEST_DEF(BLOCK_SIZE, 0) \ - TEST_DEF(BLOCK_COUNT, (1024*1024)/BLOCK_SIZE) \ + TEST_DEF(PROG_SIZE, ERASE_SIZE) \ + TEST_DEF(ERASE_SIZE, 0) \ + TEST_DEF(ERASE_COUNT, (1024*1024)/ERASE_SIZE) \ + TEST_DEF(BLOCK_SIZE, ERASE_SIZE) \ + TEST_DEF(BLOCK_COUNT, ERASE_COUNT/lfs2_max(BLOCK_SIZE/ERASE_SIZE,1)) \ TEST_DEF(CACHE_SIZE, lfs2_max(64,lfs2_max(READ_SIZE,PROG_SIZE))) \ TEST_DEF(LOOKAHEAD_SIZE, 16) \ TEST_DEF(BLOCK_CYCLES, -1) \ @@ -120,8 +126,8 @@ intmax_t test_define(size_t define); TEST_DEF(POWERLOSS_BEHAVIOR, LFS2_EMUBD_POWERLOSS_NOOP) \ TEST_DEF(DISK_VERSION, 0) -#define TEST_IMPLICIT_DEFINE_COUNT 12 #define TEST_GEOMETRY_DEFINE_COUNT 4 +#define TEST_IMPLICIT_DEFINE_COUNT 14 #endif diff --git a/tests/test_alloc.toml b/tests/test_alloc.toml index 4594f246..3b62d7ca 100644 --- a/tests/test_alloc.toml +++ b/tests/test_alloc.toml @@ -6,6 +6,7 @@ if = 'BLOCK_CYCLES == -1' [cases.test_alloc_parallel] defines.FILES = 3 defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' +defines.GC = [false, true] code = ''' const char *names[] = {"bacon", "eggs", "pancakes"}; lfs2_file_t files[FILES]; @@ -24,6 +25,9 @@ code = ''' LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0; } for (int n = 0; n < FILES; n++) { + if (GC) { + lfs2_fs_gc(&lfs2) => 0; + } size_t size = strlen(names[n]); for (lfs2_size_t i = 0; i < SIZE; i += size) { lfs2_file_write(&lfs2, &files[n], names[n], size) => size; @@ -55,6 +59,7 @@ code = ''' [cases.test_alloc_serial] defines.FILES = 3 defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' +defines.GC = [false, true] code = ''' const char *names[] = {"bacon", "eggs", "pancakes"}; @@ -75,6 +80,9 @@ code = ''' uint8_t buffer[1024]; memcpy(buffer, names[n], size); for (int i = 0; i < SIZE; i += size) { + if (GC) { + lfs2_fs_gc(&lfs2) => 0; + } lfs2_file_write(&lfs2, &file, buffer, size) => size; } lfs2_file_close(&lfs2, &file) => 0; @@ -247,6 +255,9 @@ code = ''' } res => LFS2_ERR_NOSPC; + // note that lfs2_fs_gc should not error here + lfs2_fs_gc(&lfs2) => 0; + lfs2_file_close(&lfs2, &file) => 0; lfs2_unmount(&lfs2) => 0; @@ -298,6 +309,9 @@ code = ''' } res => LFS2_ERR_NOSPC; + // note that lfs2_fs_gc should not error here + lfs2_fs_gc(&lfs2) => 0; + lfs2_file_close(&lfs2, &file) => 0; lfs2_unmount(&lfs2) => 0; @@ -337,6 +351,8 @@ code = ''' count += 1; } err => LFS2_ERR_NOSPC; + // note that lfs2_fs_gc should not error here + lfs2_fs_gc(&lfs2) => 0; lfs2_file_close(&lfs2, &file) => 0; lfs2_remove(&lfs2, "exhaustion") => 0; @@ -435,6 +451,8 @@ code = ''' break; } } + // note that lfs2_fs_gc should not error here + lfs2_fs_gc(&lfs2) => 0; lfs2_file_close(&lfs2, &file) => 0; lfs2_unmount(&lfs2) => 0; @@ -460,8 +478,8 @@ code = ''' # chained dir exhaustion test [cases.test_alloc_chained_dir_exhaustion] -if = 'BLOCK_SIZE == 512' -defines.BLOCK_COUNT = 1024 +if = 'ERASE_SIZE == 512' +defines.ERASE_COUNT = 1024 code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; @@ -538,8 +556,8 @@ code = ''' # split dir test [cases.test_alloc_split_dir] -if = 'BLOCK_SIZE == 512' -defines.BLOCK_COUNT = 1024 +if = 'ERASE_SIZE == 512' +defines.ERASE_COUNT = 1024 code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; @@ -587,8 +605,8 @@ code = ''' # outdated lookahead test [cases.test_alloc_outdated_lookahead] -if = 'BLOCK_SIZE == 512' -defines.BLOCK_COUNT = 1024 +if = 'ERASE_SIZE == 512' +defines.ERASE_COUNT = 1024 code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; @@ -655,8 +673,8 @@ code = ''' # outdated lookahead and split dir test [cases.test_alloc_outdated_lookahead_split_dir] -if = 'BLOCK_SIZE == 512' -defines.BLOCK_COUNT = 1024 +if = 'ERASE_SIZE == 512' +defines.ERASE_COUNT = 1024 code = ''' lfs2_t lfs2; lfs2_format(&lfs2, cfg) => 0; diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index 84f139f4..d3314aa2 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -2,7 +2,7 @@ if = '(int32_t)BLOCK_CYCLES == -1' [cases.test_badblocks_single] -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.ERASE_CYCLES = 0xffffffff defines.ERASE_VALUE = [0x00, 0xff, -1] defines.BADBLOCK_BEHAVIOR = [ @@ -82,7 +82,7 @@ code = ''' ''' [cases.test_badblocks_region_corruption] # (causes cascading failures) -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.ERASE_CYCLES = 0xffffffff defines.ERASE_VALUE = [0x00, 0xff, -1] defines.BADBLOCK_BEHAVIOR = [ @@ -161,7 +161,7 @@ code = ''' ''' [cases.test_badblocks_alternating_corruption] # (causes cascading failures) -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.ERASE_CYCLES = 0xffffffff defines.ERASE_VALUE = [0x00, 0xff, -1] defines.BADBLOCK_BEHAVIOR = [ diff --git a/tests/test_exhaustion.toml b/tests/test_exhaustion.toml index 61fa0dd2..788b5171 100644 --- a/tests/test_exhaustion.toml +++ b/tests/test_exhaustion.toml @@ -1,7 +1,7 @@ # test running a filesystem to exhaustion [cases.test_exhaustion_normal] defines.ERASE_CYCLES = 10 -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.BADBLOCK_BEHAVIOR = [ 'LFS2_EMUBD_BADBLOCK_PROGERROR', @@ -94,7 +94,7 @@ exhausted: # which also requires expanding superblocks [cases.test_exhaustion_superblocks] defines.ERASE_CYCLES = 10 -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.BADBLOCK_BEHAVIOR = [ 'LFS2_EMUBD_BADBLOCK_PROGERROR', @@ -188,7 +188,7 @@ exhausted: # wear-level test running a filesystem to exhaustion [cases.test_exhuastion_wear_leveling] defines.ERASE_CYCLES = 20 -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.FILES = 10 code = ''' @@ -288,7 +288,7 @@ exhausted: # wear-level test + expanding superblock [cases.test_exhaustion_wear_leveling_superblocks] defines.ERASE_CYCLES = 20 -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2' defines.FILES = 10 code = ''' @@ -385,7 +385,7 @@ exhausted: # test that we wear blocks roughly evenly [cases.test_exhaustion_wear_distribution] defines.ERASE_CYCLES = 0xffffffff -defines.BLOCK_COUNT = 256 # small bd so test runs faster +defines.ERASE_COUNT = 256 # small bd so test runs faster defines.BLOCK_CYCLES = [5, 4, 3, 2, 1] defines.CYCLES = 100 defines.FILES = 10 diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 5d4923ae..a8c8ed6a 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -14,6 +14,21 @@ code = ''' lfs2_unmount(&lfs2) => 0; ''' +# mount/unmount from interpretting a previous superblock block_count +[cases.test_superblocks_mount_unknown_block_count] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + + memset(&lfs2, 0, sizeof(lfs2)); + struct lfs2_config tweaked_cfg = *cfg; + tweaked_cfg.block_count = 0; + lfs2_mount(&lfs2, &tweaked_cfg) => 0; + assert(lfs2.block_count == cfg->block_count); + lfs2_unmount(&lfs2) => 0; +''' + + # reentrant format [cases.test_superblocks_reentrant_format] reentrant = true @@ -197,3 +212,255 @@ code = ''' assert(info.type == LFS2_TYPE_REG); lfs2_unmount(&lfs2) => 0; ''' + +# mount with unknown block_count +[cases.test_superblocks_unknown_blocks] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + + // known block_size/block_count + cfg->block_size = BLOCK_SIZE; + cfg->block_count = BLOCK_COUNT; + lfs2_mount(&lfs2, cfg) => 0; + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // unknown block_count + cfg->block_size = BLOCK_SIZE; + cfg->block_count = 0; + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // do some work + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_file_t file; + lfs2_file_open(&lfs2, &file, "test", + LFS2_O_CREAT | LFS2_O_EXCL | LFS2_O_WRONLY) => 0; + lfs2_file_write(&lfs2, &file, "hello!", 6) => 6; + lfs2_file_close(&lfs2, &file) => 0; + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_RDONLY) => 0; + uint8_t buffer[256]; + lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => 6; + lfs2_file_close(&lfs2, &file) => 0; + assert(memcmp(buffer, "hello!", 6) == 0); + lfs2_unmount(&lfs2) => 0; +''' + +# mount with blocks fewer than the erase_count +[cases.test_superblocks_fewer_blocks] +defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2'] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + + // known block_size/block_count + cfg->block_size = BLOCK_SIZE; + cfg->block_count = BLOCK_COUNT; + lfs2_mount(&lfs2, cfg) => 0; + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // incorrect block_count + cfg->block_size = BLOCK_SIZE; + cfg->block_count = ERASE_COUNT; + lfs2_mount(&lfs2, cfg) => LFS2_ERR_INVAL; + + // unknown block_count + cfg->block_size = BLOCK_SIZE; + cfg->block_count = 0; + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // do some work + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_file_t file; + lfs2_file_open(&lfs2, &file, "test", + LFS2_O_CREAT | LFS2_O_EXCL | LFS2_O_WRONLY) => 0; + lfs2_file_write(&lfs2, &file, "hello!", 6) => 6; + lfs2_file_close(&lfs2, &file) => 0; + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_RDONLY) => 0; + uint8_t buffer[256]; + lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => 6; + lfs2_file_close(&lfs2, &file) => 0; + assert(memcmp(buffer, "hello!", 6) == 0); + lfs2_unmount(&lfs2) => 0; +''' + +# mount with more blocks than the erase_count +[cases.test_superblocks_more_blocks] +defines.FORMAT_BLOCK_COUNT = '2*ERASE_COUNT' +in = 'lfs2.c' +code = ''' + lfs2_t lfs2; + lfs2_init(&lfs2, cfg) => 0; + lfs2.block_count = BLOCK_COUNT; + + lfs2_mdir_t root = { + .pair = {0, 0}, // make sure this goes into block 0 + .rev = 0, + .off = sizeof(uint32_t), + .etag = 0xffffffff, + .count = 0, + .tail = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .erased = false, + .split = false, + }; + + lfs2_superblock_t superblock = { + .version = LFS2_DISK_VERSION, + .block_size = BLOCK_SIZE, + .block_count = FORMAT_BLOCK_COUNT, + .name_max = LFS2_NAME_MAX, + .file_max = LFS2_FILE_MAX, + .attr_max = LFS2_ATTR_MAX, + }; + + lfs2_superblock_tole32(&superblock); + lfs2_dir_commit(&lfs2, &root, LFS2_MKATTRS( + {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0), NULL}, + {LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})) => 0; + lfs2_deinit(&lfs2) => 0; + + // known block_size/block_count + cfg->block_size = BLOCK_SIZE; + cfg->block_count = BLOCK_COUNT; + lfs2_mount(&lfs2, cfg) => LFS2_ERR_INVAL; +''' + +# mount and grow the filesystem +[cases.test_superblocks_grow] +defines.BLOCK_COUNT = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2'] +defines.BLOCK_COUNT_2 = 'ERASE_COUNT' +defines.KNOWN_BLOCK_COUNT = [true, false] +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + + if (KNOWN_BLOCK_COUNT) { + cfg->block_count = BLOCK_COUNT; + } else { + cfg->block_count = 0; + } + + // mount with block_size < erase_size + lfs2_mount(&lfs2, cfg) => 0; + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // same size is a noop + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, BLOCK_COUNT) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT); + lfs2_unmount(&lfs2) => 0; + + // grow to new size + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, BLOCK_COUNT_2) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + if (KNOWN_BLOCK_COUNT) { + cfg->block_count = BLOCK_COUNT_2; + } else { + cfg->block_count = 0; + } + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + // mounting with the previous size should fail + cfg->block_count = BLOCK_COUNT; + lfs2_mount(&lfs2, cfg) => LFS2_ERR_INVAL; + + if (KNOWN_BLOCK_COUNT) { + cfg->block_count = BLOCK_COUNT_2; + } else { + cfg->block_count = 0; + } + + // same size is a noop + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_grow(&lfs2, BLOCK_COUNT_2) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_unmount(&lfs2) => 0; + + // do some work + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_file_t file; + lfs2_file_open(&lfs2, &file, "test", + LFS2_O_CREAT | LFS2_O_EXCL | LFS2_O_WRONLY) => 0; + lfs2_file_write(&lfs2, &file, "hello!", 6) => 6; + lfs2_file_close(&lfs2, &file) => 0; + lfs2_unmount(&lfs2) => 0; + + lfs2_mount(&lfs2, cfg) => 0; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BLOCK_COUNT_2); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_RDONLY) => 0; + uint8_t buffer[256]; + lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => 6; + lfs2_file_close(&lfs2, &file) => 0; + assert(memcmp(buffer, "hello!", 6) == 0); + lfs2_unmount(&lfs2) => 0; +'''