From 7050922623d4596ffeb8d6f431fbd3d4e8d6d840 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 22 Apr 2017 13:30:40 -0500 Subject: [PATCH] Added optional block-level caching This adds caching of the most recent read/program blocks, allowing support of devices that don't have byte-level read+writes, along with reduced device access on devices that do support byte-level read+writes. Note: The current implementation is a bit eager to drop caches where it simplifies the cache layer. This layer is already complex enough. Note: It may be worthwhile to add a compile switch for caching to reduce code size, note sure. Note: This does add a dependency on malloc, which could have a porting layer, but I'm just using the functions from stdlib for now. These can be overwritten with noops if the user controls the system, and keeps things simple for now. --- lfs.c | 225 ++++++++++++++++++++++++++++++++++++++++----- lfs.h | 13 +++ lfs_util.h | 2 +- tests/template.fmt | 4 +- 4 files changed, 220 insertions(+), 24 deletions(-) diff --git a/lfs.c b/lfs.c index 8808c60..46a1e3b 100644 --- a/lfs.c +++ b/lfs.c @@ -9,17 +9,147 @@ #include #include +#include /// Block device operations /// +static int lfs_bd_flush(lfs_t *lfs) { + if (lfs->pcache.off != -1) { + int err = lfs->cfg->prog(lfs->cfg, lfs->pcache.block, + lfs->pcache.off, lfs->cfg->prog_size, + lfs->pcache.buffer); + if (err) { + return err; + } + + lfs->pcache.off = -1; + } + + return 0; +} + static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, lfs_off_t off, lfs_size_t size, void *buffer) { - return lfs->cfg->read(lfs->cfg, block, off, size, buffer); + uint8_t *data = buffer; + + // flush overlapping programs + while (size > 0) { + if (block == lfs->pcache.block && off >= lfs->pcache.off && + off < lfs->pcache.off + lfs->cfg->prog_size) { + // is already in cache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-lfs->pcache.off)); + memcpy(data, &lfs->pcache.buffer[off-lfs->pcache.off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } else if (block == lfs->rcache.block && off >= lfs->rcache.off && + off < lfs->rcache.off + lfs->cfg->read_size) { + // is already in cache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->read_size - (off-lfs->rcache.off)); + memcpy(data, &lfs->rcache.buffer[off-lfs->rcache.off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // write out pending programs + int err = lfs_bd_flush(lfs); + if (err) { + return err; + } + + if (off % lfs->cfg->read_size == 0 && + size >= lfs->cfg->read_size) { + // bypass cache? + lfs_size_t diff = size - (size % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, diff, data); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + lfs->rcache.block = block; + lfs->rcache.off = off - (off % lfs->cfg->read_size); + // TODO remove reading, should be unnecessary + err = lfs->cfg->read(lfs->cfg, lfs->rcache.block, + lfs->rcache.off, lfs->cfg->read_size, + lfs->rcache.buffer); + if (err) { + return err; + } + } + + return 0; } static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block, lfs_off_t off, lfs_size_t size, const void *buffer) { - return lfs->cfg->prog(lfs->cfg, block, off, size, buffer); + const uint8_t *data = buffer; + + if (block == lfs->rcache.block) { + // invalidate read cache + lfs->rcache.off = -1; + } + + while (size > 0) { + if (block == lfs->pcache.block && off >= lfs->pcache.off && + off < lfs->pcache.off + lfs->cfg->prog_size) { + // is already in cache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-lfs->pcache.off)); + memcpy(&lfs->pcache.buffer[off-lfs->pcache.off], data, diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // write out pending programs + int err = lfs_bd_flush(lfs); + if (err) { + return err; + } + + if (off % lfs->cfg->prog_size == 0 && + size >= lfs->cfg->prog_size) { + // bypass cache? + lfs_size_t diff = size - (size % lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, block, off, diff, data); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // prepare cache, first condition can no longer fail + lfs->pcache.block = block; + lfs->pcache.off = off - (off % lfs->cfg->prog_size); + err = lfs->cfg->read(lfs->cfg, lfs->pcache.block, + lfs->pcache.off, lfs->cfg->prog_size, + lfs->pcache.buffer); + if (err) { + return err; + } + } + + return 0; } static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { @@ -27,6 +157,11 @@ static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { } static int lfs_bd_sync(lfs_t *lfs) { + int err = lfs_bd_flush(lfs); + if (err) { + return err; + } + return lfs->cfg->sync(lfs->cfg); } @@ -41,11 +176,9 @@ static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block, return err; } - if (c != *data) { + if (c != data[i]) { return false; } - - data += 1; } return true; @@ -452,13 +585,13 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, } while (off < lfs->cfg->block_size-4) { - uint8_t data; - int err = lfs_bd_read(lfs, dir->pair[0], off, 1, &data); + uint8_t data = 0xff; + crc = lfs_crc(crc, 1, &data); + err = lfs_bd_prog(lfs, dir->pair[0], off, 1, &data); if (err) { return err; } - crc = lfs_crc(crc, 1, &data); off += 1; } @@ -512,13 +645,14 @@ static int lfs_dir_shift(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } while (woff < lfs->cfg->block_size-4) { - uint8_t data; - int err = lfs_bd_read(lfs, dir->pair[0], woff, 1, &data); + uint8_t data = 0xff; + crc = lfs_crc(crc, 1, &data); + err = lfs_bd_prog(lfs, dir->pair[0], woff, 1, &data); if (err) { return err; } - crc = lfs_crc(crc, 1, &data); + woff += 1; } @@ -618,6 +752,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } dir->off = sizeof(dir->d); + continue; } int err = lfs_bd_read(lfs, dir->pair[0], dir->off, @@ -986,9 +1121,51 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, /// Generic filesystem operations /// -int lfs_format(lfs_t *lfs, const struct lfs_config *config) { - lfs->cfg = config; +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; lfs->words = lfs->cfg->block_size / sizeof(uint32_t); + lfs->rcache.off = -1; + lfs->pcache.off = -1; + + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = malloc(lfs->cfg->read_size); + if (!lfs->rcache.buffer) { + return LFS_ERROR_NO_MEM; + } + } + + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = malloc(lfs->cfg->prog_size); + if (!lfs->pcache.buffer) { + return LFS_ERROR_NO_MEM; + } + } + + return 0; +} + +static int lfs_deinit(lfs_t *lfs) { + // Free allocated memory + if (!lfs->cfg->read_buffer) { + free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + free(lfs->pcache.buffer); + } + + return 0; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } // Create free list lfs->free.begin = 0; @@ -996,7 +1173,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *config) { // Create superblock dir lfs_dir_t superdir; - int err = lfs_dir_alloc(lfs, &superdir); + err = lfs_dir_alloc(lfs, &superdir); if (err) { return err; } @@ -1044,16 +1221,23 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *config) { } // sanity check that fetch works - return lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + return lfs_deinit(lfs); } -int lfs_mount(lfs_t *lfs, const struct lfs_config *config) { - lfs->cfg = config; - lfs->words = lfs->cfg->block_size / sizeof(uint32_t); +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } lfs_dir_t dir; lfs_superblock_t superblock; - int err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); if (!err) { err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), sizeof(superblock.d), &superblock.d); @@ -1078,8 +1262,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *config) { } int lfs_unmount(lfs_t *lfs) { - // Do nothing for now - return 0; + return lfs_deinit(lfs); } int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { diff --git a/lfs.h b/lfs.h index 505c60b..7efa6de 100644 --- a/lfs.h +++ b/lfs.h @@ -20,6 +20,7 @@ enum lfs_error { LFS_ERROR_IS_DIR = -7, LFS_ERROR_INVALID = -8, LFS_ERROR_NO_SPACE = -9, + LFS_ERROR_NO_MEM = -10, }; enum lfs_type { @@ -74,6 +75,12 @@ struct lfs_config { // Number of erasable blocks on the device. lfs_size_t block_count; + + // Optional, statically allocated read buffer. Must be read sized. + void *read_buffer; + + // Optional, statically allocated program buffer. Must be program sized. + void *prog_buffer; }; // File info structure @@ -159,6 +166,12 @@ typedef struct lfs { lfs_block_t end; } free; + struct { + lfs_block_t block; + lfs_off_t off; + uint8_t *buffer; + } rcache, pcache; + uint32_t lookahead[LFS_CFG_LOOKAHEAD/32]; } lfs_t; diff --git a/lfs_util.h b/lfs_util.h index 83273a4..c20312c 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -8,6 +8,7 @@ #define LFS_UTIL_H #include "lfs_config.h" +#include // Builtin functions @@ -34,5 +35,4 @@ static inline int lfs_scmp(uint32_t a, uint32_t b) { uint32_t lfs_crc(uint32_t crc, lfs_size_t size, const void *buffer); - #endif diff --git a/tests/template.fmt b/tests/template.fmt index 8d42514..920fec6 100644 --- a/tests/template.fmt +++ b/tests/template.fmt @@ -55,11 +55,11 @@ lfs_size_t rsize; uintmax_t res; #ifndef LFS_READ_SIZE -#define LFS_READ_SIZE 1 +#define LFS_READ_SIZE 64 #endif #ifndef LFS_PROG_SIZE -#define LFS_PROG_SIZE 1 +#define LFS_PROG_SIZE 64 #endif #ifndef LFS_BLOCK_SIZE