Переглянути джерело

Merge pull request #877 from littlefs-project/devel

Minor release: v2.8
Christopher Haster 2 роки тому
батько
коміт
f77214d1f0

+ 44 - 61
bd/lfs_emubd.c

@@ -48,6 +48,7 @@ static void lfs_emubd_decblock(lfs_emubd_block_t *block) {
 static lfs_emubd_block_t *lfs_emubd_mutblock(
         const struct lfs_config *cfg,
         lfs_emubd_block_t **block) {
+    lfs_emubd_t *bd = cfg->context;
     lfs_emubd_block_t *block_ = *block;
     if (block_ && block_->rc == 1) {
         // rc == 1? can modify
@@ -56,13 +57,13 @@ static lfs_emubd_block_t *lfs_emubd_mutblock(
     } else if (block_) {
         // rc > 1? need to create a copy
         lfs_emubd_block_t *nblock = malloc(
-                sizeof(lfs_emubd_block_t) + cfg->block_size);
+                sizeof(lfs_emubd_block_t) + bd->cfg->erase_size);
         if (!nblock) {
             return NULL;
         }
 
         memcpy(nblock, block_,
-                sizeof(lfs_emubd_block_t) + cfg->block_size);
+                sizeof(lfs_emubd_block_t) + bd->cfg->erase_size);
         nblock->rc = 1;
 
         lfs_emubd_decblock(block_);
@@ -72,7 +73,7 @@ static lfs_emubd_block_t *lfs_emubd_mutblock(
     } else {
         // no block? need to allocate
         lfs_emubd_block_t *nblock = malloc(
-                sizeof(lfs_emubd_block_t) + cfg->block_size);
+                sizeof(lfs_emubd_block_t) + bd->cfg->erase_size);
         if (!nblock) {
             return NULL;
         }
@@ -81,10 +82,9 @@ static lfs_emubd_block_t *lfs_emubd_mutblock(
         nblock->wear = 0;
 
         // zero for consistency
-        lfs_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 lfs_emubd_block_t *lfs_emubd_mutblock(
 
 // emubd create/destroy
 
-int lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
+int lfs_emubd_create(const struct lfs_config *cfg,
         const struct lfs_emubd_config *bdcfg) {
-    LFS_EMUBD_TRACE("lfs_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", "
+    LFS_EMUBD_TRACE("lfs_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 lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
     bd->cfg = bdcfg;
 
     // allocate our block array, all blocks start as uninitialized
-    bd->blocks = malloc(cfg->block_count * sizeof(lfs_emubd_block_t*));
+    bd->blocks = malloc(bd->cfg->erase_count * sizeof(lfs_emubd_block_t*));
     if (!bd->blocks) {
-        LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM);
+        LFS_EMUBD_TRACE("lfs_emubd_create -> %d", LFS_ERR_NOMEM);
         return LFS_ERR_NOMEM;
     }
-    memset(bd->blocks, 0, cfg->block_count * sizeof(lfs_emubd_block_t*));
+    memset(bd->blocks, 0, bd->cfg->erase_count * sizeof(lfs_emubd_block_t*));
 
     // setup testing things
     bd->readed = 0;
@@ -134,7 +134,7 @@ int lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
     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);
+            LFS_EMUBD_TRACE("lfs_emubd_create -> %d", LFS_ERR_NOMEM);
             return LFS_ERR_NOMEM;
         }
         bd->disk->rc = 1;
@@ -156,21 +156,21 @@ int lfs_emubd_createcfg(const struct lfs_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) {
-                LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", LFS_ERR_NOMEM);
+                LFS_EMUBD_TRACE("lfs_emubd_create -> %d", LFS_ERR_NOMEM);
                 return LFS_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;
                     LFS_EMUBD_TRACE("lfs_emubd_create -> %d", err);
@@ -180,33 +180,16 @@ int lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
         }
     }
 
-    LFS_EMUBD_TRACE("lfs_emubd_createcfg -> %d", 0);
+    LFS_EMUBD_TRACE("lfs_emubd_create -> %d", 0);
     return 0;
 }
 
-int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
-    LFS_EMUBD_TRACE("lfs_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 lfs_emubd_config defaults = {.erase_value=-1};
-    int err = lfs_emubd_createcfg(cfg, path, &defaults);
-    LFS_EMUBD_TRACE("lfs_emubd_create -> %d", err);
-    return err;
-}
-
 int lfs_emubd_destroy(const struct lfs_config *cfg) {
     LFS_EMUBD_TRACE("lfs_emubd_destroy(%p)", (void*)cfg);
     lfs_emubd_t *bd = cfg->context;
 
     // decrement reference counts
-    for (lfs_block_t i = 0; i < cfg->block_count; i++) {
+    for (lfs_block_t i = 0; i < bd->cfg->erase_count; i++) {
         lfs_emubd_decblock(bd->blocks[i]);
     }
     free(bd->blocks);
@@ -237,10 +220,10 @@ int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
     lfs_emubd_t *bd = cfg->context;
 
     // check if read is valid
-    LFS_ASSERT(block < cfg->block_count);
-    LFS_ASSERT(off  % cfg->read_size == 0);
-    LFS_ASSERT(size % cfg->read_size == 0);
-    LFS_ASSERT(off+size <= cfg->block_size);
+    LFS_ASSERT(block < bd->cfg->erase_count);
+    LFS_ASSERT(off  % bd->cfg->read_size == 0);
+    LFS_ASSERT(size % bd->cfg->read_size == 0);
+    LFS_ASSERT(off+size <= bd->cfg->erase_size);
 
     // get the block
     const lfs_emubd_block_t *b = bd->blocks[block];
@@ -287,10 +270,10 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
     lfs_emubd_t *bd = cfg->context;
 
     // check if write is valid
-    LFS_ASSERT(block < cfg->block_count);
-    LFS_ASSERT(off  % cfg->prog_size == 0);
-    LFS_ASSERT(size % cfg->prog_size == 0);
-    LFS_ASSERT(off+size <= cfg->block_size);
+    LFS_ASSERT(block < bd->cfg->erase_count);
+    LFS_ASSERT(off  % bd->cfg->prog_size == 0);
+    LFS_ASSERT(size % bd->cfg->prog_size == 0);
+    LFS_ASSERT(off+size <= bd->cfg->erase_size);
 
     // get the block
     lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]);
@@ -327,7 +310,7 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_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 lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
 
 int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
     LFS_EMUBD_TRACE("lfs_emubd_erase(%p, 0x%"PRIx32" (%"PRIu32"))",
-            (void*)cfg, block, cfg->block_size);
+            (void*)cfg, block, ((lfs_emubd_t*)cfg->context)->cfg->erase_size);
     lfs_emubd_t *bd = cfg->context;
 
     // check if erase is valid
-    LFS_ASSERT(block < cfg->block_count);
+    LFS_ASSERT(block < bd->cfg->erase_count);
 
     // get the block
     lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]);
