drivers/mtd: introduce nvblk

NVBLK provides a block device that operates on top of a non volatile
memory (as a mtd device) that enables wear levelling for non volatile
memory. It's operation is similar to the dhara wear levelling library,
but nvblk is meant to be used on smaller (nor, mram, rram) memory.

I am also the author and maintainer of the nvblk library.

A block device can be created during startup by using:
```
nvblk_initialize(0, mtd, CONFIG_MTD_NVBLK_DEFAULT_LBS,
                         CONFIG_MTD_NVBLK_DEFAULT_IOBS,
                         CONFIG_MTD_NVBLK_DEFAULT_SPEB);
```
and a fat filesystem on top of this as:
```
nsh> mkfatfs /dev/mtdblock0
nsh> mount -t vfat /dev/mtdblock0 /mnt
```
this fat filesystem can then be used:
```
nsh> echo "test" > /mnt/test.txt
nsh> cat test.txt
test
nsh>
```

Signed-off-by: Laczen JMS <laczenjms@gmail.com>
This commit is contained in:
Laczen JMS 2025-07-29 10:32:41 +02:00 committed by Xiang Xiao
parent 1c0e1698cd
commit c506c25f19
7 changed files with 702 additions and 0 deletions

View file

@ -397,6 +397,18 @@ static int init_storage_partition(void)
return ret;
}
#elif defined(CONFIG_MTD_NVBLK)
ret = nvblk_initialize("/dev/mtdblock0", mtd,
CONFIG_MTD_NVBLK_DEFAULT_LBS,
CONFIG_MTD_NVBLK_DEFAULT_IOBS,
CONFIG_MTD_NVBLK_DEFAULT_SPEB);
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: Failed to setup nvblk\n");
return ret;
}
#else
ret = register_mtddriver("/dev/esp32flash", mtd, 0755, NULL);

View file

@ -0,0 +1,65 @@
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_ARCH_LEDS is not set
# CONFIG_NSH_ARGCAT is not set
# CONFIG_NSH_CMDOPT_HEXDUMP is not set
CONFIG_ARCH="xtensa"
CONFIG_ARCH_BOARD="esp32-devkitc"
CONFIG_ARCH_BOARD_COMMON=y
CONFIG_ARCH_BOARD_ESP32_DEVKITC=y
CONFIG_ARCH_CHIP="esp32"
CONFIG_ARCH_CHIP_ESP32=y
CONFIG_ARCH_CHIP_ESP32WROVER=y
CONFIG_ARCH_STACKDUMP=y
CONFIG_ARCH_USE_TEXT_HEAP=y
CONFIG_ARCH_XTENSA=y
CONFIG_BOARDCTL_ROMDISK=y
CONFIG_BOARD_LOOPSPERMSEC=16717
CONFIG_BUILTIN=y
CONFIG_DEBUG_FULLOPT=y
CONFIG_DEBUG_SYMBOLS=y
CONFIG_ESP32_IRAM_HEAP=y
CONFIG_ESP32_SPIFLASH=y
CONFIG_ESP32_UART0=y
CONFIG_EXAMPLES_HELLO=y
CONFIG_EXECFUNCS_HAVE_SYMTAB=y
CONFIG_EXECFUNCS_SYSTEM_SYMTAB=y
CONFIG_FS_FAT=y
CONFIG_FS_PROCFS=y
CONFIG_HAVE_CXX=y
CONFIG_HAVE_CXXINITIALIZE=y
CONFIG_IDLETHREAD_STACKSIZE=3072
CONFIG_INIT_ENTRYPOINT="nsh_main"
CONFIG_INIT_STACKSIZE=3072
CONFIG_INTELHEX_BINARY=y
CONFIG_LIBC_EXECFUNCS=y
CONFIG_LIBC_PERROR_STDOUT=y
CONFIG_LIBC_STRERROR=y
CONFIG_LINE_MAX=64
CONFIG_MM_REGIONS=3
CONFIG_MTD_NVBLK=y
CONFIG_NSH_ARCHINIT=y
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_FILEIOSIZE=512
CONFIG_NSH_READLINE=y
CONFIG_PREALLOC_TIMERS=4
CONFIG_RAM_SIZE=114688
CONFIG_RAM_START=0x20000000
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_HPWORK=y
CONFIG_SCHED_LPWORK=y
CONFIG_SCHED_WAITPID=y
CONFIG_STACK_COLORATION=y
CONFIG_START_DAY=6
CONFIG_START_MONTH=12
CONFIG_START_YEAR=2011
CONFIG_SYMTAB_ORDEREDBYNAME=y
CONFIG_SYSLOG_BUFFER=y
CONFIG_SYSTEM_NSH=y
CONFIG_TLS_NELEM=4
CONFIG_UART0_SERIAL_CONSOLE=y

