diff --git a/lfs.c b/lfs.c index 2f6174e1..516e9b82 100644 --- a/lfs.c +++ b/lfs.c @@ -2177,12 +2177,6 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd, break; } - // toss our cksum into the filesystem seed for - // pseudorandom numbers, note we use another cksum here - // as a collection function because it is sufficiently - // random and convenient - lfs->seed = lfs_crc32c(lfs->seed, &cksum, sizeof(uint32_t)); - // save what we've found so far rbyd->eoff = ((lfs_size_t)parity_ << (8*sizeof(lfs_size_t)-1)) @@ -5541,6 +5535,45 @@ static int lfsr_fs_consumegdelta(lfs_t *lfs, const lfsr_mdir_t *mdir) { } +/// Revision count things /// + +// in mdirs, our revision count is broken down into three parts: +// +// vvvvrrrr rrrrrrnn nnnnnnnn nnnnnnnn +// '-.''----.----''---------.--------' +// '------|---------------|---------- 4-bit relocation revision +// '---------------|---------- recycle-bits recycle counter +// '---------- pseudorandom nonce + +static inline uint32_t lfsr_rev_init(lfs_t *lfs, uint32_t rev) { + // we really only care about the top revision bits here + rev &= ~((1 << 28)-1); + // increment revision + rev += 1 << 28; + // xor in a pseudorandom nonce + rev ^= ((1 << (28-lfs_smax32(lfs->recycle_bits, 0)))-1) & lfs->seed; + return rev; +} + +static inline bool lfsr_rev_needsrelocation(lfs_t *lfs, uint32_t rev) { + if (lfs->recycle_bits == -1) { + return false; + } + + // does out recycle counter overflow? + uint32_t rev_ = rev + (1 << (28-lfs_smax32(lfs->recycle_bits, 0))); + return (rev_ >> 28) != (rev >> 28); +} + +static inline uint32_t lfsr_rev_inc(lfs_t *lfs, uint32_t rev) { + // increment recycle counter/revision + rev += 1 << (28-lfs_smax32(lfs->recycle_bits, 0)); + // xor in a pseudorandom nonce + rev ^= ((1 << (28-lfs_smax32(lfs->recycle_bits, 0)))-1) & lfs->seed; + return rev; +} + + /// Metadata pair stuff /// @@ -5868,11 +5901,8 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_smid_t mid) { // note we allow corrupt errors here, as long as they are consistent rev = (err != LFS_ERR_CORRUPT) ? lfs_fromle32_(&rev) : 0; - // align revision count in new mdirs to our block_cycles, this makes - // sure we don't immediately try to relocate the mdir - if (lfs->cfg->block_cycles > 0) { - rev = lfs_alignup(rev+1, lfs->cfg->block_cycles)-1; - } + // reset recycle bits in revision count and increment + rev = lfsr_rev_init(lfs, rev); // erase, preparing for compact err = lfsr_bd_erase(lfs, mdir->rbyd.blocks[0]); @@ -5880,9 +5910,8 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_smid_t mid) { return err; } - // increment our revision count and write it to our rbyd - // TODO rev things - err = lfsr_rbyd_appendrev(lfs, &mdir->rbyd, rev + 1); + // write our revision count + err = lfsr_rbyd_appendrev(lfs, &mdir->rbyd, rev); if (err) { return err; } @@ -5906,10 +5935,7 @@ static int lfsr_mdir_swap__(lfs_t *lfs, lfsr_mdir_t *mdir_, rev = (err != LFS_ERR_CORRUPT) ? lfs_fromle32_(&rev) : 0; // decide if we need to relocate - if (!force - && lfs->cfg->block_cycles > 0 - // TODO rev things - && (rev + 1) % lfs->cfg->block_cycles == 0) { + if (!force && lfsr_rev_needsrelocation(lfs, rev)) { // alloc a new mdir return lfsr_mdir_alloc__(lfs, mdir_, mdir->mid); } @@ -5929,8 +5955,7 @@ static int lfsr_mdir_swap__(lfs_t *lfs, lfsr_mdir_t *mdir_, } // increment our revision count and write it to our rbyd - // TODO rev things - err = lfsr_rbyd_appendrev(lfs, &mdir_->rbyd, rev + 1); + err = lfsr_rbyd_appendrev(lfs, &mdir_->rbyd, lfsr_rev_inc(lfs, rev)); if (err) { return err; } @@ -7037,6 +7062,9 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, // success? update in-device state, we must not error at this point + // toss our cksum into the filesystem seed for pseudorandom numbers + lfs->seed ^= mdir_.rbyd.cksum; + // update any gstate changes lfsr_fs_commitgdelta(lfs); @@ -8346,6 +8374,10 @@ static int lfsr_mountinited(lfs_t *lfs) { } } + // toss our cksum into the filesystem seed for pseudorandom + // numbers + lfs->seed ^= tinfo.u.mdir.rbyd.cksum; + // collect any gdeltas from this mdir err = lfsr_fs_consumegdelta(lfs, &tinfo.u.mdir); if (err) { @@ -15248,14 +15280,17 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { // // check that the block size is large enough to fit ctz pointers // LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) // <= lfs->cfg->block_size); +// +// // block_cycles = 0 is no longer supported. +// // +// // block_cycles is the number of erase cycles before littlefs evicts +// // metadata logs as a part of wear leveling. Suggested values are in the +// // range of 100-1000, or set block_cycles to -1 to disable block-level +// // wear-leveling. +// LFS_ASSERT(lfs->cfg->block_cycles != 0); - // block_cycles = 0 is no longer supported. - // - // block_cycles is the number of erase cycles before littlefs evicts - // metadata logs as a part of wear leveling. Suggested values are in the - // range of 100-1000, or set block_cycles to -1 to disable block-level - // wear-leveling. - LFS_ASSERT(lfs->cfg->block_cycles != 0); + // block_recycles should not be zero, use -1 to disable + LFS_ASSERT(lfs->cfg->block_recycles != 0); // inline_size must be <= block_size/4 LFS_ASSERT(lfs->cfg->inline_size <= lfs->cfg->block_size/4); @@ -15341,6 +15376,19 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { // TODO do we need to recalculate these after mount? + // find the number of bits to use for recycle counters + // + // Multiply by 2, since we alternate which metadata block we erase each + // compaction, and limit to 28-bits so we always have some bits to + // determine the most recent revision. + if (lfs->cfg->block_recycles != -1) { + lfs->recycle_bits = lfs_min( + lfs_nlog2(2*lfs->cfg->block_recycles+1)-1, + 28); + } else { + lfs->recycle_bits = -1; + } + // calculate the upper-bound cost of a single rbyd attr after compaction // // Note that with rebalancing during compaction, we know the number diff --git a/lfs.h b/lfs.h index 5a52976b..c974e09e 100644 --- a/lfs.h +++ b/lfs.h @@ -206,13 +206,13 @@ struct lfs_config { // Number of erasable blocks on the device. lfs_size_t block_count; - // Number of erase cycles before littlefs evicts metadata logs and moves - // the metadata to another block. Suggested values are in the - // range 100-1000, with large values having better performance at the cost - // of less consistent wear distribution. + // Number of erase cycles before metadata blocks are relocated for + // wear-leveling. Suggested values are in the range 16-1024. Larger values + // relocate less frequently, improving average performance, at the cost + // of worse wear distribution. Note this is rounded down to a power-of-2. // // Set to -1 to disable block-level wear-leveling. - int32_t block_cycles; + int32_t block_recycles; // Size of the read cache in bytes. Larger buffers can improve // performance by storing more data and reducing the number of disk @@ -581,6 +581,7 @@ typedef struct lfs { // purpose flags field? this has been useful for lfsr_file_t bool hasorphans; + int8_t recycle_bits; uint8_t attr_estimate; uint8_t mleaf_bits; diff --git a/runners/bench_runner.h b/runners/bench_runner.h index 9275ce40..84edbca4 100644 --- a/runners/bench_runner.h +++ b/runners/bench_runner.h @@ -115,6 +115,7 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size); BENCH_DEFINE(BLOCK_SIZE, 4096 ) \ BENCH_DEFINE(BLOCK_COUNT, DISK_SIZE/BLOCK_SIZE ) \ BENCH_DEFINE(DISK_SIZE, 1024*1024 ) \ + BENCH_DEFINE(BLOCK_RECYCLES, -1 ) \ BENCH_DEFINE(RCACHE_SIZE, LFS_MAX(16, READ_SIZE) ) \ BENCH_DEFINE(PCACHE_SIZE, LFS_MAX(16, PROG_SIZE) ) \ BENCH_DEFINE(FBUFFER_SIZE, 16 ) \ @@ -123,7 +124,6 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size); BENCH_DEFINE(SHRUB_SIZE, INLINE_SIZE ) \ BENCH_DEFINE(FRAGMENT_SIZE, BLOCK_SIZE/8 ) \ BENCH_DEFINE(CRYSTAL_THRESH, BLOCK_SIZE/8 ) \ - BENCH_DEFINE(BLOCK_CYCLES, -1 ) \ BENCH_DEFINE(ERASE_VALUE, 0xff ) \ BENCH_DEFINE(ERASE_CYCLES, 0 ) \ BENCH_DEFINE(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR ) \ @@ -142,7 +142,7 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size); .prog_size = PROG_SIZE, \ .block_size = BLOCK_SIZE, \ .block_count = BLOCK_COUNT, \ - .block_cycles = BLOCK_CYCLES, \ + .block_recycles = BLOCK_RECYCLES, \ .rcache_size = RCACHE_SIZE, \ .pcache_size = PCACHE_SIZE, \ .fbuffer_size = FBUFFER_SIZE, \ diff --git a/runners/test_runner.h b/runners/test_runner.h index 0d676300..a2e3c3f0 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -99,6 +99,7 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size); TEST_DEFINE(BLOCK_SIZE, 4096 ) \ TEST_DEFINE(BLOCK_COUNT, DISK_SIZE/BLOCK_SIZE ) \ TEST_DEFINE(DISK_SIZE, 1024*1024 ) \ + TEST_DEFINE(BLOCK_RECYCLES, -1 ) \ TEST_DEFINE(RCACHE_SIZE, LFS_MAX(16, READ_SIZE) ) \ TEST_DEFINE(PCACHE_SIZE, LFS_MAX(16, PROG_SIZE) ) \ TEST_DEFINE(FBUFFER_SIZE, 16 ) \ @@ -107,7 +108,6 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size); TEST_DEFINE(SHRUB_SIZE, INLINE_SIZE ) \ TEST_DEFINE(FRAGMENT_SIZE, BLOCK_SIZE/8 ) \ TEST_DEFINE(CRYSTAL_THRESH, BLOCK_SIZE/8 ) \ - TEST_DEFINE(BLOCK_CYCLES, -1 ) \ TEST_DEFINE(ERASE_VALUE, 0xff ) \ TEST_DEFINE(ERASE_CYCLES, 0 ) \ TEST_DEFINE(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR ) \ @@ -126,7 +126,7 @@ void test_permutation(size_t i, uint32_t *buffer, size_t size); .prog_size = PROG_SIZE, \ .block_size = BLOCK_SIZE, \ .block_count = BLOCK_COUNT, \ - .block_cycles = BLOCK_CYCLES, \ + .block_recycles = BLOCK_RECYCLES, \ .rcache_size = RCACHE_SIZE, \ .pcache_size = PCACHE_SIZE, \ .fbuffer_size = FBUFFER_SIZE, \ diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index 3abe1565..8c47c37a 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -610,7 +610,7 @@ def main(disk, roots=None, *, # fetch the root btree = Rbyd.fetch(f, block_size, roots, trunk) - print('btree %s, rev %d, weight %d, cksum %08x' % ( + print('btree %s, rev %08x, weight %d, cksum %08x' % ( btree.addr(), btree.rev, btree.weight, btree.cksum)) # look up a bid, while keeping track of the search path diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index ae1f1746..d9a7a6cf 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -1884,7 +1884,7 @@ def main(disk, mroots=None, *, #### actual debugging begins here # print some information about the filesystem - print('littlefs v%s.%s %dx%d %s, rev %d, weight %d.%d' % ( + print('littlefs v%s.%s %dx%d %s, rev %08x, weight %d.%d' % ( config.version[0] if config.version[0] is not None else '?', config.version[1] if config.version[1] is not None else '?', (config.geometry[0] or 0), (config.geometry[1] or 0), diff --git a/scripts/dbgmtree.py b/scripts/dbgmtree.py index 6f513794..a2460a3b 100755 --- a/scripts/dbgmtree.py +++ b/scripts/dbgmtree.py @@ -1500,7 +1500,7 @@ def main(disk, mroots=None, *, #### actual debugging begins here # print some information about the mtree - print('mtree %s, rev %d, weight %d.%d, cksum %08x' % ( + print('mtree %s, rev %08x, weight %d.%d, cksum %08x' % ( mroot.addr(), mroot.rev, bweight//mleaf_weight, 1*mleaf_weight, diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index 42c5bc23..4e3cb8bd 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -1030,7 +1030,7 @@ def main(disk, blocks=None, *, weights[i], cksums[i]) - print('rbyd %s, rev %d, size %d, weight %d, cksum %08x' % ( + print('rbyd %s, rev %08x, size %d, weight %d, cksum %08x' % ( '0x%x.%x' % (block, trunk_) if len(blocks) == 1 else '0x{%x,%s}.%x' % ( diff --git a/tests/test_mtree.toml b/tests/test_mtree.toml index 30d7c425..aa041c2b 100644 --- a/tests/test_mtree.toml +++ b/tests/test_mtree.toml @@ -1136,7 +1136,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1221,7 +1221,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1318,7 +1318,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1415,7 +1415,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1469,7 +1469,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 # force our block to compact by setting prog_size=block_size, we don't have # an easy way to force the intermediary mroots to compact otherwise defines.PROG_SIZE = 'BLOCK_SIZE' @@ -1540,7 +1540,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1603,7 +1603,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1705,7 +1705,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1824,7 +1824,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1914,7 +1914,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -1990,7 +1990,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -2076,7 +2076,7 @@ code = ''' [cases.test_mtree_relocate_fuzz] defines.N = [5, 10, 20, 40] defines.FORCE_COMPACTION = [false, true] -defines.BLOCK_CYCLES = [5, 2, 1] +defines.BLOCK_RECYCLES = [5, 2, 1] defines.SEED = 'range(500)' in = 'lfs.c' code = ''' @@ -2555,7 +2555,7 @@ code = ''' [cases.test_mtree_opened_extend] # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -2613,7 +2613,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -2702,7 +2702,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -3528,7 +3528,7 @@ code = ''' [cases.test_mtree_traversal_extend] defines.VALIDATE = [false, true] # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -4003,7 +4003,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 in = 'lfs.c' code = ''' lfs_t lfs; @@ -4057,7 +4057,7 @@ code = ''' # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' # make it so blocks relocate every two compacts -defines.BLOCK_CYCLES = 2 +defines.BLOCK_RECYCLES = 1 # force our block to compact by setting prog_size=block_size, we don't have # any way to indirectly force the intermediary mroots to compact otherwise defines.PROG_SIZE = 'BLOCK_SIZE'