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.
This commit is contained in:
Christopher Haster
2017-04-22 13:30:40 -05:00
parent 789286a257
commit 7050922623
4 changed files with 220 additions and 24 deletions

225
lfs.c
View File

@@ -9,17 +9,147 @@
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
/// Block device operations /// /// 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, static int lfs_bd_read(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, lfs_size_t size, void *buffer) { 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, static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block,
lfs_off_t off, lfs_size_t size, const void *buffer) { 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) { 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) { static int lfs_bd_sync(lfs_t *lfs) {
int err = lfs_bd_flush(lfs);
if (err) {
return err;
}
return lfs->cfg->sync(lfs->cfg); return lfs->cfg->sync(lfs->cfg);
} }
@@ -41,11 +176,9 @@ static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block,
return err; return err;
} }
if (c != *data) { if (c != data[i]) {
return false; return false;
} }
data += 1;
} }
return true; 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) { while (off < lfs->cfg->block_size-4) {
uint8_t data; uint8_t data = 0xff;
int err = lfs_bd_read(lfs, dir->pair[0], off, 1, &data); crc = lfs_crc(crc, 1, &data);
err = lfs_bd_prog(lfs, dir->pair[0], off, 1, &data);
if (err) { if (err) {
return err; return err;
} }
crc = lfs_crc(crc, 1, &data);
off += 1; 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) { while (woff < lfs->cfg->block_size-4) {
uint8_t data; uint8_t data = 0xff;
int err = lfs_bd_read(lfs, dir->pair[0], woff, 1, &data); crc = lfs_crc(crc, 1, &data);
err = lfs_bd_prog(lfs, dir->pair[0], woff, 1, &data);
if (err) { if (err) {
return err; return err;
} }
crc = lfs_crc(crc, 1, &data);
woff += 1; 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); dir->off = sizeof(dir->d);
continue;
} }
int err = lfs_bd_read(lfs, dir->pair[0], dir->off, 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 /// /// Generic filesystem operations ///
int lfs_format(lfs_t *lfs, const struct lfs_config *config) { static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
lfs->cfg = config; lfs->cfg = cfg;
lfs->words = lfs->cfg->block_size / sizeof(uint32_t); 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 // Create free list
lfs->free.begin = 0; lfs->free.begin = 0;
@@ -996,7 +1173,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *config) {
// Create superblock dir // Create superblock dir
lfs_dir_t superdir; lfs_dir_t superdir;
int err = lfs_dir_alloc(lfs, &superdir); err = lfs_dir_alloc(lfs, &superdir);
if (err) { if (err) {
return err; return err;
} }
@@ -1044,16 +1221,23 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *config) {
} }
// sanity check that fetch works // 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) { int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
lfs->cfg = config; int err = lfs_init(lfs, cfg);
lfs->words = lfs->cfg->block_size / sizeof(uint32_t); if (err) {
return err;
}
lfs_dir_t dir; lfs_dir_t dir;
lfs_superblock_t superblock; 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) { if (!err) {
err = lfs_bd_read(lfs, dir.pair[0], err = lfs_bd_read(lfs, dir.pair[0],
sizeof(dir.d), sizeof(superblock.d), &superblock.d); 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) { int lfs_unmount(lfs_t *lfs) {
// Do nothing for now return lfs_deinit(lfs);
return 0;
} }
int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {

13
lfs.h
View File

@@ -20,6 +20,7 @@ enum lfs_error {
LFS_ERROR_IS_DIR = -7, LFS_ERROR_IS_DIR = -7,
LFS_ERROR_INVALID = -8, LFS_ERROR_INVALID = -8,
LFS_ERROR_NO_SPACE = -9, LFS_ERROR_NO_SPACE = -9,
LFS_ERROR_NO_MEM = -10,
}; };
enum lfs_type { enum lfs_type {
@@ -74,6 +75,12 @@ struct lfs_config {
// Number of erasable blocks on the device. // Number of erasable blocks on the device.
lfs_size_t block_count; 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 // File info structure
@@ -159,6 +166,12 @@ typedef struct lfs {
lfs_block_t end; lfs_block_t end;
} free; } free;
struct {
lfs_block_t block;
lfs_off_t off;
uint8_t *buffer;
} rcache, pcache;
uint32_t lookahead[LFS_CFG_LOOKAHEAD/32]; uint32_t lookahead[LFS_CFG_LOOKAHEAD/32];
} lfs_t; } lfs_t;

View File

@@ -8,6 +8,7 @@
#define LFS_UTIL_H #define LFS_UTIL_H
#include "lfs_config.h" #include "lfs_config.h"
#include <stdlib.h>
// Builtin functions // 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); uint32_t lfs_crc(uint32_t crc, lfs_size_t size, const void *buffer);
#endif #endif

View File

@@ -55,11 +55,11 @@ lfs_size_t rsize;
uintmax_t res; uintmax_t res;
#ifndef LFS_READ_SIZE #ifndef LFS_READ_SIZE
#define LFS_READ_SIZE 1 #define LFS_READ_SIZE 64
#endif #endif
#ifndef LFS_PROG_SIZE #ifndef LFS_PROG_SIZE
#define LFS_PROG_SIZE 1 #define LFS_PROG_SIZE 64
#endif #endif
#ifndef LFS_BLOCK_SIZE #ifndef LFS_BLOCK_SIZE