View file

@ -1 +1,2 @@
/dhara
/nvblk

View file

@ -1459,6 +1459,25 @@ config DHARA_READ_NCACHES
default 4
endif
config MTD_NVBLK
bool "MTD using Non Volatile BLock driver"
default n
if MTD_NVBLK
config MTD_NVBLK_DEFAULT_LBS
int "nvblk (logical) block size"
default 256
config MTD_NVBLK_DEFAULT_IOBS
int "nvblk io block size (block device sector size)"
default 512
config MTD_NVBLK_DEFAULT_SPEB
int "nvblk spare erase blocks"
default 1
endif
config MTD_CFI
bool "CFI(common flash interface) NOR FLASH"
default n

View file

@ -208,6 +208,29 @@ CSRCS += mtd/dhara/dhara/journal.c
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)drivers$(DELIM)mtd$(DELIM)dhara
endif
ifeq ($(CONFIG_MTD_NVBLK),y)
main.zip:
$(call DOWNLOAD,https://github.com/Laczen/nvblk/archive/refs/heads,main.zip,mtd/nvblk-main.zip)
.nvblkunpack: main.zip
$(Q) unzip mtd/nvblk-main.zip
$(call DELFILE, mtd/nvblk-main.zip)
$(Q) mv nvblk-main mtd/nvblk
$(Q) touch mtd/nvblk/.nvblkunpack
ifeq ($(wildcard mtd/nvblk/.gitignore),)
context:: .nvblkunpack
endif
distclean::
$(call DELDIR, mtd/nvblk)
CSRCS += nvblk.c
CSRCS += mtd/nvblk/src/nvblk.c
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)drivers$(DELIM)mtd$(DELIM)nvblk$(DELIM)include
endif
ifeq ($(CONFIG_MTD_CFI),y)
CSRCS += mtd_cfi.c
CSRCS += cfi.c

559
drivers/mtd/nvblk.c Normal file
View file