@@ -405,12 +388,12 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_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 lfs_emubd_erase(const struct lfs_config *cfg, lfs_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;
                 LFS_EMUBD_TRACE("lfs_emubd_erase -> %d", err);
@@ -430,7 +413,7 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_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 @@ lfs_emubd_swear_t lfs_emubd_wear(const struct lfs_config *cfg,
     lfs_emubd_t *bd = cfg->context;
 
     // check if block is valid
-    LFS_ASSERT(block < cfg->block_count);
+    LFS_ASSERT(block < bd->cfg->erase_count);
 
     // get the wear
     lfs_emubd_wear_t wear;
@@ -595,7 +578,7 @@ int lfs_emubd_setwear(const struct lfs_config *cfg,
     lfs_emubd_t *bd = cfg->context;
 
     // check if block is valid
-    LFS_ASSERT(block < cfg->block_count);
+    LFS_ASSERT(block < bd->cfg->erase_count);
 
     // set the wear
     lfs_emubd_block_t *b = lfs_emubd_mutblock(cfg, &bd->blocks[block]);
@@ -635,13 +618,13 @@ int lfs_emubd_copy(const struct lfs_config *cfg, lfs_emubd_t *copy) {
     lfs_emubd_t *bd = cfg->context;
 
     // lazily copy over our block array
-    copy->blocks = malloc(cfg->block_count * sizeof(lfs_emubd_block_t*));
+    copy->blocks = malloc(bd->cfg->erase_count * sizeof(lfs_emubd_block_t*));
     if (!copy->blocks) {
         LFS_EMUBD_TRACE("lfs_emubd_copy -> %d", LFS_ERR_NOMEM);
         return LFS_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] = lfs_emubd_incblock(bd->blocks[i]);
     }
 

+ 13 - 5
bd/lfs_emubd.h

@@ -67,6 +67,18 @@ typedef int64_t lfs_emubd_ssleep_t;
 
 // emubd config, this is required for testing
 struct lfs_emubd_config {
+    // Minimum size of a read operation in bytes.
+    lfs_size_t read_size;
+
+    // Minimum size of a program operation in bytes.
+    lfs_size_t prog_size;
+
+    // Size of an erase operation in bytes.
+    lfs_size_t erase_size;
+
+    // Number of erase blocks on the device.
+    lfs_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 lfs_emubd {
 /// Block device API ///
 
 // Create an emulating block device using the geometry in lfs_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 lfs_emubd_create(const struct lfs_config *cfg, const char *path);
-int lfs_emubd_createcfg(const struct lfs_config *cfg, const char *path,
+int lfs_emubd_create(const struct lfs_config *cfg,
         const struct lfs_emubd_config *bdcfg);
 
 // Clean up memory associated with block device

+ 24 - 19
bd/lfs_filebd.c

@@ -15,18 +15,22 @@
 #include <windows.h>
 #endif
 
-int lfs_filebd_create(const struct lfs_config *cfg, const char *path) {
+int lfs_filebd_create(const struct lfs_config *cfg, const char *path,
+        const struct lfs_filebd_config *bdcfg) {
     LFS_FILEBD_TRACE("lfs_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);
     lfs_filebd_t *bd = cfg->context;
+    bd->cfg = bdcfg;
 
     // open file
     #ifdef _WIN32
@@ -66,17 +70,17 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
     lfs_filebd_t *bd = cfg->context;
 
     // check if read is valid
-    LFS_ASSERT(block < cfg->block_count);
-    LFS_ASSERT(off  % cfg->read_size == 0);
-    LFS_ASSERT(size % cfg->read_size == 0);
-    LFS_ASSERT(off+size <= cfg->block_size);
+    LFS_ASSERT(block < bd->cfg->erase_count);
+    LFS_ASSERT(off  % bd->cfg->read_size == 0);
+    LFS_ASSERT(size % bd->cfg->read_size == 0);
+    LFS_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;
         LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err);
@@ -102,14 +106,14 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
     lfs_filebd_t *bd = cfg->context;
 
     // check if write is valid
-    LFS_ASSERT(block < cfg->block_count);
-    LFS_ASSERT(off  % cfg->prog_size == 0);
-    LFS_ASSERT(size % cfg->prog_size == 0);
-    LFS_ASSERT(off+size <= cfg->block_size);
+    LFS_ASSERT(block < bd->cfg->erase_count);
+    LFS_ASSERT(off  % bd->cfg->prog_size == 0);
+    LFS_ASSERT(size % bd->cfg->prog_size == 0);
+    LFS_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;
         LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
@@ -129,10 +133,11 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
 
 int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
     LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32" (%"PRIu32"))",
-            (void*)cfg, block, cfg->block_size);
+            (void*)cfg, block, ((lfs_file_t*)cfg->context)->cfg->erase_size);
+    lfs_filebd_t *bd = cfg->context;
 
     // check if erase is valid
-    LFS_ASSERT(block < cfg->block_count);
+    LFS_ASSERT(block < bd->cfg->erase_count);
 
     // erase is a noop
     (void)block;

+ 19 - 2
bd/lfs_filebd.h

@@ -26,14 +26,31 @@ extern "C"
 #endif
 #endif
 
+// filebd config
+struct lfs_filebd_config {
+    // Minimum size of a read operation in bytes.
+    lfs_size_t read_size;
+
+    // Minimum size of a program operation in bytes.
+    lfs_size_t prog_size;
+
+    // Size of an erase operation in bytes.
+    lfs_size_t erase_size;
+
+    // Number of erase blocks on the device.
+    lfs_size_t erase_count;
+};
+
 // filebd state
 typedef struct lfs_filebd {
     int fd;
+    const struct lfs_filebd_config *cfg;
 } lfs_filebd_t;
 
 
-// Create a file block device using the geometry in lfs_config
-int lfs_filebd_create(const struct lfs_config *cfg, const char *path);
+// Create a file block device
+int lfs_filebd_create(const struct lfs_config *cfg, const char *path,
+        const struct lfs_filebd_config *bdcfg);
 
 // Clean up memory associated with block device
 int lfs_filebd_destroy(const struct lfs_config *cfg);

+ 26 - 39
bd/lfs_rambd.c

@@ -7,18 +7,19 @@
  */
 #include "bd/lfs_rambd.h"
 
-int lfs_rambd_createcfg(const struct lfs_config *cfg,
+int lfs_rambd_create(const struct lfs_config *cfg,
         const struct lfs_rambd_config *bdcfg) {
-    LFS_RAMBD_TRACE("lfs_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})",
+    LFS_RAMBD_TRACE("lfs_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);
     lfs_rambd_t *bd = cfg->context;
     bd->cfg = bdcfg;
 
@@ -26,35 +27,20 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
     if (bd->cfg->buffer) {
         bd->buffer = bd->cfg->buffer;
     } else {
-        bd->buffer = lfs_malloc(cfg->block_size * cfg->block_count);
+        bd->buffer = lfs_malloc(bd->cfg->erase_size * bd->cfg->erase_count);
         if (!bd->buffer) {
-            LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", LFS_ERR_NOMEM);
+            LFS_RAMBD_TRACE("lfs_rambd_create -> %d", LFS_ERR_NOMEM);
             return LFS_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);
 
-    LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0);
+    LFS_RAMBD_TRACE("lfs_rambd_create -> %d", 0);
     return 0;
 }
 
-int lfs_rambd_create(const struct lfs_config *cfg) {
-    LFS_RAMBD_TRACE("lfs_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 lfs_rambd_config defaults = {0};
-    int err = lfs_rambd_createcfg(cfg, &defaults);
-    LFS_RAMBD_TRACE("lfs_rambd_create -> %d", err);
-    return err;
-}
-
 int lfs_rambd_destroy(const struct lfs_config *cfg) {
     LFS_RAMBD_TRACE("lfs_rambd_destroy(%p)", (void*)cfg);
     // clean up memory
@@ -74,13 +60,13 @@ int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
     lfs_rambd_t *bd = cfg->context;
 
     // check if read is valid
-    LFS_ASSERT(block < cfg->block_count);
-    LFS_ASSERT(off  % cfg->read_size == 0);
-    LFS_ASSERT(size % cfg->read_size == 0);
-    LFS_ASSERT(off+size <= cfg->block_size);
+    LFS_ASSERT(block < bd->cfg->erase_count);
+    LFS_ASSERT(off  % bd->cfg->read_size == 0);
+    LFS_ASSERT(size % bd->cfg->read_size == 0);
+    LFS_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);
 
     LFS_RAMBD_TRACE("lfs_rambd_read -> %d", 0);
     return 0;
@@ -94,13 +80,13 @@ int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block,
     lfs_rambd_t *bd = cfg->context;
 
     // check if write is valid
-    LFS_ASSERT(block < cfg->block_count);
-    LFS_ASSERT(off  % cfg->prog_size == 0);
-    LFS_ASSERT(size % cfg->prog_size == 0);
-    LFS_ASSERT(off+size <= cfg->block_size);
+    LFS_ASSERT(block < bd->cfg->erase_count);
+    LFS_ASSERT(off  % bd->cfg->prog_size == 0);
+    LFS_ASSERT(size % bd->cfg->prog_size == 0);
+    LFS_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);
 
     LFS_RAMBD_TRACE("lfs_rambd_prog -> %d", 0);
     return 0;
@@ -108,10 +94,11 @@ int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block,
 
 int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) {
     LFS_RAMBD_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32" (%"PRIu32"))",
-            (void*)cfg, block, cfg->block_size);
+            (void*)cfg, block, ((lfs_rambd_t*)cfg->context)->cfg->erase_size);
+    lfs_rambd_t *bd = cfg->context;
 
     // check if erase is valid
-    LFS_ASSERT(block < cfg->block_count);
+    LFS_ASSERT(block < bd->cfg->erase_count);
 
     // erase is a noop
     (void)block;

+ 15 - 4
bd/lfs_rambd.h

@@ -26,8 +26,20 @@ extern "C"
 #endif
 #endif
 
-// rambd config (optional)
+// rambd config
 struct lfs_rambd_config {
+    // Minimum size of a read operation in bytes.
+    lfs_size_t read_size;
+
+    // Minimum size of a program operation in bytes.
+    lfs_size_t prog_size;
+
+    // Size of an erase operation in bytes.
+    lfs_size_t erase_size;
+
+    // Number of erase blocks on the device.
+    lfs_size_t erase_count;
+
     // Optional statically allocated buffer for the block device.
     void *buffer;
 };
@@ -39,9 +51,8 @@ typedef struct lfs_rambd {
 } lfs_rambd_t;
 
 
-// Create a RAM block device using the geometry in lfs_config
-int lfs_rambd_create(const struct lfs_config *cfg);
-int lfs_rambd_createcfg(const struct lfs_config *cfg,
+// Create a RAM block device
+int lfs_rambd_create(const struct lfs_config *cfg,
         const struct lfs_rambd_config *bdcfg);
 
 // Clean up memory associated with block device

+ 126 - 32
lfs.c

@@ -46,8 +46,8 @@ static int lfs_bd_read(lfs_t *lfs,
         lfs_block_t block, lfs_off_t off,
         void *buffer, lfs_size_t size) {
     uint8_t *data = buffer;
-    if (block >= lfs->cfg->block_count ||
-            off+size > lfs->cfg->block_size) {
+    if (off+size > lfs->cfg->block_size
+            || (lfs->block_count && block >= lfs->block_count)) {
         return LFS_ERR_CORRUPT;
     }
 
@@ -104,7 +104,7 @@ static int lfs_bd_read(lfs_t *lfs,
         }
 
         // load to cache, first condition can no longer fail
-        LFS_ASSERT(block < lfs->cfg->block_count);
+        LFS_ASSERT(!lfs->block_count || block < lfs->block_count);
         rcache->block = block;
         rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
         rcache->size = lfs_min(
@@ -176,7 +176,7 @@ static int lfs_bd_crc(lfs_t *lfs,
 static int lfs_bd_flush(lfs_t *lfs,
         lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
     if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) {
-        LFS_ASSERT(pcache->block < lfs->cfg->block_count);
+        LFS_ASSERT(pcache->block < lfs->block_count);
         lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size);
         int err = lfs->cfg->prog(lfs->cfg, pcache->block,
                 pcache->off, pcache->buffer, diff);
@@ -229,7 +229,7 @@ static int lfs_bd_prog(lfs_t *lfs,
         lfs_block_t block, lfs_off_t off,
         const void *buffer, lfs_size_t size) {
     const uint8_t *data = buffer;
-    LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->cfg->block_count);
+    LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count);
     LFS_ASSERT(off + size <= lfs->cfg->block_size);
 
     while (size > 0) {
@@ -273,7 +273,7 @@ static int lfs_bd_prog(lfs_t *lfs,
 
 #ifndef LFS_READONLY
 static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
-    LFS_ASSERT(block < lfs->cfg->block_count);
+    LFS_ASSERT(block < lfs->block_count);
     int err = lfs->cfg->erase(lfs->cfg, block);
     LFS_ASSERT(err <= 0);
     return err;
@@ -597,7 +597,7 @@ static int lfs_rawunmount(lfs_t *lfs);
 static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
     lfs_t *lfs = (lfs_t*)p;
     lfs_block_t off = ((block - lfs->free.off)
-            + lfs->cfg->block_count) % lfs->cfg->block_count;
+            + lfs->block_count) % lfs->block_count;
 
     if (off < lfs->free.size) {
         lfs->free.buffer[off / 32] |= 1U << (off % 32);
@@ -611,7 +611,7 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
 // is to prevent blocks from being garbage collected in the middle of a
 // commit operation
 static void lfs_alloc_ack(lfs_t *lfs) {
-    lfs->free.ack = lfs->cfg->block_count;
+    lfs->free.ack = lfs->block_count;
 }
 
 // drop the lookahead buffer, this is done during mounting and failed
@@ -622,6 +622,26 @@ static void lfs_alloc_drop(lfs_t *lfs) {
     lfs_alloc_ack(lfs);
 }
 
+#ifndef LFS_READONLY
+static int lfs_fs_rawgc(lfs_t *lfs) {
+    // Move free offset at the first unused block (lfs->free.i)
+    // lfs->free.i is equal lfs->free.size when all blocks are used
+    lfs->free.off = (lfs->free.off + lfs->free.i) % lfs->block_count;
+    lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
+    lfs->free.i = 0;
+
+    // find mask of free blocks from tree
+    memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
+    int err = lfs_fs_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true);
+    if (err) {
+        lfs_alloc_drop(lfs);
+        return err;
+    }
+
+    return 0;
+}
+#endif
+
 #ifndef LFS_READONLY
 static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
     while (true) {
@@ -632,7 +652,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
 
             if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
                 // found a free block
-                *block = (lfs->free.off + off) % lfs->cfg->block_count;
+                *block = (lfs->free.off + off) % lfs->block_count;
 
                 // eagerly find next off so an alloc ack can
                 // discredit old lookahead blocks
@@ -654,16 +674,8 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
             return LFS_ERR_NOSPC;
         }
 
-        lfs->free.off = (lfs->free.off + lfs->free.size)
-                % lfs->cfg->block_count;
-        lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
-        lfs->free.i = 0;
-
-        // find mask of free blocks from tree
-        memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
-        int err = lfs_fs_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true);
-        if (err) {
-            lfs_alloc_drop(lfs);
+        int err = lfs_fs_rawgc(lfs);
+        if(err) {
             return err;
         }
     }
@@ -1067,7 +1079,8 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
 
     // if either block address is invalid we return LFS_ERR_CORRUPT here,
     // otherwise later writes to the pair could fail
-    if (pair[0] >= lfs->cfg->block_count || pair[1] >= lfs->cfg->block_count) {
+    if (lfs->block_count 
+            && (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count)) {
         return LFS_ERR_CORRUPT;
     }
 
@@ -2140,7 +2153,7 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
 
         // do we have extra space? littlefs can't reclaim this space
         // by itself, so expand cautiously
-        if ((lfs_size_t)size < lfs->cfg->block_count/2) {
+        if ((lfs_size_t)size < lfs->block_count/2) {
             LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev);
             int err = lfs_dir_split(lfs, dir, attrs, attrcount,
                     source, begin, end);
@@ -4095,6 +4108,7 @@ static int lfs_rawremoveattr(lfs_t *lfs, const char *path, uint8_t type) {
 /// Filesystem operations ///
 static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     lfs->cfg = cfg;
+    lfs->block_count = cfg->block_count;  // May be 0
     int err = 0;
 
 #ifdef LFS_MULTIVERSION
@@ -4237,6 +4251,8 @@ static int lfs_deinit(lfs_t *lfs) {
     return 0;
 }
 
+
+
 #ifndef LFS_READONLY
 static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
     int err = 0;
@@ -4246,11 +4262,13 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
             return err;
         }
 
+        LFS_ASSERT(cfg->block_count != 0);
+
         // create free lookahead
         memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
         lfs->free.off = 0;
         lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size,
-                lfs->cfg->block_count);
+                lfs->block_count);
         lfs->free.i = 0;
         lfs_alloc_ack(lfs);
 
@@ -4265,7 +4283,7 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
         lfs_superblock_t superblock = {
             .version     = lfs_fs_disk_version(lfs),
             .block_size  = lfs->cfg->block_size,
-            .block_count = lfs->cfg->block_count,
+            .block_count = lfs->block_count,
             .name_max    = lfs->name_max,
             .file_max    = lfs->file_max,
             .attr_max    = lfs->attr_max,
@@ -4422,13 +4440,17 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
                 lfs->attr_max = superblock.attr_max;
             }
 
-            if (superblock.block_count != lfs->cfg->block_count) {
+            // this is where we get the block_count from disk if block_count=0
+            if (lfs->cfg->block_count
+                    && superblock.block_count != lfs->cfg->block_count) {
                 LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")",
                         superblock.block_count, lfs->cfg->block_count);
                 err = LFS_ERR_INVAL;
                 goto cleanup;
             }
 
+            lfs->block_count = superblock.block_count;
+
             if (superblock.block_size != lfs->cfg->block_size) {
                 LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")",
                         superblock.block_size, lfs->cfg->block_size);
@@ -4444,12 +4466,6 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
         }
     }
 
-    // found superblock?
-    if (lfs_pair_isnull(lfs->root)) {
-        err = LFS_ERR_INVAL;
-        goto cleanup;
-    }
-
     // update littlefs with gstate
     if (!lfs_gstate_iszero(&lfs->gstate)) {
         LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32,
@@ -4462,7 +4478,7 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
 
     // setup free lookahead, to distribute allocations uniformly across
     // boots, we start the allocator at a random location
-    lfs->free.off = lfs->seed % lfs->cfg->block_count;
+    lfs->free.off = lfs->seed % lfs->block_count;
     lfs_alloc_drop(lfs);
 
     return 0;
@@ -4506,6 +4522,10 @@ static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
         fsinfo->disk_version = superblock.version;
     }
 
+    // filesystem geometry
+    fsinfo->block_size = lfs->cfg->block_size;
+    fsinfo->block_count = lfs->block_count;
+
     // other on-disk configuration, we cache all of these for internal use
     fsinfo->name_max = lfs->name_max;
     fsinfo->file_max = lfs->file_max;
@@ -4771,7 +4791,7 @@ static int lfs_fs_desuperblock(lfs_t *lfs) {
     lfs_superblock_t superblock = {
         .version     = lfs_fs_disk_version(lfs),
         .block_size  = lfs->cfg->block_size,
-        .block_count = lfs->cfg->block_count,
+        .block_count = lfs->block_count,
         .name_max    = lfs->name_max,
         .file_max    = lfs->file_max,
         .attr_max    = lfs->attr_max,
@@ -5025,6 +5045,44 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) {
     return size;
 }
 
+#ifndef LFS_READONLY
+int lfs_fs_rawgrow(lfs_t *lfs, lfs_size_t block_count) {
+    // shrinking is not supported
+    LFS_ASSERT(block_count >= lfs->block_count);
+
+    if (block_count > lfs->block_count) {
+        lfs->block_count = block_count;
+
+        // fetch the root
+        lfs_mdir_t root;
+        int err = lfs_dir_fetch(lfs, &root, lfs->root);
+        if (err) {
+            return err;
+        }
+
+        // update the superblock
+        lfs_superblock_t superblock;
+        lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                &superblock);
+        if (tag < 0) {
+            return tag;
+        }
+        lfs_superblock_fromle32(&superblock);
+
+        superblock.block_count = lfs->block_count;
+
+        lfs_superblock_tole32(&superblock);
+        err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+                {tag, &superblock}));
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+#endif
 
 #ifdef LFS_MIGRATE
 ////// Migration from littelfs v1 below this //////
@@ -5449,6 +5507,10 @@ static int lfs1_unmount(lfs_t *lfs) {
 /// v1 migration ///
 static int lfs_rawmigrate(lfs_t *lfs, const struct lfs_config *cfg) {
     struct lfs1 lfs1;
+
+    // Indeterminate filesystem size not allowed for migration.
+    LFS_ASSERT(cfg->block_count != 0);
+
     int err = lfs1_mount(lfs, &lfs1, cfg);
     if (err) {
         return err;
@@ -6188,6 +6250,22 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) {
     return err;
 }
 
+#ifndef LFS_READONLY
+int lfs_fs_gc(lfs_t *lfs) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs);
+
+    err = lfs_fs_rawgc(lfs);
+
+    LFS_TRACE("lfs_fs_gc -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
 #ifndef LFS_READONLY
 int lfs_fs_mkconsistent(lfs_t *lfs) {
     int err = LFS_LOCK(lfs->cfg);
@@ -6204,6 +6282,22 @@ int lfs_fs_mkconsistent(lfs_t *lfs) {
 }
 #endif
 
+#ifndef LFS_READONLY
+int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_grow(%p, %"PRIu32")", (void*)lfs, block_count);
+
+    err = lfs_fs_rawgrow(lfs, block_count);
+
+    LFS_TRACE("lfs_fs_grow -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
 #ifdef LFS_MIGRATE
 int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
     int err = LFS_LOCK(cfg);

+ 30 - 1
lfs.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 LFS_VERSION 0x00020007
+#define LFS_VERSION 0x00020008
 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 
@@ -293,6 +293,12 @@ struct lfs_fsinfo {
     // On-disk version.
     uint32_t disk_version;
 
+    // Size of a logical block in bytes.
+    lfs_size_t block_size;
+
+    // Number of logical blocks in filesystem.
+    lfs_size_t block_count;
+
     // Upper limit on the length of file names in bytes.
     lfs_size_t name_max;
 
@@ -433,6 +439,7 @@ typedef struct lfs {
     } free;
 
     const struct lfs_config *cfg;
+    lfs_size_t block_count;
     lfs_size_t name_max;
     lfs_size_t file_max;
     lfs_size_t attr_max;
@@ -705,6 +712,18 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs);
 // Returns a negative error code on failure.
 int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_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 lfs_fs_gc(lfs_t *lfs);
+
 #ifndef LFS_READONLY
 // Attempt to make the filesystem consistent and ready for writing
 //
@@ -717,6 +736,16 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
 int lfs_fs_mkconsistent(lfs_t *lfs);
 #endif
 
+#ifndef LFS_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 lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);
+#endif
+
 #ifndef LFS_READONLY
 #ifdef LFS_MIGRATE
 // Attempts to migrate a previous version of littlefs

+ 16 - 12
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 lfs_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 = lfs_emubd_createcfg(&cfg, bench_disk_path, &bdcfg);
+    int err = lfs_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;

+ 19 - 13
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/lfs_max(BLOCK_SIZE/ERASE_SIZE,1))\
     BENCH_DEF(CACHE_SIZE,         lfs_max(64,lfs_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, LFS_EMUBD_POWERLOSS_NOOP)
 
 #define BENCH_GEOMETRY_DEFINE_COUNT 4
-#define BENCH_IMPLICIT_DEFINE_COUNT 11
+#define BENCH_IMPLICIT_DEFINE_COUNT 13
 
 
 #endif

+ 36 - 16
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 lfs_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 = lfs_emubd_createcfg(&cfg, test_disk_path, &bdcfg);
+    int err = lfs_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 lfs_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 = lfs_emubd_createcfg(&cfg, test_disk_path, &bdcfg);
+    int err = lfs_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 lfs_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 = lfs_emubd_createcfg(&cfg, test_disk_path, &bdcfg);
+    int err = lfs_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 lfs_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 = lfs_emubd_createcfg(&cfg, test_disk_path, &bdcfg);
+    int err = lfs_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 lfs_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 = lfs_emubd_createcfg(&cfg, test_disk_path, &bdcfg);
+    int err = lfs_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;

+ 20 - 14
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/lfs_max(BLOCK_SIZE/ERASE_SIZE,1)) \
     TEST_DEF(CACHE_SIZE,         lfs_max(64,lfs_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, LFS_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

+ 26 - 8
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"};
     lfs_file_t files[FILES];
@@ -24,6 +25,9 @@ code = '''
                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
     }
     for (int n = 0; n < FILES; n++) {
+        if (GC) {
+            lfs_fs_gc(&lfs) => 0;
+        }
         size_t size = strlen(names[n]);
         for (lfs_size_t i = 0; i < SIZE; i += size) {
             lfs_file_write(&lfs, &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) {
+                lfs_fs_gc(&lfs) => 0;
+            }
             lfs_file_write(&lfs, &file, buffer, size) => size;
         }
         lfs_file_close(&lfs, &file) => 0;
@@ -247,6 +255,9 @@ code = '''
     }
     res => LFS_ERR_NOSPC;
 
+    // note that lfs_fs_gc should not error here
+    lfs_fs_gc(&lfs) => 0;
+
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
@@ -298,6 +309,9 @@ code = '''
     }
     res => LFS_ERR_NOSPC;
 
+    // note that lfs_fs_gc should not error here
+    lfs_fs_gc(&lfs) => 0;
+
     lfs_file_close(&lfs, &file) => 0;
     lfs_unmount(&lfs) => 0;
 
@@ -337,6 +351,8 @@ code = '''
         count += 1;
     }
     err => LFS_ERR_NOSPC;
+    // note that lfs_fs_gc should not error here
+    lfs_fs_gc(&lfs) => 0;
     lfs_file_close(&lfs, &file) => 0;
 
     lfs_remove(&lfs, "exhaustion") => 0;
@@ -435,6 +451,8 @@ code = '''
             break;
         }
     }
+    // note that lfs_fs_gc should not error here
+    lfs_fs_gc(&lfs) => 0;
     lfs_file_close(&lfs, &file) => 0;
 
     lfs_unmount(&lfs) => 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 = '''
     lfs_t lfs;
     lfs_format(&lfs, 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 = '''
     lfs_t lfs;
     lfs_format(&lfs, 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 = '''
     lfs_t lfs;
     lfs_format(&lfs, 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 = '''
     lfs_t lfs;
     lfs_format(&lfs, cfg) => 0;

+ 3 - 3
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 = [

+ 5 - 5
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 = [
     'LFS_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 = [
     'LFS_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

+ 267 - 0
tests/test_superblocks.toml

@@ -14,6 +14,21 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
+# mount/unmount from interpretting a previous superblock block_count
+[cases.test_superblocks_mount_unknown_block_count]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    memset(&lfs, 0, sizeof(lfs));
+    struct lfs_config tweaked_cfg = *cfg;
+    tweaked_cfg.block_count = 0;
+    lfs_mount(&lfs, &tweaked_cfg) => 0;
+    assert(lfs.block_count == cfg->block_count);
+    lfs_unmount(&lfs) => 0;
+'''
+
+
 # reentrant format
 [cases.test_superblocks_reentrant_format]
 reentrant = true
@@ -197,3 +212,255 @@ code = '''
     assert(info.type == LFS_TYPE_REG);
     lfs_unmount(&lfs) => 0;
 '''
+
+# mount with unknown block_count
+[cases.test_superblocks_unknown_blocks]
+code = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // known block_size/block_count
+    cfg->block_size = BLOCK_SIZE;
+    cfg->block_count = BLOCK_COUNT;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_fsinfo fsinfo;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // unknown block_count
+    cfg->block_size = BLOCK_SIZE;
+    cfg->block_count = 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // do some work
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "test",
+            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+    uint8_t buffer[256];
+    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    assert(memcmp(buffer, "hello!", 6) == 0);
+    lfs_unmount(&lfs) => 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 = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    // known block_size/block_count
+    cfg->block_size = BLOCK_SIZE;
+    cfg->block_count = BLOCK_COUNT;
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_fsinfo fsinfo;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // incorrect block_count
+    cfg->block_size = BLOCK_SIZE;
+    cfg->block_count = ERASE_COUNT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+
+    // unknown block_count
+    cfg->block_size = BLOCK_SIZE;
+    cfg->block_count = 0;
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // do some work
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "test",
+            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+    uint8_t buffer[256];
+    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    assert(memcmp(buffer, "hello!", 6) == 0);
+    lfs_unmount(&lfs) => 0;
+'''
+
+# mount with more blocks than the erase_count
+[cases.test_superblocks_more_blocks]
+defines.FORMAT_BLOCK_COUNT = '2*ERASE_COUNT'
+in = 'lfs.c'
+code = '''
+    lfs_t lfs;
+    lfs_init(&lfs, cfg) => 0;
+    lfs.block_count = BLOCK_COUNT;
+
+    lfs_mdir_t root = {
+        .pair = {0, 0}, // make sure this goes into block 0
+        .rev = 0,
+        .off = sizeof(uint32_t),
+        .etag = 0xffffffff,
+        .count = 0,
+        .tail = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .erased = false,
+        .split = false,
+    };
+
+    lfs_superblock_t superblock = {
+        .version     = LFS_DISK_VERSION,
+        .block_size  = BLOCK_SIZE,
+        .block_count = FORMAT_BLOCK_COUNT,
+        .name_max    = LFS_NAME_MAX,
+        .file_max    = LFS_FILE_MAX,
+        .attr_max    = LFS_ATTR_MAX,
+    };
+
+    lfs_superblock_tole32(&superblock);
+    lfs_dir_commit(&lfs, &root, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
+            {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
+            {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                &superblock})) => 0;
+    lfs_deinit(&lfs) => 0;
+
+    // known block_size/block_count
+    cfg->block_size = BLOCK_SIZE;
+    cfg->block_count = BLOCK_COUNT;
+    lfs_mount(&lfs, cfg) => LFS_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 = '''
+    lfs_t lfs;
+    lfs_format(&lfs, cfg) => 0;
+
+    if (KNOWN_BLOCK_COUNT) {
+        cfg->block_count = BLOCK_COUNT;
+    } else {
+        cfg->block_count = 0;
+    }
+
+    // mount with block_size < erase_size
+    lfs_mount(&lfs, cfg) => 0;
+    struct lfs_fsinfo fsinfo;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // same size is a noop
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, BLOCK_COUNT) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT);
+    lfs_unmount(&lfs) => 0;
+
+    // grow to new size
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    if (KNOWN_BLOCK_COUNT) {
+        cfg->block_count = BLOCK_COUNT_2;
+    } else {
+        cfg->block_count = 0;
+    }
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    // mounting with the previous size should fail
+    cfg->block_count = BLOCK_COUNT;
+    lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+
+    if (KNOWN_BLOCK_COUNT) {
+        cfg->block_count = BLOCK_COUNT_2;
+    } else {
+        cfg->block_count = 0;
+    }
+
+    // same size is a noop
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_grow(&lfs, BLOCK_COUNT_2) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_unmount(&lfs) => 0;
+
+    // do some work
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_file_t file;
+    lfs_file_open(&lfs, &file, "test",
+            LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
+    lfs_file_write(&lfs, &file, "hello!", 6) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+
+    lfs_mount(&lfs, cfg) => 0;
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.block_size == BLOCK_SIZE);
+    assert(fsinfo.block_count == BLOCK_COUNT_2);
+    lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+    uint8_t buffer[256];
+    lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
+    lfs_file_close(&lfs, &file) => 0;
+    assert(memcmp(buffer, "hello!", 6) == 0);
+    lfs_unmount(&lfs) => 0;
+'''