diff --git a/lfs.c b/lfs.c index 48a10e8e..63be7fb8 100644 --- a/lfs.c +++ b/lfs.c @@ -26,6 +26,100 @@ enum { }; +/// Mapping from logical to physical erase size /// + +static int lfs_bd_rawread(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + LFS_ASSERT(block < lfs->block_count); + LFS_ASSERT(off + size <= lfs->block_size); + LFS_ASSERT(size % lfs->cfg->read_size == 0); + + // adjust to physical erase size + block = (block * (lfs->block_size/lfs->erase_size)) + + (off / lfs->erase_size); + off = off % lfs->erase_size; + uint8_t *buffer_ = buffer; + + // read in erase_size chunks + while (size > 0) { + lfs_size_t delta = lfs_min(size, off + lfs->erase_size); + LFS_ASSERT(block <= lfs->erase_count); + LFS_ASSERT(off + size <= lfs->erase_size); + LFS_ASSERT(size % lfs->cfg->read_size == 0); + int err = lfs->cfg->read(lfs->cfg, block, off, buffer_, delta); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + off += delta; + if (off == lfs->erase_size) { + block += 1; + off = 0; + } + size -= delta; + } + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_bd_rawprog(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + LFS_ASSERT(block < lfs->block_count); + LFS_ASSERT(off + size <= lfs->block_size); + LFS_ASSERT(size % lfs->cfg->prog_size == 0); + + // adjust to physical erase size + block = (block * (lfs->block_size/lfs->erase_size)) + + (off / lfs->erase_size); + off = off % lfs->erase_size; + uint8_t *buffer_ = buffer; + + // prog in erase_size chunks + while (size > 0) { + lfs_size_t delta = lfs_min(size, off + lfs->erase_size); + LFS_ASSERT(block <= lfs->erase_count); + LFS_ASSERT(off + size <= lfs->erase_size); + LFS_ASSERT(size % lfs->cfg->prog_size == 0); + int err = lfs->cfg->prog(lfs->cfg, block, off, buffer_, delta); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + off += delta; + if (off == lfs->erase_size) { + block += 1; + off = 0; + } + size -= delta; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + LFS_ASSERT(block < lfs->block_count); + + // adjust to physical erase size + block = block * (lfs->block_size/lfs->erase_size); + for (lfs_block_t i = 0; i < lfs->block_size/lfs->erase_size; i++) { + LFS_ASSERT(block + i <= lfs->erase_count); + int err = lfs->cfg->erase(lfs->cfg, block + i); + LFS_ASSERT(err <= 0); + if (err < 0) { + return err; + } + } + + return 0; +} +#endif + + /// Caching block device operations /// static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { @@ -46,8 +140,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 (block >= lfs->block_count || + off+size > lfs->block_size) { return LFS_ERR_CORRUPT; } @@ -92,7 +186,7 @@ static int lfs_bd_read(lfs_t *lfs, size >= lfs->cfg->read_size) { // bypass cache? diff = lfs_aligndown(diff, lfs->cfg->read_size); - int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + int err = lfs_bd_rawread(lfs, block, off, data, diff); if (err) { return err; } @@ -104,18 +198,16 @@ static int lfs_bd_read(lfs_t *lfs, } // load to cache, first condition can no longer fail - LFS_ASSERT(block < lfs->cfg->block_count); rcache->block = block; rcache->off = lfs_aligndown(off, lfs->cfg->read_size); rcache->size = lfs_min( lfs_min( lfs_alignup(off+hint, lfs->cfg->read_size), - lfs->cfg->block_size) + lfs->block_size) - rcache->off, lfs->cfg->cache_size); - int err = lfs->cfg->read(lfs->cfg, rcache->block, + int err = lfs_bd_rawread(lfs, rcache->block, rcache->off, rcache->buffer, rcache->size); - LFS_ASSERT(err <= 0); if (err) { return err; } @@ -155,11 +247,9 @@ static int lfs_bd_cmp(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_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); - int err = lfs->cfg->prog(lfs->cfg, pcache->block, + int err = lfs_bd_rawprog(lfs, pcache->block, pcache->off, pcache->buffer, diff); - LFS_ASSERT(err <= 0); if (err) { return err; } @@ -208,8 +298,8 @@ 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(off + size <= lfs->cfg->block_size); + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count); + LFS_ASSERT(off + size <= lfs->block_size); while (size > 0) { if (block == pcache->block && @@ -250,15 +340,6 @@ static int lfs_bd_prog(lfs_t *lfs, } #endif -#ifndef LFS_READONLY -static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { - LFS_ASSERT(block < lfs->cfg->block_count); - int err = lfs->cfg->erase(lfs->cfg, block); - LFS_ASSERT(err <= 0); - return err; -} -#endif - /// Small type-level utilities /// // operations on block pairs @@ -528,7 +609,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); @@ -542,7 +623,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 @@ -563,7 +644,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 @@ -586,7 +667,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { } lfs->free.off = (lfs->free.off + lfs->free.size) - % lfs->cfg->block_count; + % lfs->block_count; lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack); lfs->free.i = 0; @@ -676,7 +757,7 @@ static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, lfs_tag_t gmask, lfs_tag_t gtag, lfs_off_t off, void *buffer, lfs_size_t size) { uint8_t *data = buffer; - if (off+size > lfs->cfg->block_size) { + if (off+size > lfs->block_size) { return LFS_ERR_CORRUPT; } @@ -998,7 +1079,7 @@ 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 (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count) { return LFS_ERR_CORRUPT; } @@ -1044,7 +1125,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, lfs_tag_t tag; off += lfs_tag_dsize(ptag); int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, + NULL, &lfs->rcache, lfs->block_size, dir->pair[0], off, &tag, sizeof(tag)); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -1063,7 +1144,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC && dir->off % lfs->cfg->prog_size == 0); break; - } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + } else if (off + lfs_tag_dsize(tag) > lfs->block_size) { dir->erased = false; break; } @@ -1074,7 +1155,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // check the crc attr uint32_t dcrc; err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, + NULL, &lfs->rcache, lfs->block_size, dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -1117,7 +1198,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) { uint8_t dat; err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, + NULL, &lfs->rcache, lfs->block_size, dir->pair[0], off+j, &dat, 1); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -1150,7 +1231,7 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, tempsplit = (lfs_tag_chunk(tag) & 1); err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, + NULL, &lfs->rcache, lfs->block_size, dir->pair[0], off+sizeof(tag), &temptail, 8); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -1770,7 +1851,7 @@ static int lfs_dir_compact(lfs_t *lfs, .begin = 0, .end = (lfs->cfg->metadata_max ? - lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, + lfs->cfg->metadata_max : lfs->block_size) - 8, }; // erase block to write to @@ -1940,11 +2021,11 @@ static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir, // if (end - split < 0xff && size <= lfs_min( - lfs->cfg->block_size - 40, + lfs->block_size - 40, lfs_alignup( (lfs->cfg->metadata_max ? lfs->cfg->metadata_max - : lfs->cfg->block_size)/2, + : lfs->block_size)/2, lfs->cfg->prog_size))) { break; } @@ -1986,7 +2067,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); @@ -2056,7 +2137,7 @@ static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir, .begin = dir->off, .end = (lfs->cfg->metadata_max ? - lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, + lfs->cfg->metadata_max : lfs->block_size) - 8, }; // traverse attrs that need to be written out @@ -2647,7 +2728,7 @@ static int lfs_dir_rawrewind(lfs_t *lfs, lfs_dir_t *dir) { /// File index list operations /// static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { lfs_off_t size = *off; - lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t b = lfs->block_size - 2*4; lfs_off_t i = size / b; if (i == 0) { return 0; @@ -2725,7 +2806,7 @@ static int lfs_ctz_extend(lfs_t *lfs, noff = noff + 1; // just copy out the last block if it is incomplete - if (noff != lfs->cfg->block_size) { + if (noff != lfs->block_size) { for (lfs_off_t i = 0; i < noff; i++) { uint8_t data; err = lfs_bd_read(lfs, @@ -3265,7 +3346,7 @@ static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, while (nsize > 0) { // check if we need a new block if (!(file->flags & LFS_F_READING) || - file->off == lfs->cfg->block_size) { + file->off == lfs->block_size) { if (!(file->flags & LFS_F_INLINE)) { int err = lfs_ctz_find(lfs, NULL, &file->cache, file->ctz.head, file->ctz.size, @@ -3282,10 +3363,10 @@ static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, } // read as much as we can in current block - lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + lfs_size_t diff = lfs_min(nsize, lfs->block_size - file->off); if (file->flags & LFS_F_INLINE) { int err = lfs_dir_getread(lfs, &file->m, - NULL, &file->cache, lfs->cfg->block_size, + NULL, &file->cache, lfs->block_size, LFS_MKTAG(0xfff, 0x1ff, 0), LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), file->off, data, diff); @@ -3294,7 +3375,7 @@ static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, } } else { int err = lfs_bd_read(lfs, - NULL, &file->cache, lfs->cfg->block_size, + NULL, &file->cache, lfs->block_size, file->block, file->off, data, diff); if (err) { return err; @@ -3339,7 +3420,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, lfs_min(0x3fe, lfs_min( lfs->cfg->cache_size, (lfs->cfg->metadata_max ? - lfs->cfg->metadata_max : lfs->cfg->block_size) / 8))) { + lfs->cfg->metadata_max : lfs->block_size) / 8))) { // inline file doesn't fit anymore int err = lfs_file_outline(lfs, file); if (err) { @@ -3351,7 +3432,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, while (nsize > 0) { // check if we need a new block if (!(file->flags & LFS_F_WRITING) || - file->off == lfs->cfg->block_size) { + file->off == lfs->block_size) { if (!(file->flags & LFS_F_INLINE)) { if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { // find out which block we're extending from @@ -3385,7 +3466,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, } // program as much as we can in current block - lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + lfs_size_t diff = lfs_min(nsize, lfs->block_size - file->off); while (true) { int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true, file->block, file->off, data, diff); @@ -3915,20 +3996,40 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { int err = 0; // validate that the lfs-cfg sizes were initiated properly before - // performing any arithmetic logics with them + // performing any arithmetic logic with them LFS_ASSERT(lfs->cfg->read_size != 0); LFS_ASSERT(lfs->cfg->prog_size != 0); LFS_ASSERT(lfs->cfg->cache_size != 0); + LFS_ASSERT(lfs->cfg->erase_size != 0 || lfs->cfg->block_size != 0); + LFS_ASSERT(lfs->cfg->erase_count != 0 || lfs->cfg->block_count != 0); - // check that block size is a multiple of cache size is a multiple - // of prog and read sizes + // check that cache_size is a multiple of prog_size and read_size LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0); LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); - LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); + + // setup erase_size/count, these can be zero for backwards compatibility + lfs->erase_size = lfs->cfg->erase_size; + lfs->erase_count = lfs->cfg->erase_count; + lfs->block_size = lfs->cfg->block_size; + lfs->block_count = lfs->cfg->block_count; + if (!lfs->erase_size) { + lfs->erase_size = lfs->block_size; + } + if (!lfs->erase_count) { + lfs->erase_count = lfs->block_count; + } + + // check that block_size is a multiple of erase_size is a mulitiple of + // cache_size, this implies everything is a multiple of read_size and + // prog_size + LFS_ASSERT(lfs->erase_size % lfs->cfg->cache_size == 0); + if (lfs->block_size) { + LFS_ASSERT(lfs->block_size % lfs->erase_size == 0); + } // 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); + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->block_size-2*4)) + <= lfs->block_size); // block_cycles = 0 is no longer supported. // @@ -3998,7 +4099,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->attr_max = LFS_ATTR_MAX; } - LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); + LFS_ASSERT(lfs->cfg->metadata_max <= lfs->block_size); // setup default state lfs->root[0] = LFS_BLOCK_NULL; @@ -4045,11 +4146,19 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) { return err; } + // if block_size/block_count not specified, assume equal to erase blocks + if (!lfs->block_size) { + lfs->block_size = lfs->erase_size; + } + if (!lfs->block_count) { + lfs->block_count = lfs->erase_count; + } + // 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); @@ -4063,8 +4172,8 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) { // write one superblock lfs_superblock_t superblock = { .version = LFS_DISK_VERSION, - .block_size = lfs->cfg->block_size, - .block_count = lfs->cfg->block_count, + .block_size = lfs->block_size, + .block_count = lfs->block_count, .name_max = lfs->name_max, .file_max = lfs->file_max, .attr_max = lfs->attr_max, @@ -4108,111 +4217,180 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) { return err; } - // scan directory blocks for superblock and any global updates - lfs_mdir_t dir = {.tail = {0, 1}}; - lfs_block_t cycle = 0; - while (!lfs_pair_isnull(dir.tail)) { - if (cycle >= lfs->cfg->block_count/2) { - // loop detected - err = LFS_ERR_CORRUPT; - goto cleanup; + // if block_size is unknown we need to search for it + lfs_size_t block_size_limit = lfs->block_size; + if (!lfs->block_size) { + lfs->block_size = lfs->erase_size; + // make sure this doesn't overflow + lfs_size_t limit = lfs->block_count + ? lfs->block_count/2 + : lfs->erase_count/2; + if (limit > ((lfs_size_t)-1) / lfs->block_size) { + block_size_limit = ((lfs_size_t)-1); + } else { + block_size_limit = limit * lfs->block_size; } - cycle += 1; + } - // fetch next block in tail list - lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, - LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), - NULL, - lfs_dir_find_match, &(struct lfs_dir_find_match){ - lfs, "littlefs", 8}); - if (tag < 0) { - err = tag; - goto cleanup; - } + // search for the correct block_size + while (true) { + // setup block_size/count so underlying operations work + lfs->block_count = lfs->erase_count + / (lfs->block_size/lfs->erase_size); - // has superblock? - if (tag && !lfs_tag_isdelete(tag)) { - // update root - lfs->root[0] = dir.pair[0]; - lfs->root[1] = dir.pair[1]; + // scan directory blocks for superblock and any global updates + lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->block_count/2) { + // loop detected + err = LFS_ERR_CORRUPT; + goto cleanup; + } + cycle += 1; - // grab superblock - lfs_superblock_t superblock; - tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); + // fetch next block in tail list + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), + NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, "littlefs", 8}); if (tag < 0) { + if (tag == LFS_ERR_CORRUPT) { + // maybe our block_size is wrong + goto next_block_size; + } err = tag; goto cleanup; } - lfs_superblock_fromle32(&superblock); - // check version - uint16_t major_version = (0xffff & (superblock.version >> 16)); - uint16_t minor_version = (0xffff & (superblock.version >> 0)); - if ((major_version != LFS_DISK_VERSION_MAJOR || - minor_version > LFS_DISK_VERSION_MINOR)) { - LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16, - major_version, minor_version); - err = LFS_ERR_INVAL; - goto cleanup; - } + // has superblock? + if (tag && !lfs_tag_isdelete(tag)) { + // grab superblock + lfs_superblock_t superblock; + tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + if (tag == LFS_ERR_CORRUPT) { + // maybe our block_size is wrong + goto next_block_size; + } + err = tag; + goto cleanup; + } + lfs_superblock_fromle32(&superblock); - // check superblock configuration - if (superblock.name_max) { - if (superblock.name_max > lfs->name_max) { - LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", - superblock.name_max, lfs->name_max); + // we may not be done, first check the stored block_size, it + // it's different we need to remount in case we found an + // outdated superblock + if (superblock.block_size != lfs->block_size) { + if (lfs->cfg->block_size + || superblock.block_size % lfs->erase_size != 0 + || superblock.block_size < lfs->block_size) { + LFS_ERROR("Invalid block size %"PRIu32, + superblock.block_size); + err = LFS_ERR_INVAL; + goto cleanup; + } + + // remount with correct block_size + lfs->block_size = superblock.block_size; + goto next_mount; + } + + if (superblock.block_count != lfs->block_count) { + if ((lfs->cfg->block_size && lfs->cfg->block_count) + || superblock.block_count > lfs->block_count) { + LFS_ERROR("Invalid block count %"PRIu32, + superblock.block_count); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->block_count = superblock.block_count; + } + + // check version + uint16_t major_version = (0xffff & (superblock.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16, + major_version, minor_version); err = LFS_ERR_INVAL; goto cleanup; } - lfs->name_max = superblock.name_max; - } + // check superblock configuration + if (superblock.name_max) { + if (superblock.name_max > lfs->name_max) { + LFS_ERROR("Unsupported name_max %"PRIu32, + superblock.name_max); + err = LFS_ERR_INVAL; + goto cleanup; + } - if (superblock.file_max) { - if (superblock.file_max > lfs->file_max) { - LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", - superblock.file_max, lfs->file_max); - err = LFS_ERR_INVAL; - goto cleanup; + lfs->name_max = superblock.name_max; } - lfs->file_max = superblock.file_max; - } + if (superblock.file_max) { + if (superblock.file_max > lfs->file_max) { + LFS_ERROR("Unsupported file_max %"PRIu32, + superblock.file_max); + err = LFS_ERR_INVAL; + goto cleanup; + } - if (superblock.attr_max) { - if (superblock.attr_max > lfs->attr_max) { - LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", - superblock.attr_max, lfs->attr_max); - err = LFS_ERR_INVAL; - goto cleanup; + lfs->file_max = superblock.file_max; } - lfs->attr_max = superblock.attr_max; + if (superblock.attr_max) { + if (superblock.attr_max > lfs->attr_max) { + LFS_ERROR("Unsupported attr_max %"PRIu32, + superblock.attr_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->attr_max = superblock.attr_max; + } + + // update root + lfs->root[0] = dir.pair[0]; + lfs->root[1] = dir.pair[1]; } - if (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; + // has gstate? + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); + if (err) { goto cleanup; } - if (superblock.block_size != lfs->cfg->block_size) { - LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", - superblock.block_size, lfs->cfg->block_size); - err = LFS_ERR_INVAL; - goto cleanup; - } + // we found a valid superblock, set block_size_limit so block_size + // will no longer change + block_size_limit = lfs->block_size; } - // has gstate? - err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); - if (err) { + break; + +next_block_size: + lfs->block_size += lfs->erase_size; + if (lfs->block_size > block_size_limit) { + err = LFS_ERR_INVAL; goto cleanup; } + + // if block_count is set, skip block_sizes that aren't a factor, + // this brings our search down from O(n) to O(d(n)), and + // O(log(n)) for powers of 2 + if (lfs->cfg->block_count && lfs->cfg->block_count + % (lfs->block_size/lfs->erase_size) != 0) { + goto next_block_size; + } + +next_mount:; } // found superblock? @@ -4233,7 +4411,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; @@ -4270,7 +4448,7 @@ int lfs_fs_rawtraverse(lfs_t *lfs, lfs_block_t cycle = 0; while (!lfs_pair_isnull(dir.tail)) { - if (cycle >= lfs->cfg->block_count/2) { + if (cycle >= lfs->block_count/2) { // loop detected return LFS_ERR_CORRUPT; } @@ -4355,7 +4533,7 @@ static int lfs_fs_pred(lfs_t *lfs, pdir->tail[1] = 1; lfs_block_t cycle = 0; while (!lfs_pair_isnull(pdir->tail)) { - if (cycle >= lfs->cfg->block_count/2) { + if (cycle >= lfs->block_count/2) { // loop detected return LFS_ERR_CORRUPT; } @@ -4392,7 +4570,7 @@ static int lfs_fs_parent_match(void *data, lfs_block_t child[2]; int err = lfs_bd_read(lfs, - &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, + &lfs->pcache, &lfs->rcache, lfs->block_size, disk->block, disk->off, &child, sizeof(child)); if (err) { return err; @@ -4411,7 +4589,7 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], parent->tail[1] = 1; lfs_block_t cycle = 0; while (!lfs_pair_isnull(parent->tail)) { - if (cycle >= lfs->cfg->block_count/2) { + if (cycle >= lfs->block_count/2) { // loop detected return LFS_ERR_CORRUPT; } @@ -4828,7 +5006,7 @@ static int lfs1_dir_fetch(lfs_t *lfs, } if ((0x7fffffff & test.size) < sizeof(test)+4 || - (0x7fffffff & test.size) > lfs->cfg->block_size) { + (0x7fffffff & test.size) > lfs->block_size) { continue; } @@ -5262,8 +5440,8 @@ static int lfs_rawmigrate(lfs_t *lfs, const struct lfs_config *cfg) { lfs_superblock_t superblock = { .version = LFS_DISK_VERSION, - .block_size = lfs->cfg->block_size, - .block_count = lfs->cfg->block_count, + .block_size = lfs->block_size, + .block_count = lfs->block_count, .name_max = lfs->name_max, .file_max = lfs->file_max, .attr_max = lfs->attr_max, @@ -5324,6 +5502,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { LFS_TRACE("lfs_format(%p, %p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".erase_size=%"PRIu32", .erase_count=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " @@ -5333,8 +5512,9 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { (void*)lfs, (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, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_size, cfg->prog_size, cfg->erase_size, cfg->erase_count, + cfg->block_size, cfg->block_count, cfg->block_cycles, + cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); @@ -5354,6 +5534,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { LFS_TRACE("lfs_mount(%p, %p {.context=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".erase_size=%"PRIu32", .erase_count=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " @@ -5363,8 +5544,9 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { (void*)lfs, (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, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_size, cfg->prog_size, cfg->erase_size, cfg->erase_count, + cfg->block_size, cfg->block_count, cfg->block_cycles, + cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); diff --git a/lfs.h b/lfs.h index ab534526..d5927bca 100644 --- a/lfs.h +++ b/lfs.h @@ -189,21 +189,52 @@ struct lfs_config { int (*unlock)(const struct lfs_config *c); #endif - // Minimum size of a block read in bytes. All read operations will be a - // multiple of this value. + // Minimum size of a read operation in bytes. All read operations + // will be a multiple of this value. lfs_size_t read_size; - // Minimum size of a block program in bytes. All program operations will be - // a multiple of this value. + // Minimum size of a program operation in bytes. All program operations + // will be a multiple of this value. lfs_size_t prog_size; - // Size of an erasable block in bytes. This does not impact ram consumption - // and may be larger than the physical erase size. However, non-inlined - // files take up at minimum one block. Must be a multiple of the read and - // program sizes. + // Minimum size of an erase operation in bytes. All erase operations + // will be a multiple of this value. This must be a multiple of the read + // and program sizes. + // + // If zero, the block_size is used as the erase_size. This is mostly for + // backwards compatibility. + lfs_size_t erase_size; + + // Number of erase blocks on the device. + // + // If zero, the block_count is used as the erase_count. This is mostly for + // backwards compatibility. + lfs_size_t erase_count; + + // Size of a logical block in bytes. This does not impact RAM consumption + // and may be a multiple of the physical erase_size. + // + // Note, non-inlined files take up at minimum one logical block, and + // directories take up at minimum two logical blocks. + // + // If zero, littlefs attempts to find the superblock and use the the + // block_size stored there. This requires searching different block_sizes. + // If a superblock is found this takes no longer than mounting with a known + // block_size, but it can take time to fail if a superblock is not found: + // + // - O(block_size) if a superblock is found + // - O(d(block_count)) if block_count is non-zero + // - O(log(block_count)) if block_count is a power of 2 + // - O(erase_count) if block_count is zero lfs_size_t block_size; - // Number of erasable blocks on the device. + // Number of logical blocks on the device. + // + // If zero, littlefs uses the block_count stored in the superblock. + // + // If non-zero and block_size is zero, littlefs will assume block_size + // is a factor of erase_size*block_count to speed up mount when no + // superblock is found. lfs_size_t block_count; // Number of erase cycles before littlefs evicts metadata logs and moves @@ -408,6 +439,10 @@ typedef struct lfs { } free; const struct lfs_config *cfg; + lfs_size_t erase_size; + lfs_size_t erase_count; + lfs_size_t block_size; + lfs_size_t block_count; lfs_size_t name_max; lfs_size_t file_max; lfs_size_t attr_max; diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index b50b3933..5035b931 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -256,5 +256,5 @@ code = ''' lfs_t lfs; lfs_format(&lfs, cfg) => LFS_ERR_NOSPC; - lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; + lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; ''' diff --git a/tests/test_evil.toml b/tests/test_evil.toml index 4acd5ef0..1dde8650 100644 --- a/tests/test_evil.toml +++ b/tests/test_evil.toml @@ -24,7 +24,7 @@ code = ''' lfs_deinit(&lfs) => 0; // test that mount fails gracefully - lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; + lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; ''' [cases.test_evil_invalid_dir_pointer] diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 689bbcd2..60bda767 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -31,7 +31,7 @@ code = ''' [cases.test_superblocks_invalid_mount] code = ''' lfs_t lfs; - lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; + lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; ''' # expanding superblock