@ -0,0 +1,559 @@
/****************************************************************************
* drivers/mtd/nvblk.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <debug.h>
#include <stdio.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/lib/lib.h>
#include <nvblk/nvblk.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define NVBLK_MIN_LBS 128 /* Minimal logical block size */
/****************************************************************************
* Private Types
****************************************************************************/
struct nvblk_dev_s
{
struct nvb_config cfg; /* nvblk configuration */
struct nvb_info info; /* nvblk data */
mutex_t lock;
FAR struct mtd_dev_s *mtd; /* Contained MTD interface */
struct mtd_geometry_s geo; /* Device geometry */
uint16_t refs; /* Number of references */
uint8_t log2_bpiob; /* (logical) blocks per IO block */
uint8_t log2_ppb; /* pages per (logical) block */
bool unlinked; /* The driver has been unlinked */
/* Two pagesize buffer first is for working temp buffer
* second is for journel use
*/
FAR uint8_t *pagebuf;
};
typedef struct nvblk_dev_s nvblk_dev_t;
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int nvblk_open(FAR struct inode *inode);
static int nvblk_close(FAR struct inode *inode);
static ssize_t nvblk_read(FAR struct inode *inode,
FAR unsigned char *buffer,
blkcnt_t start_sector,
unsigned int nsectors);
static ssize_t nvblk_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t start_sector,
unsigned int nsectors);
static int nvblk_geometry(FAR struct inode *inode,
FAR struct geometry *geometry);
static int nvblk_ioctl(FAR struct inode *inode,
int cmd,
unsigned long arg);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int nvblk_unlink(FAR struct inode *inode);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
static const struct block_operations g_nvblk_bops =
{
nvblk_open, /* open */
nvblk_close, /* close */
nvblk_read, /* read */
nvblk_write, /* write */
nvblk_geometry, /* geometry */
nvblk_ioctl /* ioctl */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, nvblk_unlink /* unlink */
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
static int nvblk_convert_result(int err)
{
switch (err)
{
case 0:
return 0;
case -NVB_ENOENT:
return -ENOENT;
case -NVB_EINVAL:
return -EINVAL;
case -NVB_EROFS:
return -EROFS;
case -NVB_EAGAIN:
return -EAGAIN;
case -NVB_ENOSPC:
return -ENOSPC;
default:
return -EFAULT;
}
}
/****************************************************************************
* Name: nvblk_open
*
* Description: Open the block device
*
****************************************************************************/
static int nvblk_open(FAR struct inode *inode)
{
FAR nvblk_dev_t *dev;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
nxmutex_lock(&dev->lock);
dev->refs++;
nxmutex_unlock(&dev->lock);
return 0;
}
/****************************************************************************
* Name: nvblk_close
*
* Description: close the block device
*
****************************************************************************/
static int nvblk_close(FAR struct inode *inode)
{
FAR nvblk_dev_t *dev;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
nxmutex_lock(&dev->lock);
(void)nvb_sync(&dev->info);
dev->refs--;
nxmutex_unlock(&dev->lock);
if (dev->refs == 0 && dev->unlinked)
{
nxmutex_destroy(&dev->lock);
kmm_free(dev->pagebuf);
kmm_free(dev);
}
return 0;
}
/****************************************************************************
* Name: nvblk_read
*
* Description: Read the specified number of sectors
*
****************************************************************************/
static ssize_t nvblk_read(FAR struct inode *inode,
FAR unsigned char *buffer,
blkcnt_t start_sector,
unsigned int nsectors)
{
FAR nvblk_dev_t *dev;
uint16_t bstart;
uint16_t bcnt;
uint16_t n;
int ret = 0;
finfo("Read %lld %zd\n", (long long)start_sector, nsectors);
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
nxmutex_lock(&dev->lock);
bstart = start_sector << dev->log2_bpiob;
bcnt = nsectors << dev->log2_bpiob;
for (n = 0; n < bcnt; n++)
{
ret = nvb_read(&dev->info, buffer, bstart, 1);
if (ret == -NVB_ENOENT)
{
memset(buffer, 0xff, (1 << dev->cfg.log2_bs));
ret = 0;
}
if (ret < 0)
{
break;
}
buffer += (1 << dev->cfg.log2_bs);
bstart++;
}
if (ret < 0)
{
ret = nvblk_convert_result(ret);
ferr("Read startblock %lld failed nsectors %zd err: %d\n",
(long long)start_sector, nsectors, ret);
}
nxmutex_unlock(&dev->lock);
return ret < 0 ? ret : nsectors;
}
/****************************************************************************
* Name: nvblk_write
*
* Description: Write the specified number of sectors
*
****************************************************************************/
static ssize_t nvblk_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t start_sector,
unsigned int nsectors)
{
FAR nvblk_dev_t *dev;
uint16_t bstart;
uint16_t bcnt;
int ret = 0;
finfo("Write %lld %zd\n", (long long)start_sector, nsectors);
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
nxmutex_lock(&dev->lock);
bstart = start_sector << dev->log2_bpiob;
bcnt = nsectors << dev->log2_bpiob;
ret = nvblk_convert_result(nvb_write(&dev->info, buffer, bstart, bcnt));
if (ret < 0)
{
ferr("Write startblock %lld failed nsectors %zd err: %d\n",
(long long)start_sector, nsectors, ret);
}
nxmutex_unlock(&dev->lock);
return ret < 0 ? ret : nsectors;
}
/****************************************************************************
* Name: nvblk_geometry
*
* Description: Return device geometry
*
****************************************************************************/
static int nvblk_geometry(FAR struct inode *inode,
FAR struct geometry *geometry)
{
FAR nvblk_dev_t *dev;
uint32_t blkcnt;
int ret = -EINVAL;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
if (!geometry)
{
return ret;
}
nxmutex_lock(&dev->lock);
ret = nvb_ioctl(&dev->info, NVB_CMD_GET_BLK_COUNT, &blkcnt);
if (ret < 0)
{
ret = nvblk_convert_result(ret);
goto end;
}
geometry->geo_available = true;
geometry->geo_mediachanged = false;
geometry->geo_writeenabled = true;
geometry->geo_nsectors = blkcnt >> dev->log2_bpiob;
geometry->geo_sectorsize = (1 << (dev->log2_bpiob + dev->cfg.log2_bs));
strlcpy(geometry->geo_model, dev->geo.model, sizeof(geometry->geo_model));
end:
nxmutex_unlock(&dev->lock);
return ret;
}
/****************************************************************************
* Name: nvblk_ioctl
*
* Description:
* Set/Get option to/from block device.
*
* No ioctl commands are supported.
*
****************************************************************************/
static int nvblk_ioctl(FAR struct inode *inode,
int cmd,
unsigned long arg)
{
FAR nvblk_dev_t *dev;
int ret;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
nxmutex_lock(&dev->lock);
switch (cmd)
{
case BIOC_FLUSH:
{
ret = nvb_sync(&dev->info);
if (ret < 0)
{
ferr("sync failed: %d\n", ret);
}
}
break;
default:
ret = -ENOTTY;
break;
}
nxmutex_unlock(&dev->lock);
return ret;
}
/****************************************************************************
* Name: nvblk_unlink
*
* Description: Unlink the device
*
****************************************************************************/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int nvblk_unlink(FAR struct inode *inode)
{
FAR nvblk_dev_t *dev;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
nxmutex_lock(&dev->lock);
dev->unlinked = true;
nxmutex_unlock(&dev->lock);
if (dev->refs == 0)
{
nxmutex_destroy(&dev->lock);
kmm_free(dev->pagebuf);
kmm_free(dev);
}
return 0;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/* nvblk cfg interface implementation */
int nvblk_cfg_read(FAR const struct nvb_config *cfg,
uint32_t p, void *buf)
{
FAR nvblk_dev_t *dev = container_of(cfg, FAR nvblk_dev_t, cfg);
if (MTD_BREAD(dev->mtd, p << dev->log2_ppb, 1 << dev->log2_ppb, buf) < 0)
{
return -NVB_EFAULT;
}
return 0;
}
int nvblk_cfg_prog(FAR const struct nvb_config *cfg,
uint32_t p, const void *buf)
{
FAR nvblk_dev_t *dev = container_of(cfg, FAR nvblk_dev_t, cfg);
if ((p % (1 << cfg->log2_bpeb)) == 0)
{
if (MTD_ERASE(dev->mtd, p >> cfg->log2_bpeb, 1U) < 0)
{
return -NVB_EFAULT;
}
}
if (MTD_BWRITE(dev->mtd, p << dev->log2_ppb, 1 << dev->log2_ppb, buf) < 0)
{
return -NVB_EFAULT;
}
return 0;
}
/****************************************************************************
* Name: nvblk_initialize_by_path
*
* Description:
* Initialize to provide a block driver wrapper around an MTD interface
*
* Input Parameters:
* path - The block device path.
* mtd - The MTD device that supports the FLASH interface.
* lbs - The logical blocksize (size of the nvblk blocks).
* iobs - The input output blocksize (multiple of lbs).
* speb - The number of spare erase blocks.
*
****************************************************************************/
int nvblk_initialize(FAR const char *path,
FAR struct mtd_dev_s *mtd,
uint32_t lbs,
uint32_t iobs,
uint32_t speb)
{
FAR nvblk_dev_t *dev;
int ret;
/* Sanity check */
if (path == NULL || mtd == NULL || lbs < NVBLK_MIN_LBS || lbs > iobs ||
iobs == 0U || (iobs & (iobs - 1U)) != 0U ||
lbs == 0U || (lbs & (lbs - 1U)) != 0U)
{
return -EINVAL;
}
finfo("path=\"%s\"\n", path);
/* Allocate a NVBLK_MTDBLOCK device structure */
dev = (FAR nvblk_dev_t *)kmm_zalloc(sizeof(nvblk_dev_t));
if (dev == NULL)
{
return -ENOMEM;
}
nxmutex_init(&dev->lock);
/* Initialize the NVBLK_MTDBLOCK device structure */
dev->mtd = mtd;
/* Get the device geometry. (casting to uintptr_t first
* eliminates complaints on some architectures where the
* sizeof long is different
* from the size of a pointer).
*/
ret = MTD_IOCTL(mtd,
MTDIOC_GEOMETRY,
(unsigned long)((uintptr_t)&dev->geo));
if (ret < 0)
{
ferr("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
goto err;
}
/* Validate the number of R/W blocks per erase block */
if ((dev->geo.erasesize % lbs) != 0 || (lbs % dev->geo.blocksize) != 0 ||
(dev->geo.neraseblocks <= speb))
{
ferr("NVBLK bad config\n");
ret = -EINVAL;
goto err;
}
/* Init nvblk */
dev->log2_bpiob = fls(iobs / lbs) - 1;
dev->log2_ppb = fls(lbs / dev->geo.blocksize) - 1;
dev->cfg.log2_bs = fls(lbs) - 1;
dev->cfg.log2_bpeb = fls(dev->geo.erasesize / lbs) - 1;
dev->cfg.eb = dev->geo.neraseblocks;
dev->cfg.sp_eb = speb;
dev->pagebuf = kmm_zalloc(lbs * 2);
if (!dev->pagebuf)
{
ret = -ENOMEM;
goto err;
}
dev->cfg.mb = &dev->pagebuf[0];
dev->cfg.gb = &dev->pagebuf[lbs];
dev->cfg.read = nvblk_cfg_read;
dev->cfg.prog = nvblk_cfg_prog;
finfo("Initializing nvblk\n");
ret = nvb_init(&dev->info, &dev->cfg);
if (ret < 0)
{
ferr("failed to initialize nvblk: %d\n", ret);
goto err;
}
finfo("succeeded initializing nvblk\n");
finfo("Physical Size: ebcnt [%d] bcnt [%d]", dev->cfg.eb,
dev->cfg.eb << dev->cfg.log2_bpeb);
finfo("Head at mblock [%d], Tail at mblock [%d]\n", dev->info.head,
dev->info.tail);
finfo("Root at mblock [%d]\n", dev->info.root);
finfo("cpe [%d], tail_cpe [%d]\n", dev->info.cpe, dev->info.tail_cpe);
finfo("used [%d], pass [%x]\n", dev->info.used, dev->info.pass);
/* Inode private data is a reference to the
* NVBLK_MTDBLOCK device structure
*/
ret = register_blockdriver(path, &g_nvblk_bops, 0666, dev);
if (ret < 0)
{
ferr("register_blockdriver failed: %d\n", ret);
goto err;
}
return ret;
err:
nxmutex_destroy(&dev->lock);
kmm_free(dev->pagebuf);
kmm_free(dev);
return ret;
}

View file

@ -820,6 +820,29 @@ int dhara_initialize_by_path(FAR const char *path,
FAR struct mtd_dev_s *mtd);
#endif
/****************************************************************************
* Name: nvblk_initialize
*
* Description:
* Initialize to provide a block driver wrapper around an MTD interface
*
* Input Parameters:
* path - The block device path.
* mtd - The MTD device that supports the FLASH interface.
* lbs - The logical blocksize (size of the nvblk blocks).
* iobs - The input output blocksize (multiple of lbs).
* speb - The number of spare erase blocks.
*
****************************************************************************/
#ifdef CONFIG_MTD_NVBLK
int nvblk_initialize(FAR const char *path,
FAR struct mtd_dev_s *mtd,
uint32_t lbs,
uint32_t iobs,
uint32_t speb);
#endif
/****************************************************************************
* Name: register_cfi_driver
*