mirror of
https://github.com/littlefs-project/littlefs.git
synced 2025-12-01 12:20:02 +00:00
kiwibd: Added kiwibd, a lighter-weight variant of emubd
Useful for emulating much larger disks in a file (or in RAM). kiwibd doesn't have all the features of emubd, but this allows it to prioritize disk size and speed for benchmarking. kiwibd still keeps some features useful for benchmarking/emulation: - Optional erase value emulation, including nor-masking - Read/prog/erase trackers for measuring bd operations - Read/prog/erase sleeps for slowing down the simulation to a human viewable speed
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
/*
|
||||
* Emulating block device, wraps filebd and rambd while providing a bunch
|
||||
* of hooks for testing littlefs in various conditions.
|
||||
* emubd - High-level emulating block device with many bells and
|
||||
* whistles for testing powerloss, wear, etc.
|
||||
*
|
||||
* Note emubd always backs the block device in RAM. Consider using
|
||||
* kiwibd if you need a block device larger than the available RAM on
|
||||
* the system.
|
||||
*
|
||||
* Copyright (c) 2022, The littlefs authors.
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
@ -1030,7 +1034,7 @@ int lfs3_emubd_sync(const struct lfs3_cfg *cfg) {
|
||||
}
|
||||
|
||||
|
||||
/// Additional extended API for driving test features ///
|
||||
/// Additional emubd features for testing ///
|
||||
|
||||
void lfs3_emubd_seed(const struct lfs3_cfg *cfg, uint32_t seed) {
|
||||
LFS3_EMUBD_TRACE("lfs3_emubd_seed(%p, 0x%08"PRIx32")",
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
/*
|
||||
* Emulating block device, wraps filebd and rambd while providing a bunch
|
||||
* of hooks for testing littlefs in various conditions.
|
||||
* emubd - High-level emulating block device with many bells and
|
||||
* whistles for testing powerloss, wear, etc.
|
||||
*
|
||||
* Note emubd always backs the block device in RAM. Consider using
|
||||
* kiwibd if you need a block device larger than the available RAM on
|
||||
* the system.
|
||||
*
|
||||
* Copyright (c) 2022, The littlefs authors.
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
@ -11,8 +15,6 @@
|
||||
|
||||
#include "lfs3.h"
|
||||
#include "lfs3_util.h"
|
||||
#include "bd/lfs3_rambd.h"
|
||||
#include "bd/lfs3_filebd.h"
|
||||
|
||||
|
||||
// Block device specific tracing
|
||||
@ -99,7 +101,8 @@ struct lfs3_emubd_cfg {
|
||||
uint32_t seed;
|
||||
|
||||
// Path to file to use as a mirror of the disk. This provides a way to view
|
||||
// the current state of the block device.
|
||||
// the current state of the block device, but does not eliminate the RAM
|
||||
// requirement.
|
||||
const char *disk_path;
|
||||
|
||||
// Artificial delay in nanoseconds, there is no purpose for this other
|
||||
@ -157,8 +160,11 @@ typedef struct lfs3_emubd {
|
||||
|
||||
// Create an emulating block device using the geometry in lfs3_cfg
|
||||
//
|
||||
// Note that filebd is used if a path is provided, if path is NULL
|
||||
// emubd will use rambd which can be much faster.
|
||||
// If disk_path is provided, emubd will mirror the block device in the
|
||||
// file. Note this is a write-only mirror intended for introspection,
|
||||
// and does not eliminate the RAM requirement.
|
||||
//
|
||||
// TODO wait, why do we have both disk_path and path here?
|
||||
int lfs3_emubd_create(const struct lfs3_cfg *cfg, const char *path);
|
||||
int lfs3_emubd_createcfg(const struct lfs3_cfg *cfg, const char *path,
|
||||
const struct lfs3_emubd_cfg *bdcfg);
|
||||
@ -186,7 +192,7 @@ int lfs3_emubd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block);
|
||||
int lfs3_emubd_sync(const struct lfs3_cfg *cfg);
|
||||
|
||||
|
||||
/// Additional extended API for driving test features ///
|
||||
/// Additional emubd features for testing ///
|
||||
|
||||
// Set the current prng state
|
||||
void lfs3_emubd_seed(const struct lfs3_cfg *cfg, uint32_t seed);
|
||||
|
||||
559
bd/lfs3_kiwibd.c
Normal file
559
bd/lfs3_kiwibd.c
Normal file
@ -0,0 +1,559 @@
|
||||
/*
|
||||
* kiwibd - A lightweight variant of emubd, useful for emulating large
|
||||
* disks backed by a file or in RAM.
|
||||
*
|
||||
* Unlike emubd, file-backed disks are _not_ mirrored in RAM. kiwibd has
|
||||
* fewer features than emubd, prioritizing speed for benchmarking.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#endif
|
||||
|
||||
#include "bd/lfs3_kiwibd.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
|
||||
// low-level flash memory emulation
|
||||
|
||||
// read data
|
||||
static inline void lfs3_kiwibd_memread(const struct lfs3_cfg *cfg,
|
||||
void *restrict dst, const void *restrict src, size_t size) {
|
||||
(void)cfg;
|
||||
memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
static inline void lfs3_kiwibd_memprog(const struct lfs3_cfg *cfg,
|
||||
void *restrict dst, const void *restrict src, size_t size) {
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
// emulating nor-masking?
|
||||
if (bd->cfg->erase_value == -2) {
|
||||
uint8_t *dst_ = dst;
|
||||
const uint8_t *src_ = src;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
dst_[i] &= src_[i];
|
||||
}
|
||||
} else {
|
||||
memcpy(dst, src, size);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void lfs3_kiwibd_memerase(const struct lfs3_cfg *cfg,
|
||||
void *restrict dst, size_t size) {
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
// emulating erase value?
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
memset(dst,
|
||||
(bd->cfg->erase_value >= 0)
|
||||
? bd->cfg->erase_value
|
||||
: 0xff,
|
||||
size);
|
||||
}
|
||||
}
|
||||
|
||||
// this is slightly different from lfs3_kiwibd_memerase in that we use
|
||||
// lfs3_kiwibd_memzero when we need to unconditionally zero memory
|
||||
static inline void lfs3_kiwibd_memzero(const struct lfs3_cfg *cfg,
|
||||
void *restrict dst, size_t size) {
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
memset(dst,
|
||||
(bd->cfg->erase_value == -1) ? 0
|
||||
: (bd->cfg->erase_value >= 0) ? bd->cfg->erase_value
|
||||
: (bd->cfg->erase_value == -2) ? 0xff
|
||||
: 0,
|
||||
size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// kiwibd create/destroy
|
||||
|
||||
int lfs3_kiwibd_createcfg(const struct lfs3_cfg *cfg, const char *path,
|
||||
const struct lfs3_kiwibd_cfg *bdcfg) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_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", "
|
||||
".disk_path=\"%s\", "
|
||||
".buffer=%p, "
|
||||
".read_sleep=%"PRIu64", "
|
||||
".prog_sleep=%"PRIu64", "
|
||||
".erase_sleep=%"PRIu64"})",
|
||||
(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->disk_path,
|
||||
bdcfg->buffer,
|
||||
bdcfg->read_sleep,
|
||||
bdcfg->prog_sleep,
|
||||
bdcfg->erase_sleep);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
bd->cfg = bdcfg;
|
||||
|
||||
// setup some initial state
|
||||
bd->readed = 0;
|
||||
bd->proged = 0;
|
||||
bd->erased = 0;
|
||||
if (bd->cfg->disk_path) {
|
||||
bd->u.disk.fd = -1;
|
||||
bd->u.disk.scratch = NULL;
|
||||
} else {
|
||||
bd->u.mem = NULL;
|
||||
}
|
||||
int err;
|
||||
|
||||
// if we have a disk_path, try to open the backing file
|
||||
if (bd->cfg->disk_path) {
|
||||
bd->u.disk.fd = open(bd->cfg->disk_path,
|
||||
O_RDWR | O_CREAT, 0666);
|
||||
if (bd->u.disk.fd < 0) {
|
||||
err = -errno;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// allocate a scratch buffer to help with zeroing/masking/etc
|
||||
bd->u.disk.scratch = malloc(cfg->block_size);
|
||||
if (!bd->u.disk.scratch) {
|
||||
err = LFS3_ERR_NOMEM;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// zero for reproducibility
|
||||
lfs3_kiwibd_memzero(cfg, bd->u.disk.scratch, cfg->block_size);
|
||||
for (lfs3_block_t i = 0; i < cfg->block_count; i++) {
|
||||
ssize_t res = write(bd->u.disk.fd,
|
||||
bd->u.disk.scratch,
|
||||
cfg->block_size);
|
||||
if (res < 0) {
|
||||
err = -errno;
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, try to malloc a big memory array
|
||||
} else {
|
||||
bd->u.mem = malloc((size_t)cfg->block_size * cfg->block_count);
|
||||
if (!bd->u.mem) {
|
||||
err = LFS3_ERR_NOMEM;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// zero for reproducibility
|
||||
lfs3_kiwibd_memzero(cfg, bd->u.mem,
|
||||
(size_t)cfg->block_size * cfg->block_count);
|
||||
}
|
||||
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_createcfg -> %d", 0);
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_createcfg -> %d", err);
|
||||
// clean up memory
|
||||
if (bd->cfg->disk_path) {
|
||||
if (bd->u.disk.fd != -1) {
|
||||
close(bd->u.disk.fd);
|
||||
}
|
||||
free(bd->u.disk.scratch);
|
||||
} else {
|
||||
free(bd->u.mem);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_create(const struct lfs3_cfg *cfg, const char *path) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_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 lfs3_kiwibd_cfg defaults = {.erase_value=-1};
|
||||
int err = lfs3_kiwibd_createcfg(cfg, path, &defaults);
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_create -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_destroy(const struct lfs3_cfg *cfg) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_destroy(%p)", (void*)cfg);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
|
||||
// clean up memory
|
||||
if (bd->cfg->disk_path) {
|
||||
close(bd->u.disk.fd);
|
||||
free(bd->u.disk.scratch);
|
||||
} else {
|
||||
free(bd->u.mem);
|
||||
}
|
||||
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_destroy -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// block device API
|
||||
|
||||
int lfs3_kiwibd_read(const struct lfs3_cfg *cfg, lfs3_block_t block,
|
||||
lfs3_off_t off, void *buffer, lfs3_size_t size) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_read(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
|
||||
// check if read is valid
|
||||
LFS3_ASSERT(block < cfg->block_count);
|
||||
LFS3_ASSERT(off % cfg->read_size == 0);
|
||||
LFS3_ASSERT(size % cfg->read_size == 0);
|
||||
LFS3_ASSERT(off+size <= cfg->block_size);
|
||||
|
||||
// read in file?
|
||||
if (bd->cfg->disk_path) {
|
||||
lfs3_kiwibd_memerase(cfg,
|
||||
bd->u.disk.scratch,
|
||||
cfg->block_size);
|
||||
|
||||
off_t res = lseek(bd->u.disk.fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off,
|
||||
SEEK_SET);
|
||||
if (res < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_read -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ssize_t res_ = read(bd->u.disk.fd, buffer, size);
|
||||
if (res_ < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_read -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// read in RAM?
|
||||
} else {
|
||||
lfs3_kiwibd_memread(cfg,
|
||||
buffer,
|
||||
&bd->u.mem[(size_t)block*cfg->block_size + (size_t)off],
|
||||
size);
|
||||
}
|
||||
|
||||
// track reads
|
||||
bd->readed += size;
|
||||
if (bd->cfg->read_sleep) {
|
||||
int err = nanosleep(&(struct timespec){
|
||||
.tv_sec=bd->cfg->read_sleep/1000000000,
|
||||
.tv_nsec=bd->cfg->read_sleep%1000000000},
|
||||
NULL);
|
||||
if (err) {
|
||||
err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_read -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_read -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
|
||||
lfs3_off_t off, const void *buffer, lfs3_size_t size) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
|
||||
// check if write is valid
|
||||
LFS3_ASSERT(block < cfg->block_count);
|
||||
LFS3_ASSERT(off % cfg->prog_size == 0);
|
||||
LFS3_ASSERT(size % cfg->prog_size == 0);
|
||||
LFS3_ASSERT(off+size <= cfg->block_size);
|
||||
|
||||
// prog in file?
|
||||
if (bd->cfg->disk_path) {
|
||||
// were we erased properly?
|
||||
if (bd->cfg->erase_value >= 0) {
|
||||
off_t res = lseek(bd->u.disk.fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off,
|
||||
SEEK_SET);
|
||||
if (res < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ssize_t res_ = read(bd->u.disk.fd, bd->u.disk.scratch, size);
|
||||
if (res_ < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (lfs3_off_t i = 0; i < size; i++) {
|
||||
LFS3_ASSERT(bd->u.disk.scratch[i] == bd->cfg->erase_value);
|
||||
}
|
||||
}
|
||||
|
||||
// masking progs?
|
||||
if (bd->cfg->erase_value == -2) {
|
||||
off_t res = lseek(bd->u.disk.fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off,
|
||||
SEEK_SET);
|
||||
if (res < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ssize_t res_ = read(bd->u.disk.fd, bd->u.disk.scratch, size);
|
||||
if (res_ < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs3_kiwibd_memprog(cfg, bd->u.disk.scratch, buffer, size);
|
||||
|
||||
res = lseek(bd->u.disk.fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off,
|
||||
SEEK_SET);
|
||||
if (res < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
res_ = write(bd->u.disk.fd, bd->u.disk.scratch, size);
|
||||
if (res_ < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// normal progs?
|
||||
} else {
|
||||
off_t res = lseek(bd->u.disk.fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off,
|
||||
SEEK_SET);
|
||||
if (res < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ssize_t res_ = write(bd->u.disk.fd, buffer, size);
|
||||
if (res_ < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// prog in RAM?
|
||||
} else {
|
||||
// were we erased properly?
|
||||
if (bd->cfg->erase_value >= 0) {
|
||||
for (lfs3_off_t i = 0; i < size; i++) {
|
||||
LFS3_ASSERT(
|
||||
bd->u.mem[(size_t)block*cfg->block_size + (size_t)off]
|
||||
== bd->cfg->erase_value);
|
||||
}
|
||||
}
|
||||
|
||||
lfs3_kiwibd_memprog(cfg,
|
||||
&bd->u.mem[(size_t)block*cfg->block_size + (size_t)off],
|
||||
buffer,
|
||||
size);
|
||||
}
|
||||
|
||||
// track progs
|
||||
bd->proged += size;
|
||||
if (bd->cfg->prog_sleep) {
|
||||
int err = nanosleep(&(struct timespec){
|
||||
.tv_sec=bd->cfg->prog_sleep/1000000000,
|
||||
.tv_nsec=bd->cfg->prog_sleep%1000000000},
|
||||
NULL);
|
||||
if (err) {
|
||||
err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erase(%p, 0x%"PRIx32" (%"PRIu32"))",
|
||||
(void*)cfg, block, cfg->block_size);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
|
||||
// check if erase is valid
|
||||
LFS3_ASSERT(block < cfg->block_count);
|
||||
|
||||
// emulate an erase value?
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
// erase in file?
|
||||
if (bd->cfg->disk_path) {
|
||||
off_t res = lseek(bd->u.disk.fd,
|
||||
(off_t)block*cfg->block_size,
|
||||
SEEK_SET);
|
||||
if (res < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erase -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs3_kiwibd_memerase(cfg,
|
||||
bd->u.disk.scratch,
|
||||
cfg->block_size);
|
||||
|
||||
ssize_t res_ = write(bd->u.disk.fd,
|
||||
bd->u.disk.scratch,
|
||||
cfg->block_size);
|
||||
if (res_ < 0) {
|
||||
int err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erase -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// erase in RAM?
|
||||
} else {
|
||||
lfs3_kiwibd_memerase(cfg,
|
||||
&bd->u.mem[(size_t)block*cfg->block_size],
|
||||
cfg->block_size);
|
||||
}
|
||||
}
|
||||
|
||||
erased:;
|
||||
// track erases
|
||||
bd->erased += cfg->block_size;
|
||||
if (bd->cfg->erase_sleep) {
|
||||
int err = nanosleep(&(struct timespec){
|
||||
.tv_sec=bd->cfg->erase_sleep/1000000000,
|
||||
.tv_nsec=bd->cfg->erase_sleep%1000000000},
|
||||
NULL);
|
||||
if (err) {
|
||||
err = -errno;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erase -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erase -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_sync(const struct lfs3_cfg *cfg) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_sync(%p)", (void*)cfg);
|
||||
|
||||
// in theory we could actually sync here, but if our goal is
|
||||
// performance, why bother?
|
||||
//
|
||||
// filebd may be a better block device is your goal is actual
|
||||
// storage
|
||||
|
||||
// sync is a noop
|
||||
(void)cfg;
|
||||
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_sync -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// Additional kiwibd features ///
|
||||
|
||||
lfs3_kiwibd_sio_t lfs3_kiwibd_readed(const struct lfs3_cfg *cfg) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_readed(%p)", (void*)cfg);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_readed -> %"PRIu64, bd->readed);
|
||||
return bd->readed;
|
||||
}
|
||||
|
||||
lfs3_kiwibd_sio_t lfs3_kiwibd_proged(const struct lfs3_cfg *cfg) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_proged(%p)", (void*)cfg);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_proged -> %"PRIu64, bd->proged);
|
||||
return bd->proged;
|
||||
}
|
||||
|
||||
lfs3_kiwibd_sio_t lfs3_kiwibd_erased(const struct lfs3_cfg *cfg) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erased(%p)", (void*)cfg);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_erased -> %"PRIu64, bd->erased);
|
||||
return bd->erased;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_setreaded(const struct lfs3_cfg *cfg,
|
||||
lfs3_kiwibd_io_t readed) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_setreaded(%p, %"PRIu64")",
|
||||
(void*)cfg, readed);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
bd->readed = readed;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_setreaded -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_setproged(const struct lfs3_cfg *cfg,
|
||||
lfs3_kiwibd_io_t proged) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_setproged(%p, %"PRIu64")",
|
||||
(void*)cfg, proged);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
bd->proged = proged;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_setproged -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs3_kiwibd_seterased(const struct lfs3_cfg *cfg,
|
||||
lfs3_kiwibd_io_t erased) {
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_seterased(%p, %"PRIu64")",
|
||||
(void*)cfg, erased);
|
||||
lfs3_kiwibd_t *bd = cfg->context;
|
||||
bd->erased = erased;
|
||||
LFS3_KIWIBD_TRACE("lfs3_kiwibd_seterased -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
143
bd/lfs3_kiwibd.h
Normal file
143
bd/lfs3_kiwibd.h
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* kiwibd - A lightweight variant of emubd, useful for emulating large
|
||||
* disks backed by a file or in RAM.
|
||||
*
|
||||
* Unlike emubd, file-backed disks are _not_ mirrored in RAM. kiwibd has
|
||||
* fewer features than emubd, prioritizing speed for benchmarking.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef LFS3_KIWIBD_H
|
||||
#define LFS3_KIWIBD_H
|
||||
|
||||
#include "lfs3.h"
|
||||
#include "lfs3_util.h"
|
||||
|
||||
|
||||
// Block device specific tracing
|
||||
#ifndef LFS3_KIWIBD_TRACE
|
||||
#ifdef LFS3_KIWIBD_YES_TRACE
|
||||
#define LFS3_KIWIBD_TRACE(...) LFS3_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define LFS3_KIWIBD_TRACE(...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Type for measuring read/program/erase operations
|
||||
typedef uint64_t lfs3_kiwibd_io_t;
|
||||
typedef int64_t lfs3_kiwibd_sio_t;
|
||||
|
||||
// Type for delays in nanoseconds
|
||||
typedef uint64_t lfs3_kiwibd_sleep_t;
|
||||
typedef int64_t lfs3_kiwibd_ssleep_t;
|
||||
|
||||
// kiwibd config, this is required for testing
|
||||
struct lfs3_kiwibd_cfg {
|
||||
// 8-bit erase value to use for simulating erases. -1 simulates a noop
|
||||
// erase, which is faster than simulating a fixed erase value. -2 emulates
|
||||
// nor-masking, which is useful for testing other filesystems (littlefs
|
||||
// does _not_ rely on this!).
|
||||
int32_t erase_value;
|
||||
|
||||
// Path to file to back the block device. If provided kiwibd uses this
|
||||
// instead of RAM, allowing emulation of block devices > available RAM.
|
||||
const char *disk_path;
|
||||
|
||||
// Optional statically allocated buffer for the block device. Ignored
|
||||
// if disk_path is provided.
|
||||
void *buffer;
|
||||
|
||||
// Artificial delay in nanoseconds, there is no purpose for this other
|
||||
// than slowing down the simulation.
|
||||
lfs3_kiwibd_sleep_t read_sleep;
|
||||
|
||||
// Artificial delay in nanoseconds, there is no purpose for this other
|
||||
// than slowing down the simulation.
|
||||
lfs3_kiwibd_sleep_t prog_sleep;
|
||||
|
||||
// Artificial delay in nanoseconds, there is no purpose for this other
|
||||
// than slowing down the simulation.
|
||||
lfs3_kiwibd_sleep_t erase_sleep;
|
||||
};
|
||||
|
||||
// kiwibd state
|
||||
typedef struct lfs3_kiwibd {
|
||||
// backing disk
|
||||
union {
|
||||
struct {
|
||||
int fd;
|
||||
uint8_t *scratch;
|
||||
} disk;
|
||||
uint8_t *mem;
|
||||
} u;
|
||||
|
||||
// amount read/progged/erased
|
||||
lfs3_kiwibd_io_t readed;
|
||||
lfs3_kiwibd_io_t proged;
|
||||
lfs3_kiwibd_io_t erased;
|
||||
|
||||
const struct lfs3_kiwibd_cfg *cfg;
|
||||
} lfs3_kiwibd_t;
|
||||
|
||||
|
||||
/// Block device API ///
|
||||
|
||||
// Create a kiwibd using the geometry in lfs3_cfg
|
||||
//
|
||||
// If disk_path is provided, it will be used to back the kiwibd,
|
||||
// otherwise kiwibd will try to use RAM.
|
||||
//
|
||||
// TODO wait, why do we have both disk_path and path here?
|
||||
int lfs3_kiwibd_create(const struct lfs3_cfg *cfg, const char *path);
|
||||
int lfs3_kiwibd_createcfg(const struct lfs3_cfg *cfg, const char *path,
|
||||
const struct lfs3_kiwibd_cfg *bdcfg);
|
||||
|
||||
// Clean up memory associated with block device
|
||||
int lfs3_kiwibd_destroy(const struct lfs3_cfg *cfg);
|
||||
|
||||
// Read a block
|
||||
int lfs3_kiwibd_read(const struct lfs3_cfg *cfg, lfs3_block_t block,
|
||||
lfs3_off_t off, void *buffer, lfs3_size_t size);
|
||||
|
||||
// Program a block
|
||||
//
|
||||
// The block must have previously been erased.
|
||||
int lfs3_kiwibd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
|
||||
lfs3_off_t off, const void *buffer, lfs3_size_t size);
|
||||
|
||||
// Erase a block
|
||||
//
|
||||
// A block must be erased before being programmed. The
|
||||
// state of an erased block is undefined.
|
||||
int lfs3_kiwibd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block);
|
||||
|
||||
// Sync the block device
|
||||
int lfs3_kiwibd_sync(const struct lfs3_cfg *cfg);
|
||||
|
||||
|
||||
/// Additional kiwibd features ///
|
||||
|
||||
// Get total amount of bytes read
|
||||
lfs3_kiwibd_sio_t lfs3_kiwibd_readed(const struct lfs3_cfg *cfg);
|
||||
|
||||
// Get total amount of bytes programmed
|
||||
lfs3_kiwibd_sio_t lfs3_kiwibd_proged(const struct lfs3_cfg *cfg);
|
||||
|
||||
// Get total amount of bytes erased
|
||||
lfs3_kiwibd_sio_t lfs3_kiwibd_erased(const struct lfs3_cfg *cfg);
|
||||
|
||||
// Manually set amount of bytes read
|
||||
int lfs3_kiwibd_setreaded(const struct lfs3_cfg *cfg,
|
||||
lfs3_kiwibd_io_t readed);
|
||||
|
||||
// Manually set amount of bytes programmed
|
||||
int lfs3_kiwibd_setproged(const struct lfs3_cfg *cfg,
|
||||
lfs3_kiwibd_io_t proged);
|
||||
|
||||
// Manually set amount of bytes erased
|
||||
int lfs3_kiwibd_seterased(const struct lfs3_cfg *cfg,
|
||||
lfs3_kiwibd_io_t erased);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@ -24,17 +24,17 @@ int lfs3_rambd_createcfg(const struct lfs3_cfg *cfg,
|
||||
|
||||
// allocate buffer?
|
||||
if (bd->cfg->buffer) {
|
||||
bd->buffer = bd->cfg->buffer;
|
||||
bd->mem = bd->cfg->buffer;
|
||||
} else {
|
||||
bd->buffer = lfs3_malloc(cfg->block_size * cfg->block_count);
|
||||
if (!bd->buffer) {
|
||||
bd->mem = lfs3_malloc(cfg->block_size * cfg->block_count);
|
||||
if (!bd->mem) {
|
||||
LFS3_RAMBD_TRACE("lfs3_rambd_createcfg -> %d", LFS3_ERR_NOMEM);
|
||||
return LFS3_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
// zero for reproducibility
|
||||
memset(bd->buffer, 0, cfg->block_size * cfg->block_count);
|
||||
memset(bd->mem, 0, cfg->block_size * cfg->block_count);
|
||||
|
||||
LFS3_RAMBD_TRACE("lfs3_rambd_createcfg -> %d", 0);
|
||||
return 0;
|
||||
@ -60,7 +60,7 @@ int lfs3_rambd_destroy(const struct lfs3_cfg *cfg) {
|
||||
// clean up memory
|
||||
lfs3_rambd_t *bd = cfg->context;
|
||||
if (!bd->cfg->buffer) {
|
||||
lfs3_free(bd->buffer);
|
||||
lfs3_free(bd->mem);
|
||||
}
|
||||
LFS3_RAMBD_TRACE("lfs3_rambd_destroy -> %d", 0);
|
||||
return 0;
|
||||
@ -80,7 +80,7 @@ int lfs3_rambd_read(const struct lfs3_cfg *cfg, lfs3_block_t block,
|
||||
LFS3_ASSERT(off+size <= cfg->block_size);
|
||||
|
||||
// read data
|
||||
memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size);
|
||||
memcpy(buffer, &bd->mem[block*cfg->block_size + off], size);
|
||||
|
||||
LFS3_RAMBD_TRACE("lfs3_rambd_read -> %d", 0);
|
||||
return 0;
|
||||
@ -100,7 +100,7 @@ int lfs3_rambd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block,
|
||||
LFS3_ASSERT(off+size <= cfg->block_size);
|
||||
|
||||
// program data
|
||||
memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size);
|
||||
memcpy(&bd->mem[block*cfg->block_size + off], buffer, size);
|
||||
|
||||
LFS3_RAMBD_TRACE("lfs3_rambd_prog -> %d", 0);
|
||||
return 0;
|
||||
|
||||
@ -29,7 +29,7 @@ struct lfs3_rambd_cfg {
|
||||
|
||||
// rambd state
|
||||
typedef struct lfs3_rambd {
|
||||
uint8_t *buffer;
|
||||
uint8_t *mem;
|
||||
const struct lfs3_rambd_cfg *cfg;
|
||||
} lfs3_rambd_t;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user