From c506c25f195213ea6a07905f42587d836a3b6d9a Mon Sep 17 00:00:00 2001 From: Laczen JMS Date: Tue, 29 Jul 2025 10:32:41 +0200 Subject: [PATCH] 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 --- .../esp32/common/src/esp32_board_spiflash.c | 12 + .../esp32-devkitc/configs/nvblk/defconfig | 65 ++ drivers/mtd/.gitignore | 1 + drivers/mtd/Kconfig | 19 + drivers/mtd/Make.defs | 23 + drivers/mtd/nvblk.c | 559 ++++++++++++++++++ include/nuttx/mtd/mtd.h | 23 + 7 files changed, 702 insertions(+) create mode 100644 boards/xtensa/esp32/esp32-devkitc/configs/nvblk/defconfig create mode 100644 drivers/mtd/nvblk.c diff --git a/boards/xtensa/esp32/common/src/esp32_board_spiflash.c b/boards/xtensa/esp32/common/src/esp32_board_spiflash.c index 9c388b0f6c..3b86557ded 100644 --- a/boards/xtensa/esp32/common/src/esp32_board_spiflash.c +++ b/boards/xtensa/esp32/common/src/esp32_board_spiflash.c @@ -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); diff --git a/boards/xtensa/esp32/esp32-devkitc/configs/nvblk/defconfig b/boards/xtensa/esp32/esp32-devkitc/configs/nvblk/defconfig new file mode 100644 index 0000000000..f388114b89 --- /dev/null +++ b/boards/xtensa/esp32/esp32-devkitc/configs/nvblk/defconfig @@ -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 diff --git a/drivers/mtd/.gitignore b/drivers/mtd/.gitignore index 87e3b49806..ee59a39219 100644 --- a/drivers/mtd/.gitignore +++ b/drivers/mtd/.gitignore @@ -1 +1,2 @@ /dhara +/nvblk diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 38a364f58e..0104ae2177 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -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 diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index e357e8c84c..11a3b2e595 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -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 diff --git a/drivers/mtd/nvblk.c b/drivers/mtd/nvblk.c new file mode 100644 index 0000000000..236eaa58df --- /dev/null +++ b/drivers/mtd/nvblk.c @@ -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 + +#include +#include +#include + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * 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; +} diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h index a1c791dbf5..73a3b529f1 100644 --- a/include/nuttx/mtd/mtd.h +++ b/include/nuttx/mtd/mtd.h @@ -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 *