You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2022/09/15 09:47:05 UTC
[incubator-nuttx] branch master updated: drivers/mtd:init commit of power-loss resilient cfg
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new f6ebbe4429 drivers/mtd:init commit of power-loss resilient cfg
f6ebbe4429 is described below
commit f6ebbe442970b4cfbf4f3941491be1aa592d0240
Author: 田昕 <ti...@xiaomi.com>
AuthorDate: Wed Aug 10 22:11:29 2022 +0800
drivers/mtd:init commit of power-loss resilient cfg
Signed-off-by: 田昕 <ti...@xiaomi.com>
---
boards/risc-v/esp32c3/esp32c3-devkit/Kconfig | 3 +
.../esp32c3-devkit/configs/nvcfgdata/defconfig | 52 +
.../esp32c3/esp32c3-devkit/src/esp32c3_spiflash.c | 31 +
drivers/mtd/Kconfig | 9 +
drivers/mtd/Make.defs | 8 +-
drivers/mtd/mtd_config.c | 35 +
drivers/mtd/mtd_config_fs.c | 2086 ++++++++++++++++++++
include/nuttx/mtd/configdata.h | 15 +
8 files changed, 2238 insertions(+), 1 deletion(-)
diff --git a/boards/risc-v/esp32c3/esp32c3-devkit/Kconfig b/boards/risc-v/esp32c3/esp32c3-devkit/Kconfig
index feb82692f2..fe9591a434 100644
--- a/boards/risc-v/esp32c3/esp32c3-devkit/Kconfig
+++ b/boards/risc-v/esp32c3/esp32c3-devkit/Kconfig
@@ -109,6 +109,9 @@ choice ESP32C3_SPIFLASH_FS
bool "LittleFS"
select FS_LITTLEFS
+ config ESP32C3_SPIFLASH_MTD_CONFIG
+ bool "Non-volatile storage"
+
endchoice
config ESP32C3_SPIFLASH_FS_MOUNT_PT
diff --git a/boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig b/boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig
new file mode 100644
index 0000000000..5a8fa6a3d4
--- /dev/null
+++ b/boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig
@@ -0,0 +1,52 @@
+#
+# 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_NSH_ARGCAT is not set
+# CONFIG_NSH_CMDOPT_HEXDUMP is not set
+# CONFIG_NSH_CMDPARMS is not set
+CONFIG_ARCH="risc-v"
+CONFIG_ARCH_BOARD="esp32c3-devkit"
+CONFIG_ARCH_BOARD_ESP32C3_DEVKIT=y
+CONFIG_ARCH_CHIP="esp32c3"
+CONFIG_ARCH_CHIP_ESP32C3=y
+CONFIG_ARCH_CHIP_ESP32C3WROOM02=y
+CONFIG_ARCH_INTERRUPTSTACK=1536
+CONFIG_ARCH_RISCV=y
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_BCH=y
+CONFIG_BOARD_LOOPSPERMSEC=15000
+CONFIG_BUILTIN=y
+CONFIG_DEV_ZERO=y
+CONFIG_ESP32C3_SPIFLASH=y
+CONFIG_ESP32C3_SPIFLASH_MTD_CONFIG=y
+CONFIG_ESP32C3_STORAGE_MTD_OFFSET=0x3e6000
+CONFIG_ESP32C3_STORAGE_MTD_SIZE=0x3000
+CONFIG_FS_PROCFS=y
+CONFIG_IDLETHREAD_STACKSIZE=2048
+CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_INTELHEX_BINARY=y
+CONFIG_LIBC_PERROR_STDOUT=y
+CONFIG_LIBC_STRERROR=y
+CONFIG_MTD_CONFIG=y
+CONFIG_MTD_CONFIG_FAIL_SAFE=y
+CONFIG_MTD_CONFIG_NAMED=y
+CONFIG_NFILE_DESCRIPTORS_PER_BLOCK=6
+CONFIG_NSH_ARCHINIT=y
+CONFIG_NSH_BUILTIN_APPS=y
+CONFIG_NSH_FILEIOSIZE=512
+CONFIG_NSH_READLINE=y
+CONFIG_NSH_STRERROR=y
+CONFIG_PREALLOC_TIMERS=0
+CONFIG_RR_INTERVAL=200
+CONFIG_SCHED_WAITPID=y
+CONFIG_START_DAY=29
+CONFIG_START_MONTH=11
+CONFIG_START_YEAR=2019
+CONFIG_SYSTEM_CFGDATA=y
+CONFIG_SYSTEM_NSH=y
+CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE=y
+CONFIG_UART0_SERIAL_CONSOLE=y
diff --git a/boards/risc-v/esp32c3/esp32c3-devkit/src/esp32c3_spiflash.c b/boards/risc-v/esp32c3/esp32c3-devkit/src/esp32c3_spiflash.c
index 8d40796925..c6386f6f29 100644
--- a/boards/risc-v/esp32c3/esp32c3-devkit/src/esp32c3_spiflash.c
+++ b/boards/risc-v/esp32c3/esp32c3-devkit/src/esp32c3_spiflash.c
@@ -37,6 +37,7 @@
#include <nuttx/kmalloc.h>
#include <nuttx/spi/spi.h>
#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
#include <nuttx/fs/nxffs.h>
#ifdef CONFIG_BCH
#include <nuttx/drivers/drivers.h>
@@ -500,6 +501,36 @@ static int init_storage_partition(void)
return ret;
}
+#elif defined (CONFIG_ESP32C3_SPIFLASH_MTD_CONFIG)
+
+#if defined (CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE)
+
+ /* To test power-loss resilient kv system,
+ * we write possible power-loss flash layout into flash
+ * then start kv system to see if it recovers.
+ * To do so, we need a mtd driver so that test code can
+ * write into flash.
+ */
+
+ const char *path = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME;
+ ret = register_mtddriver(path, mtd, 0777, NULL);
+ if (ret < 0)
+ {
+ ferr("ERROR: Failed to register MTD: %d\n", ret);
+ return ret;
+ }
+
+#else
+
+ ret = mtdconfig_register(mtd);
+ if (ret < 0)
+ {
+ ferr("ERROR: Failed to setup mtd config\n");
+ return ret;
+ }
+
+#endif
+
#else
ret = register_mtddriver("/dev/esp32c3flash", mtd, 0755, NULL);
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 7db54f6644..a07389d5ae 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -184,6 +184,15 @@ config MTD_CONFIG_NAME_LEN
---help---
Sets the maximum length of config item names.
+config MTD_CONFIG_FAIL_SAFE
+ bool "Enable Fail Safe MTD Config"
+ default n
+ depends on MTD_BYTE_WRITE
+ ---help---
+ Enable the new storage layout to support the resilient to power loss.
+ This replaces the drivers/mtd/mtd_config, which
+ is resilient to power loss.
+
endif # MTD_CONFIG
comment "MTD Device Drivers"
diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs
index 461c0c6a80..bafb1bbf89 100644
--- a/drivers/mtd/Make.defs
+++ b/drivers/mtd/Make.defs
@@ -27,7 +27,13 @@
ifeq ($(CONFIG_MTD),y)
-CSRCS += ftl.c mtd_config.c
+CSRCS += ftl.c
+
+ifeq ($(CONFIG_MTD_CONFIG_FAIL_SAFE),y)
+CSRCS += mtd_config_fs.c
+else ifeq ($(CONFIG_MTD_CONFIG),y)
+CSRCS += mtd_config.c
+endif
ifeq ($(CONFIG_MTD_PARTITION),y)
CSRCS += mtd_partition.c
diff --git a/drivers/mtd/mtd_config.c b/drivers/mtd/mtd_config.c
index 35f8ddfd36..ade51d77f2 100644
--- a/drivers/mtd/mtd_config.c
+++ b/drivers/mtd/mtd_config.c
@@ -1786,4 +1786,39 @@ int mtdconfig_register(FAR struct mtd_dev_s *mtd)
errout:
return ret;
}
+
+/****************************************************************************
+ * Name: mtdconfig_unregister
+ *
+ * Description:
+ * Unregister a /dev/config device backed by a MTD.
+ *
+ ****************************************************************************/
+
+int mtdconfig_unregister(void)
+{
+ int ret;
+ struct file file;
+ FAR struct inode *inode;
+ FAR struct mtdconfig_struct_s *dev;
+
+ ret = file_open(&file, "/dev/config", 0);
+ if (ret < 0)
+ {
+ ferr("ERROR: open /dev/config failed: %d\n", ret);
+ return ret;
+ }
+
+ inode = file.f_inode;
+ dev = (FAR struct mtdconfig_struct_s *)inode->i_private;
+ nxmutex_destroy(&dev->exclsem);
+ kmm_free(dev);
+
+ file_close(&file);
+
+ unregister_driver("/dev/config");
+
+ return OK;
+}
+
#endif /* CONFIG_MTD_CONFIG */
diff --git a/drivers/mtd/mtd_config_fs.c b/drivers/mtd/mtd_config_fs.c
new file mode 100644
index 0000000000..1a1e066543
--- /dev/null
+++ b/drivers/mtd/mtd_config_fs.c
@@ -0,0 +1,2086 @@
+/****************************************************************************
+ * drivers/mtd/mtd_config_fs.c
+ *
+ * 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.
+ *
+ * NVS: non volatile storage in flash
+ *
+ * Copyright (c) 2018 Laczen
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/crc8.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
+/* MASKS AND SHIFT FOR ADDRESSES
+ * an address in nvs is an uint32_t where:
+ * high 2 bytes represent the block number
+ * low 2 bytes represent the offset in a block
+ */
+
+#define ADDR_BLOCK_MASK 0xFFFF0000
+#define ADDR_BLOCK_SHIFT 16
+#define ADDR_OFFS_MASK 0x0000FFFF
+
+/* We don't want to store all the read content in stack or heap,
+ * so we make a buffer to do compare or move.
+ */
+
+#define NVS_BUFFER_SIZE 32
+
+/* If data is written after last ate, and power loss happens,
+ * we need to find a clean offset by skipping dirty data.
+ * This macro defines how many bytes to skip when dirty data
+ * is spotted(may take several skips).
+ * Normally 1 byte is okay, such process only happens when
+ * nvs is started, and it is acceptable to take some time during
+ * starting.
+ */
+
+#define NVS_CORRUPT_DATA_SKIP_STEP 1
+
+/* Gc done or close ate has the id of 0xffffffff.
+ * We can tell if the ate is special by looking at its id.
+ */
+
+#define NVS_SPECIAL_ATE_ID 0xffffffff
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Non-volatile Storage File system structure */
+
+struct nvs_fs
+{
+ FAR struct mtd_dev_s *mtd; /* MTD device */
+ struct mtd_geometry_s geo;
+ uint8_t erasestate; /* Erased value */
+ uint32_t ate_wra; /* Next alloc table entry
+ * Write address
+ */
+ uint32_t data_wra; /* Next data write address */
+ uint32_t step_addr; /* For traverse */
+ mutex_t nvs_lock;
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+ uint32_t id; /* Data id */
+ uint16_t offset; /* Data offset within block */
+ uint16_t len; /* Data len within block */
+ uint16_t key_len; /* Key string len */
+ uint8_t part; /* Part of a multipart data - future extension */
+ uint8_t crc8; /* Crc8 check of the ate entry */
+ uint8_t expired; /* 0xFF-newest entry, others-old entry */
+ uint8_t reserved[3]; /* For future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int mtdconfig_open(FAR struct file *filep);
+static int mtdconfig_close(FAR struct file *filep);
+static ssize_t mtdconfig_read(FAR struct file *filep, FAR char *buffer,
+ size_t buflen);
+static int mtdconfig_ioctl(FAR struct file *filep, int cmd,
+ unsigned long arg);
+static int mtdconfig_poll(FAR struct file *filep, FAR struct pollfd *fds,
+ bool setup);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations g_mtdnvs_fops =
+{
+ mtdconfig_open, /* Open */
+ mtdconfig_close, /* Close */
+ mtdconfig_read, /* Read */
+ NULL, /* Write */
+ NULL, /* Seek */
+ mtdconfig_ioctl, /* Ioctl */
+ mtdconfig_poll /* Poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ , NULL /* Unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nvs_fnv_hash
+ ****************************************************************************/
+
+static uint32_t nvs_fnv_hash(FAR const uint8_t *input, uint32_t len)
+{
+ uint32_t i = 0;
+ uint32_t hval = 2166136261;
+
+ /* FNV-1 hash each octet in the buffer */
+
+ while (i++ < len)
+ {
+ /* Multiply by the 32 bit FNV magic prime mod 2^32 */
+
+ hval *= 0x01000193;
+
+ /* Xor the bottom with the current octet */
+
+ hval ^= *input++;
+ }
+
+ return hval;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_wrt
+ *
+ * Description:
+ * Flash routines, process offset then write.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_wrt(FAR struct nvs_fs *fs, uint32_t addr,
+ FAR const void *data, size_t len)
+{
+ off_t offset;
+ int ret;
+
+ offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+ offset += addr & ADDR_OFFS_MASK;
+
+ ret = MTD_WRITE(fs->mtd, offset, len, data);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_rd
+ *
+ * Description:
+ * Basic flash read from nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_rd(FAR struct nvs_fs *fs, uint32_t addr,
+ FAR void *data, size_t len)
+{
+ off_t offset;
+ int ret;
+
+ offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+ offset += addr & ADDR_OFFS_MASK;
+
+ ret = MTD_READ(fs->mtd, offset, len, data);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_ate_wrt
+ *
+ * Description:
+ * Allocation entry write.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_ate_wrt(FAR struct nvs_fs *fs,
+ FAR const struct nvs_ate *entry)
+{
+ int rc;
+
+ rc = nvs_flash_wrt(fs, fs->ate_wra, entry, sizeof(struct nvs_ate));
+ fs->ate_wra -= sizeof(struct nvs_ate);
+
+ return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_data_wrt
+ ****************************************************************************/
+
+static int nvs_flash_data_wrt(FAR struct nvs_fs *fs,
+ FAR const void *data, size_t len)
+{
+ int rc;
+
+ rc = nvs_flash_wrt(fs, fs->data_wra, data, len);
+ fs->data_wra += len;
+ finfo("write data done, data_wra=0x%" PRIx32 "\n",
+ fs->data_wra);
+
+ return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_ate_rd
+ ****************************************************************************/
+
+static int nvs_flash_ate_rd(FAR struct nvs_fs *fs, uint32_t addr,
+ FAR struct nvs_ate *entry)
+{
+ return nvs_flash_rd(fs, addr, entry, sizeof(struct nvs_ate));
+}
+
+/****************************************************************************
+ * Name: nvs_flash_block_cmp
+ *
+ * Description:
+ * Compares the data in flash at addr to data
+ * in blocks of size NVS_BLOCK_SIZE aligned to fs->write_block_size.
+ * Returns 0 if equal, 1 if not equal, errcode if error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_block_cmp(FAR struct nvs_fs *fs, uint32_t addr,
+ FAR const void *data, size_t len)
+{
+ FAR const uint8_t *data8 = (FAR const uint8_t *)data;
+ int rc;
+ size_t bytes_to_cmp;
+ uint8_t buf[NVS_BUFFER_SIZE];
+
+ while (len > 0)
+ {
+ bytes_to_cmp = MIN(NVS_BUFFER_SIZE, len);
+ rc = nvs_flash_rd(fs, addr, buf, bytes_to_cmp);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = memcmp(data8, buf, bytes_to_cmp);
+ if (rc)
+ {
+ return 1;
+ }
+
+ len -= bytes_to_cmp;
+ addr += bytes_to_cmp;
+ data8 += bytes_to_cmp;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_direct_cmp
+ *
+ * Description:
+ * Compares the data in flash at addr1 and addr2
+ * of len in blocks of size NVS_BLOCK_SIZE aligned to fs->write_block_size.
+ * Returns 0 if equal, 1 if not equal, errcode if error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_direct_cmp(FAR struct nvs_fs *fs, uint32_t addr1,
+ uint32_t addr2, size_t len)
+{
+ int rc;
+ size_t bytes_to_cmp;
+ uint8_t buf1[NVS_BUFFER_SIZE];
+ uint8_t buf2[NVS_BUFFER_SIZE];
+
+ while (len > 0)
+ {
+ bytes_to_cmp = MIN(NVS_BUFFER_SIZE, len);
+ rc = nvs_flash_rd(fs, addr1, buf1, bytes_to_cmp);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = nvs_flash_rd(fs, addr2, buf2, bytes_to_cmp);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = memcmp(buf1, buf2, bytes_to_cmp);
+ if (rc)
+ {
+ return 1;
+ }
+
+ len -= bytes_to_cmp;
+ addr1 += bytes_to_cmp;
+ addr2 += bytes_to_cmp;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_cmp_const
+ *
+ * Description:
+ * Compares the data in flash at addr to a constant
+ * value. returns 0 if all data in flash is equal to value, 1 if not equal,
+ * errcode if error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_cmp_const(FAR struct nvs_fs *fs, uint32_t addr,
+ uint8_t value, size_t len)
+{
+ int rc;
+ size_t bytes_to_cmp;
+ uint8_t cmp[NVS_BUFFER_SIZE];
+
+ memset(cmp, value, NVS_BUFFER_SIZE);
+ while (len > 0)
+ {
+ bytes_to_cmp = MIN(NVS_BUFFER_SIZE, len);
+ rc = nvs_flash_block_cmp(fs, addr, cmp, bytes_to_cmp);
+ if (rc)
+ {
+ return rc;
+ }
+
+ len -= bytes_to_cmp;
+ addr += bytes_to_cmp;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_block_move
+ *
+ * Description:
+ * Move a block at addr to the current data write
+ * location and updates the data write location.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_block_move(FAR struct nvs_fs *fs, uint32_t addr,
+ size_t len)
+{
+ int rc;
+ size_t bytes_to_copy;
+ uint8_t buf[NVS_BUFFER_SIZE];
+
+ while (len)
+ {
+ bytes_to_copy = MIN(NVS_BUFFER_SIZE, len);
+ rc = nvs_flash_rd(fs, addr, buf, bytes_to_copy);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = nvs_flash_data_wrt(fs, buf, bytes_to_copy);
+ if (rc)
+ {
+ return rc;
+ }
+
+ len -= bytes_to_copy;
+ addr += bytes_to_copy;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_erase_block
+ *
+ * Description:
+ * Erase a block by first checking it is used and then erasing if required.
+ * Return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_block(FAR struct nvs_fs *fs, uint32_t addr)
+{
+ int rc;
+
+ finfo("Erasing addr %" PRIx32 "\n", addr);
+ rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+ if (rc < 0)
+ {
+ ferr("Erasing failed %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_ate_crc8_update
+ *
+ * Description:
+ * Crc update on allocation entry.
+ *
+ ****************************************************************************/
+
+static void nvs_ate_crc8_update(FAR struct nvs_ate *entry)
+{
+ uint8_t ate_crc;
+
+ ate_crc = crc8part((FAR const uint8_t *)entry,
+ offsetof(struct nvs_ate, crc8), 0xff);
+ entry->crc8 = ate_crc;
+}
+
+/****************************************************************************
+ * Name: nvs_ate_crc8_check
+ *
+ * Description:
+ * Crc check on allocation entry.
+ * Returns 0 if OK, 1 on crc fail.
+ *
+ ****************************************************************************/
+
+static int nvs_ate_crc8_check(FAR const struct nvs_ate *entry)
+{
+ uint8_t ate_crc;
+
+ ate_crc = crc8part((FAR const uint8_t *)entry,
+ offsetof(struct nvs_ate, crc8), 0xff);
+ if (ate_crc == entry->crc8)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_ate_cmp_const
+ *
+ * Description:
+ * Compares an ATE to a constant value. returns 0 if
+ * the whole ATE is equal to value, 1 if not equal.
+ *
+ ****************************************************************************/
+
+static int nvs_ate_cmp_const(FAR const struct nvs_ate *entry,
+ uint8_t value)
+{
+ FAR const uint8_t *data8 = (FAR const uint8_t *)entry;
+ int i;
+
+ for (i = 0; i < sizeof(struct nvs_ate); i++)
+ {
+ if (data8[i] != value)
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_ate_valid
+ *
+ * Description:
+ * Return 1 if crc8 and offset valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+ FAR const struct nvs_ate *entry)
+{
+ if (nvs_ate_crc8_check(entry) ||
+ entry->offset >= (fs->geo.erasesize - sizeof(struct nvs_ate)))
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ * Validates an block close ate:
+ * A valid block close ate:
+ * - Calid ate.
+ * - Len = 0 and id = 0xFFFFFFFF.
+ * - Offset points to location at ate multiple from block size.
+ * Return 1 if valid, 0 otherwise.
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+ FAR const struct nvs_ate *entry)
+{
+ if (!nvs_ate_valid(fs, entry) || entry->len != 0 ||
+ entry->id != NVS_SPECIAL_ATE_ID)
+ {
+ return 0;
+ }
+
+ if ((fs->geo.erasesize - entry->offset) % sizeof(struct nvs_ate))
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_flash_wrt_entry
+ *
+ * Description:
+ * Store an entry in flash
+ *
+ ****************************************************************************/
+
+static int nvs_flash_wrt_entry(FAR struct nvs_fs *fs, uint32_t id,
+ FAR const uint8_t *key, size_t key_size,
+ FAR const void *data, size_t len)
+{
+ int rc;
+ struct nvs_ate entry;
+
+ memset(&entry, fs->erasestate, sizeof(entry));
+ entry.id = id;
+ entry.offset = fs->data_wra & ADDR_OFFS_MASK;
+ entry.len = len;
+ entry.key_len = key_size;
+
+ nvs_ate_crc8_update(&entry);
+
+ /* Let's sew key and data into one, key comes first, then data */
+
+ rc = nvs_flash_data_wrt(fs, key, key_size);
+ if (rc)
+ {
+ ferr("Write key failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = nvs_flash_data_wrt(fs, data, len);
+ if (rc)
+ {
+ ferr("Write value failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = nvs_flash_ate_wrt(fs, &entry);
+ if (rc)
+ {
+ ferr("Write ate failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_recover_last_ate
+ *
+ * Description:
+ * If the closing ate is invalid, its offset cannot be trusted and
+ * the last valid ate of the block should instead try to be recovered
+ * by going through all ate's.
+ *
+ * Addr should point to the faulty closing ate and will be updated to
+ * the last valid ate. If no valid ate is found it will be left untouched.
+ *
+ ****************************************************************************/
+
+static int nvs_recover_last_ate(FAR struct nvs_fs *fs,
+ FAR uint32_t *addr)
+{
+ uint32_t data_end_addr;
+ uint32_t ate_end_addr;
+ struct nvs_ate end_ate;
+ int rc;
+
+ finfo("Recovering last ate from block %" PRIu32 "\n",
+ (*addr >> ADDR_BLOCK_SHIFT));
+
+ *addr -= sizeof(struct nvs_ate);
+ ate_end_addr = *addr;
+ data_end_addr = *addr & ADDR_BLOCK_MASK;
+ while (ate_end_addr > data_end_addr)
+ {
+ rc = nvs_flash_ate_rd(fs, ate_end_addr, &end_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ if (nvs_ate_valid(fs, &end_ate))
+ {
+ /* Found a valid ate, update data_end_addr and *addr */
+
+ data_end_addr &= ADDR_BLOCK_MASK;
+ data_end_addr += end_ate.offset + end_ate.key_len + end_ate.len;
+ *addr = ate_end_addr;
+ }
+
+ ate_end_addr -= sizeof(struct nvs_ate);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_prev_ate
+ *
+ * Description:
+ * Walking through allocation entry list, from newest to oldest entries.
+ * Read ate from addr, modify addr to the previous ate.
+ *
+ ****************************************************************************/
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+ FAR struct nvs_ate *ate)
+{
+ int rc;
+ struct nvs_ate close_ate;
+
+ rc = nvs_flash_ate_rd(fs, *addr, ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ *addr += sizeof(struct nvs_ate);
+ if (((*addr) & ADDR_OFFS_MASK) !=
+ (fs->geo.erasesize - sizeof(struct nvs_ate)))
+ {
+ return 0;
+ }
+
+ /* Last ate in block, do jump to previous block */
+
+ if (((*addr) >> ADDR_BLOCK_SHIFT) == 0)
+ {
+ *addr += ((fs->geo.neraseblocks - 1) << ADDR_BLOCK_SHIFT);
+ }
+ else
+ {
+ *addr -= (1 << ADDR_BLOCK_SHIFT);
+ }
+
+ rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = nvs_ate_cmp_const(&close_ate, fs->erasestate);
+
+ /* At the end of filesystem */
+
+ if (!rc)
+ {
+ *addr = fs->ate_wra;
+ return 0;
+ }
+
+ /* Update the address if the close ate is valid. */
+
+ if (nvs_close_ate_valid(fs, &close_ate))
+ {
+ *addr &= ADDR_BLOCK_MASK;
+ *addr += close_ate.offset;
+ return 0;
+ }
+
+ /* The close_ate was invalid, `lets find out the last valid ate
+ * and point the address to this found ate.
+ *
+ * Remark: if there was absolutely no valid data in the block *addr
+ * is kept at block_end - 2*ate_size, the next read will contain
+ * invalid data and continue with a block jump
+ */
+
+ return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_block_advance
+ ****************************************************************************/
+
+static void nvs_block_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+ *addr += (1 << ADDR_BLOCK_SHIFT);
+ if ((*addr >> ADDR_BLOCK_SHIFT) == fs->geo.neraseblocks)
+ {
+ *addr -= (fs->geo.neraseblocks << ADDR_BLOCK_SHIFT);
+ }
+}
+
+/****************************************************************************
+ * Name: nvs_block_close
+ *
+ * Description:
+ * Allocation entry close (this closes the current block) by writing
+ * offset of last ate to the block end.
+ *
+ ****************************************************************************/
+
+static int nvs_block_close(FAR struct nvs_fs *fs)
+{
+ int rc;
+ struct nvs_ate close_ate;
+
+ memset(&close_ate, fs->erasestate, sizeof(close_ate));
+ close_ate.id = NVS_SPECIAL_ATE_ID;
+ close_ate.len = 0;
+ close_ate.key_len = 0;
+ close_ate.offset =
+ (fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK;
+
+ fs->ate_wra &= ADDR_BLOCK_MASK;
+ fs->ate_wra += fs->geo.erasesize - sizeof(struct nvs_ate);
+
+ nvs_ate_crc8_update(&close_ate);
+
+ rc = nvs_flash_ate_wrt(fs, &close_ate);
+ if (rc < 0)
+ {
+ ferr("Write ate failed, rc=%d\n", rc);
+ }
+
+ nvs_block_advance(fs, &fs->ate_wra);
+
+ fs->data_wra = fs->ate_wra & ADDR_BLOCK_MASK;
+ finfo("block close, data_wra=0x%" PRIx32 "\n", fs->data_wra);
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_add_gc_done_ate
+ ****************************************************************************/
+
+static int nvs_add_gc_done_ate(FAR struct nvs_fs *fs)
+{
+ struct nvs_ate gc_done_ate;
+
+ finfo("Adding gc done ate at %" PRIx32 "\n", fs->ate_wra & ADDR_OFFS_MASK);
+ memset(&gc_done_ate, fs->erasestate, sizeof(gc_done_ate));
+ gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+ gc_done_ate.len = 0;
+ gc_done_ate.key_len = 0;
+ gc_done_ate.offset = fs->data_wra & ADDR_OFFS_MASK;
+ nvs_ate_crc8_update(&gc_done_ate);
+
+ return nvs_flash_ate_wrt(fs, &gc_done_ate);
+}
+
+/****************************************************************************
+ * Name: nvs_expire_ate
+ ****************************************************************************/
+
+static int nvs_expire_ate(FAR struct nvs_fs *fs, uint32_t addr)
+{
+ uint8_t expired = 0;
+
+ return nvs_flash_wrt(fs, addr + offsetof(struct nvs_ate, expired),
+ &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ * Garbage collection: the address ate_wra has been updated to the new
+ * block that has just been started. The data to gc is in the block
+ * after this new block.
+ *
+ ****************************************************************************/
+
+static int nvs_gc(FAR struct nvs_fs *fs)
+{
+ int rc;
+ struct nvs_ate close_ate;
+ struct nvs_ate gc_ate;
+ uint32_t sec_addr;
+ uint32_t gc_addr;
+ uint32_t gc_prev_addr;
+ uint32_t data_addr;
+ uint32_t stop_addr;
+
+ finfo("gc: before gc, ate_wra %" PRIx32 "\n", fs->ate_wra);
+
+ sec_addr = (fs->ate_wra & ADDR_BLOCK_MASK);
+ nvs_block_advance(fs, &sec_addr);
+ gc_addr = sec_addr + fs->geo.erasesize - sizeof(struct nvs_ate);
+
+ finfo("gc: set, sec_addr %" PRIx32 ", gc_addr %" PRIx32 "\n", sec_addr,
+ gc_addr);
+
+ /* If the block is not closed don't do gc */
+
+ rc = nvs_flash_ate_rd(fs, gc_addr, &close_ate);
+ if (rc < 0)
+ {
+ /* Flash error */
+
+ return rc;
+ }
+
+ rc = nvs_ate_cmp_const(&close_ate, fs->erasestate);
+ if (!rc)
+ {
+ goto gc_done;
+ }
+
+ stop_addr = gc_addr - sizeof(struct nvs_ate);
+
+ if (nvs_close_ate_valid(fs, &close_ate))
+ {
+ gc_addr &= ADDR_BLOCK_MASK;
+ gc_addr += close_ate.offset;
+ }
+ else
+ {
+ rc = nvs_recover_last_ate(fs, &gc_addr);
+ if (rc)
+ {
+ return rc;
+ }
+ }
+
+ do
+ {
+ gc_prev_addr = gc_addr;
+ rc = nvs_prev_ate(fs, &gc_addr, &gc_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ if (gc_ate.expired != fs->erasestate)
+ {
+ /* Deleted or old ate, ignore it */
+
+ continue;
+ }
+
+ if (!nvs_ate_valid(fs, &gc_ate))
+ {
+ continue;
+ }
+
+ if (gc_ate.id != NVS_SPECIAL_ATE_ID)
+ {
+ /* Copy needed */
+
+ finfo("Moving %" PRIu32 ", key_len %" PRIu16 ", len %" PRIu16 "\n",
+ gc_ate.id, gc_ate.key_len, gc_ate.len);
+
+ data_addr = gc_prev_addr & ADDR_BLOCK_MASK;
+ data_addr += gc_ate.offset;
+
+ gc_ate.offset = fs->data_wra & ADDR_OFFS_MASK;
+ nvs_ate_crc8_update(&gc_ate);
+
+ rc = nvs_flash_block_move(fs, data_addr,
+ gc_ate.key_len + gc_ate.len);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = nvs_flash_ate_wrt(fs, &gc_ate);
+ if (rc)
+ {
+ return rc;
+ }
+ }
+ }
+ while (gc_prev_addr != stop_addr);
+
+gc_done:
+ rc = nvs_add_gc_done_ate(fs);
+ if (rc)
+ {
+ return rc;
+ }
+
+ /* Erase the gc'ed block */
+
+ rc = nvs_flash_erase_block(fs, sec_addr);
+ if (rc)
+ {
+ return rc;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_startup
+ ****************************************************************************/
+
+static int nvs_startup(FAR struct nvs_fs *fs)
+{
+ int rc;
+ struct nvs_ate last_ate;
+ size_t empty_len;
+ uint32_t wlk_addr;
+ uint32_t second_addr;
+ uint32_t last_addr;
+ struct nvs_ate second_ate;
+
+ /* Initialize addr to 0 for the case fs->geo.neraseblocks == 0. This
+ * should never happen but both
+ * Coverity and GCC believe the contrary.
+ */
+
+ uint32_t addr = 0;
+ uint16_t i;
+ uint16_t closed_blocks = 0;
+
+ fs->ate_wra = 0;
+ fs->data_wra = 0;
+
+ /* Check the number of blocks, it should be at least 2. */
+
+ if (fs->geo.neraseblocks < 2)
+ {
+ ferr("Configuration error - block count\n");
+ return -EINVAL;
+ }
+
+ /* 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).
+ */
+
+ rc = MTD_IOCTL(fs->mtd, MTDIOC_GEOMETRY,
+ (unsigned long)((uintptr_t)&(fs->geo)));
+ if (rc < 0)
+ {
+ ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", rc);
+ return rc;
+ }
+
+ rc = MTD_IOCTL(fs->mtd, MTDIOC_ERASESTATE,
+ (unsigned long)((uintptr_t)&fs->erasestate));
+ if (rc < 0)
+ {
+ ferr("ERROR: MTD ioctl(MTDIOC_ERASESTATE) failed: %d\n", rc);
+ return rc;
+ }
+
+ /* Step through the blocks to find a open block following
+ * a closed block, this is where NVS can write.
+ */
+
+ for (i = 0; i < fs->geo.neraseblocks; i++)
+ {
+ addr = (i << ADDR_BLOCK_SHIFT) +
+ (uint16_t)(fs->geo.erasesize - sizeof(struct nvs_ate));
+ rc = nvs_flash_cmp_const(fs, addr, fs->erasestate,
+ sizeof(struct nvs_ate));
+ fwarn("rc=%d\n", rc);
+ if (rc)
+ {
+ /* Closed block */
+
+ closed_blocks++;
+ nvs_block_advance(fs, &addr);
+ rc = nvs_flash_cmp_const(fs, addr, fs->erasestate,
+ sizeof(struct nvs_ate));
+ if (!rc)
+ {
+ /* Open block */
+
+ break;
+ }
+ }
+
+ fwarn("i=%" PRIu16 ", closed_blocks=%" PRIu16 ", addr=0x%" PRIx32 "\n",
+ i, closed_blocks, addr);
+ }
+
+ /* All blocks are closed, this is not a nvs fs */
+
+ if (closed_blocks == fs->geo.neraseblocks)
+ {
+ return -EDEADLK;
+ }
+
+ if (i == fs->geo.neraseblocks)
+ {
+ /* None of the blocks where closed, in most cases we can set
+ * the address to the first block, except when there are only
+ * two blocks. Then we can only set it to the first block if
+ * the last block contains no ate's. So we check this first
+ */
+
+ rc = nvs_flash_cmp_const(fs, addr - sizeof(struct nvs_ate),
+ fs->erasestate, sizeof(struct nvs_ate));
+ if (!rc)
+ {
+ /* Empty ate */
+
+ nvs_block_advance(fs, &addr);
+ }
+ }
+
+ /* Addr contains address of closing ate in the most recent block,
+ * search for the last valid ate using the recover_last_ate routine
+ */
+
+ rc = nvs_recover_last_ate(fs, &addr);
+ if (rc)
+ {
+ return rc;
+ }
+
+ /* Addr contains address of the last valid ate in the most recent block
+ * search for the first ate containing all cells erased, in the process
+ * also update fs->data_wra.
+ */
+
+ fs->ate_wra = addr;
+ fs->data_wra = addr & ADDR_BLOCK_MASK;
+ finfo("recovered ate ate_wra=0x%" PRIx32 ", data_wra=0x%" PRIx32 "\n",
+ fs->ate_wra, fs->data_wra);
+
+ while (fs->ate_wra >= fs->data_wra)
+ {
+ rc = nvs_flash_ate_rd(fs, fs->ate_wra, &last_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = nvs_ate_cmp_const(&last_ate, fs->erasestate);
+ if (!rc)
+ {
+ /* Found 0xff empty location */
+
+ break;
+ }
+
+ if (nvs_ate_valid(fs, &last_ate))
+ {
+ /* Complete write of ate was performed */
+
+ fs->data_wra = addr & ADDR_BLOCK_MASK;
+ fs->data_wra += last_ate.offset + last_ate.key_len +
+ last_ate.len;
+ finfo("recovered data_wra=0x%" PRIx32 "\n", fs->data_wra);
+ }
+
+ fs->ate_wra -= sizeof(struct nvs_ate);
+ }
+
+ /* If the block after the write block is not empty, gc was interrupted
+ * we might need to restart gc if it has not yet finished. Otherwise
+ * just erase the block.
+ * When gc needs to be restarted, first erase the block otherwise the
+ * data might not fit into the block.
+ */
+
+ addr = fs->ate_wra & ADDR_BLOCK_MASK;
+ nvs_block_advance(fs, &addr);
+ rc = nvs_flash_cmp_const(fs, addr, fs->erasestate, fs->geo.erasesize);
+ if (rc < 0)
+ {
+ return rc;
+ }
+
+ if (rc)
+ {
+ /* The block after fs->ate_wrt is not empty, look for a marker
+ * (gc_done_ate) that indicates that gc was finished.
+ */
+
+ bool gc_done_marker = false;
+ struct nvs_ate gc_done_ate;
+
+ addr = fs->ate_wra + sizeof(struct nvs_ate);
+ while ((addr & ADDR_OFFS_MASK) <
+ (fs->geo.erasesize - sizeof(struct nvs_ate)))
+ {
+ rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ if (nvs_ate_valid(fs, &gc_done_ate) &&
+ (gc_done_ate.id == NVS_SPECIAL_ATE_ID) &&
+ (gc_done_ate.len == 0))
+ {
+ gc_done_marker = true;
+ break;
+ }
+
+ addr += sizeof(struct nvs_ate);
+ }
+
+ if (gc_done_marker)
+ {
+ /* Erase the next block */
+
+ fwarn("GC Done marker found\n");
+ addr = fs->ate_wra & ADDR_BLOCK_MASK;
+ nvs_block_advance(fs, &addr);
+ rc = nvs_flash_erase_block(fs, addr);
+ goto end;
+ }
+
+ fwarn("No GC Done marker found: restarting gc\n");
+ rc = nvs_flash_erase_block(fs, fs->ate_wra);
+ if (rc)
+ {
+ return rc;
+ }
+
+ fs->ate_wra &= ADDR_BLOCK_MASK;
+ fs->ate_wra += (fs->geo.erasesize - 2 * sizeof(struct nvs_ate));
+ fs->data_wra = (fs->ate_wra & ADDR_BLOCK_MASK);
+ finfo("GC when data_wra=0x%" PRIx32 "\n", fs->data_wra);
+ rc = nvs_gc(fs);
+ goto end;
+ }
+
+ /* Possible data write after last ate write, update data_wra */
+
+ while (fs->ate_wra > fs->data_wra)
+ {
+ empty_len = fs->ate_wra - fs->data_wra;
+
+ rc = nvs_flash_cmp_const(fs, fs->data_wra, fs->erasestate, empty_len);
+ if (rc < 0)
+ {
+ return rc;
+ }
+
+ if (!rc)
+ {
+ break;
+ }
+
+ fs->data_wra += NVS_CORRUPT_DATA_SKIP_STEP;
+ finfo("update for powerloss data write, data_wra=0x%" PRIx32 "\n",
+ fs->data_wra);
+ }
+
+ /* If the ate_wra is pointing to the first ate write location in a
+ * block and data_wra is not 0, erase the block as it contains no
+ * valid data (this also avoids closing a block without any data).
+ */
+
+ if (((fs->ate_wra + 2 * sizeof(struct nvs_ate)) == fs->geo.erasesize) &&
+ (fs->data_wra != (fs->ate_wra & ADDR_BLOCK_MASK)))
+ {
+ rc = nvs_flash_erase_block(fs, fs->ate_wra);
+ if (rc)
+ {
+ return rc;
+ }
+
+ fs->data_wra = fs->ate_wra & ADDR_BLOCK_MASK;
+ finfo("erase due to no data, data_wra=0x%" PRIx32 "\n",
+ fs->data_wra);
+ }
+
+ /* Check if there exists an old entry with the same id and key
+ * as the newest entry.
+ * If so, power loss occured before writing the old entry id as expired.
+ * We need to set old entry expired.
+ */
+
+ wlk_addr = fs->ate_wra;
+ while (1)
+ {
+ last_addr = wlk_addr;
+ rc = nvs_prev_ate(fs, &wlk_addr, &last_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ /* Skip last one */
+
+ if (wlk_addr == fs->ate_wra)
+ {
+ break;
+ }
+
+ if (nvs_ate_valid(fs, &last_ate)
+ && (last_ate.id != NVS_SPECIAL_ATE_ID))
+ {
+ finfo("ate found at 0x%" PRIx32 ", id %" PRIu32 ", "
+ "key_len %" PRIu16 ", offset %" PRIu16 "\n",
+ last_addr, last_ate.id, last_ate.key_len, last_ate.offset);
+ while (1)
+ {
+ second_addr = wlk_addr;
+ rc = nvs_prev_ate(fs, &wlk_addr, &second_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ if (nvs_ate_valid(fs, &second_ate)
+ && second_ate.id == last_ate.id
+ && second_ate.expired == fs->erasestate)
+ {
+ finfo("same id at 0x%" PRIx32 ", key_len %" PRIu16 ", "
+ "offset %" PRIu16 "\n",
+ second_addr, second_ate.key_len, second_ate.offset);
+ if ((second_ate.key_len == last_ate.key_len) &&
+ !nvs_flash_direct_cmp(fs,
+ (last_addr & ADDR_BLOCK_MASK) +
+ last_ate.offset,
+ (second_addr & ADDR_BLOCK_MASK) +
+ second_ate.offset,
+ last_ate.key_len))
+ {
+ finfo("old ate found at 0x%" PRIx32 "\n", second_addr);
+ rc = nvs_expire_ate(fs, second_addr);
+ if (rc < 0)
+ {
+ ferr("expire ate failed, addr %" PRIx32 "\n",
+ second_addr);
+ return rc;
+ }
+
+ goto end;
+ }
+ else
+ {
+ fwarn("hash conflict\n");
+ }
+ }
+
+ if (wlk_addr == fs->ate_wra)
+ {
+ goto end;
+ }
+ }
+ }
+ }
+
+end:
+ /* If the block is empty, add a gc done ate to avoid having insufficient
+ * space when doing gc.
+ */
+
+ if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+ (fs->geo.erasesize - 2 * sizeof(struct nvs_ate))))
+ {
+ rc = nvs_add_gc_done_ate(fs);
+ }
+
+ finfo("%" PRIu32 " Eraseblocks of %" PRIu32 " bytes\n",
+ fs->geo.neraseblocks, fs->geo.erasesize);
+ finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+ (fs->ate_wra >> ADDR_BLOCK_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+ finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+ (fs->data_wra >> ADDR_BLOCK_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+ return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_read_entry
+ *
+ * Description:
+ * Read An entry from the file system. But expired ones will return
+ * -ENOENT.
+ *
+ * Input Parameters:
+ * fs - Pointer to file system.
+ * key - Key of the entry to be read.
+ * key_size - Size of key.
+ * data - Pointer to data buffer.
+ * len - Number of bytes to be read.
+ * ate_addr - The addr of found ate.
+ *
+ * Returned Value:
+ * Number of bytes read. On success, it will be equal to the number
+ * of bytes requested to be read. When the return value is larger than the
+ * number of bytes requested to read this indicates not all bytes were
+ * read, and more data is available. On error returns -ERRNO code.
+ *
+ ****************************************************************************/
+
+static ssize_t nvs_read_entry(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+ size_t key_size, FAR void *data, size_t len,
+ FAR uint32_t *ate_addr)
+{
+ int rc;
+ uint32_t wlk_addr;
+ uint32_t rd_addr;
+ uint32_t hist_addr;
+ struct nvs_ate wlk_ate;
+ uint32_t hash_id;
+
+ hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+ wlk_addr = fs->ate_wra;
+ rd_addr = wlk_addr;
+
+ do
+ {
+ rd_addr = wlk_addr;
+ hist_addr = wlk_addr;
+ rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate);
+ if (rc)
+ {
+ ferr("Walk to previous ate failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ if ((wlk_ate.id == hash_id) && (nvs_ate_valid(fs, &wlk_ate)))
+ {
+ if ((wlk_ate.key_len == key_size)
+ && (!nvs_flash_block_cmp(fs,
+ (rd_addr & ADDR_BLOCK_MASK) + wlk_ate.offset, key, key_size)))
+ {
+ /* It is old or deleted, return -ENOENT */
+
+ if (wlk_ate.expired != fs->erasestate)
+ {
+ return -ENOENT;
+ }
+ break;
+ }
+ else
+ {
+ fwarn("hash conflict\n");
+ }
+ }
+
+ if (wlk_addr == fs->ate_wra)
+ {
+ return -ENOENT;
+ }
+ }
+ while (true);
+
+ if (data)
+ {
+ rd_addr &= ADDR_BLOCK_MASK;
+ rd_addr += wlk_ate.offset + wlk_ate.key_len;
+ rc = nvs_flash_rd(fs, rd_addr, data,
+ MIN(len, wlk_ate.len));
+ if (rc)
+ {
+ ferr("Data read failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (ate_addr)
+ {
+ *ate_addr = hist_addr;
+ }
+
+ return wlk_ate.len;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ * Write an entry to the file system.
+ *
+ * Input Parameters:
+ * fs - Pointer to file system.
+ * pdata - Pointer to data buffer.
+ *
+ * Returned Value:
+ * Number of bytes written. On success, it will be equal to the
+ * number of bytes requested to be written. On error returns -ERRNO code.
+ *
+ ****************************************************************************/
+
+static ssize_t nvs_write(FAR struct nvs_fs *fs,
+ FAR struct config_data_s *pdata)
+{
+ int rc;
+ int gc_count;
+ size_t data_size;
+ size_t key_size;
+ struct nvs_ate wlk_ate;
+ uint32_t wlk_addr;
+ uint32_t rd_addr;
+ uint32_t hist_addr;
+ uint16_t required_space = 0;
+ bool prev_found = false;
+ uint32_t hash_id;
+ uint16_t block_to_write_befor_gc;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+ FAR const uint8_t *key;
+#else
+ uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+ key = (FAR const uint8_t *)pdata->name;
+ key_size = strlen(pdata->name) + 1;
+#else
+ memcpy(key, &pdata->id, sizeof(pdata->id));
+ memcpy(key + sizeof(pdata->id), &pdata->instance, sizeof(pdata->instance));
+ key_size = sizeof(pdata->id) + sizeof(pdata->instance);
+#endif
+
+ /* Data now contains input data and input key, input key first. */
+
+ data_size = key_size + pdata->len;
+
+ /* The maximum data size is block size - 3 ate
+ * where: 1 ate for data, 1 ate for block close, 1 ate for gc done.
+ */
+
+ finfo("key_size=%zu, len=%zu\n", key_size, pdata->len);
+
+ if ((data_size > (fs->geo.erasesize - 3 * sizeof(struct nvs_ate))) ||
+ ((pdata->len > 0) && (pdata->configdata == NULL)))
+ {
+ return -EINVAL;
+ }
+
+ /* Calc hash id of key. */
+
+ hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+ /* Find latest entry with same id. */
+
+ wlk_addr = fs->ate_wra;
+
+ while (1)
+ {
+ rd_addr = wlk_addr;
+ hist_addr = wlk_addr;
+ rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ if ((wlk_ate.id == hash_id) && (nvs_ate_valid(fs, &wlk_ate)))
+ {
+ if ((wlk_ate.key_len == key_size)
+ && !nvs_flash_block_cmp(fs,
+ (rd_addr & ADDR_BLOCK_MASK) +
+ wlk_ate.offset, key, key_size))
+ {
+ prev_found = true;
+ break;
+ }
+ else
+ {
+ fwarn("hash conflict\n");
+ }
+ }
+
+ if (wlk_addr == fs->ate_wra)
+ {
+ break;
+ }
+ }
+
+ if (prev_found)
+ {
+ finfo("Previous found\n");
+
+ /* Previous entry found. */
+
+ rd_addr &= ADDR_BLOCK_MASK;
+
+ if (pdata->len == 0)
+ {
+ /* If prev ate is expired, it is deleted. */
+
+ if (wlk_ate.expired != fs->erasestate)
+ {
+ /* Skip delete entry as it is already the
+ * last one.
+ */
+
+ return 0;
+ }
+ else
+ {
+ rc = nvs_expire_ate(fs, hist_addr);
+ if (rc < 0)
+ {
+ ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+ return rc;
+ }
+
+ /* Delete now requires no extra space, so skip write and gc. */
+
+ finfo("nvs_delete success\n");
+ return 0;
+ }
+ }
+ else if (pdata->len == wlk_ate.len &&
+ wlk_ate.expired == fs->erasestate)
+ {
+ /* Do not try to compare if lengths are not equal
+ * or prev one is deleted.
+ * Compare the data and if equal return 0.
+ */
+
+ rd_addr += wlk_ate.offset + wlk_ate.key_len;
+ rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata,
+ pdata->len);
+ if (rc <= 0)
+ {
+ return rc;
+ }
+ }
+ }
+ else
+ {
+ /* Skip delete entry for non-existing entry. */
+
+ if (pdata->len == 0)
+ {
+ return 0;
+ }
+ }
+
+ /* Leave space for gc_done ate */
+
+ required_space = data_size + sizeof(struct nvs_ate);
+ gc_count = 0;
+ block_to_write_befor_gc = fs->ate_wra >> ADDR_BLOCK_SHIFT;
+ while (1)
+ {
+ if (gc_count == fs->geo.neraseblocks)
+ {
+ /* Gc'ed all blocks, no extra space will be created
+ * by extra gc.
+ */
+
+ return -ENOSPC;
+ }
+
+ if (fs->ate_wra >= fs->data_wra + required_space)
+ {
+ /* Nvs is changed after gc, we will look for the old ate.
+ */
+
+ if (prev_found && wlk_ate.expired == fs->erasestate)
+ {
+ finfo("prev entry exists, search for it\n");
+
+ /* If gc touched second latest ate, search for it again */
+
+ if (gc_count >= fs->geo.neraseblocks - 1 -
+ (block_to_write_befor_gc + fs->geo.neraseblocks -
+ (hist_addr >> ADDR_BLOCK_SHIFT))
+ % fs->geo.neraseblocks)
+ {
+ rc = nvs_read_entry(fs, key, key_size, NULL, 0,
+ &hist_addr);
+ finfo("relocate for prev entry, %" PRIx32 ", "
+ "rc %d\n",
+ hist_addr, rc);
+ if (rc < 0)
+ {
+ ferr("read prev entry failed\n");
+ return rc;
+ }
+ }
+ }
+
+ finfo("Write entry, ate_wra=0x%" PRIx32 ", "
+ "data_wra=0x%" PRIx32 "\n",
+ fs->ate_wra, fs->data_wra);
+ rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+ pdata->configdata, pdata->len);
+ if (rc)
+ {
+ fwarn("Write entry failed\n");
+ return rc;
+ }
+
+ finfo("Write entry success\n");
+
+ /* Expiring the old ate if exists.
+ * After this operation, only the latest ate is valid.
+ * Expire the old one only if it is not deleted before(Or it is
+ * already expired)
+ */
+
+ if (prev_found && wlk_ate.expired == fs->erasestate)
+ {
+ rc = nvs_expire_ate(fs, hist_addr);
+ finfo("expir prev entry, %" PRIx32 ", rc %d\n",
+ hist_addr, rc);
+ if (rc < 0)
+ {
+ ferr("expire ate failed, addr %" PRIx32 "\n",
+ hist_addr);
+ return rc;
+ }
+ }
+
+ break;
+ }
+
+ rc = nvs_block_close(fs);
+ if (rc)
+ {
+ return rc;
+ }
+
+ rc = nvs_gc(fs);
+ if (rc)
+ {
+ return rc;
+ }
+
+ gc_count++;
+ finfo("Gc count=%d\n", gc_count);
+ }
+
+ finfo("nvs_write success\n");
+ return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_delete
+ *
+ * Description:
+ * Delete an entry from the file system.
+ *
+ * Input Parameters:
+ * fs - Pointer to file system.
+ * pdata - Pointer to data buffer.
+ *
+ * Returned Value:
+ * 0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_delete(FAR struct nvs_fs *fs, FAR struct config_data_s *pdata)
+{
+ /* If user wants to operate /dev/config directly.
+ * Set len=0 to trigger delete, so that user doesn't need to do that.
+ */
+
+ pdata->len = 0;
+ return nvs_write(fs, pdata);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ * Read an entry from the file system.
+ *
+ * Input Parameters:
+ * fs - Pointer to file system.
+ * pdata - Pointer to data buffer.
+ *
+ * Returned Value:
+ * 0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static ssize_t nvs_read(FAR struct nvs_fs *fs,
+ FAR struct config_data_s *pdata)
+{
+ size_t key_size;
+ ssize_t ret;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+ FAR const uint8_t *key;
+#else
+ uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+ if (pdata == NULL || pdata->len == 0)
+ {
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+ key = (FAR const uint8_t *)pdata->name;
+ key_size = strlen(pdata->name) + 1;
+#else
+ memcpy(key, &pdata->id, sizeof(pdata->id));
+ memcpy(key + sizeof(pdata->id), &pdata->instance, sizeof(pdata->instance));
+ key_size = sizeof(pdata->id) + sizeof(pdata->instance);
+#endif
+
+ ret = nvs_read_entry(fs, key, key_size, pdata->configdata, pdata->len,
+ NULL);
+ if (ret > 0)
+ {
+ pdata->len = ret;
+ return 0;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ * Get the next KV in database.
+ *
+ * Input Parameters:
+ * fs - Pointer to file system.
+ * pdata - Pointer to data buffer.
+ * first - true if we are reading the first KV.
+ *
+ * Returned Value:
+ * 0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_next(FAR struct nvs_fs *fs,
+ FAR struct config_data_s *pdata, bool first)
+{
+ int rc;
+ struct nvs_ate step_ate;
+ uint32_t rd_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+ FAR uint8_t *key = (FAR uint8_t *)pdata->name;
+#else
+ uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+ if (first)
+ {
+ fs->step_addr = fs->ate_wra;
+ }
+ else
+ {
+ if (fs->step_addr == fs->ate_wra)
+ {
+ return -ENOENT;
+ }
+ }
+
+ do
+ {
+ rd_addr = fs->step_addr;
+ rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+ if (rc)
+ {
+ return rc;
+ }
+
+ if (nvs_ate_valid(fs, &step_ate)
+ && step_ate.id != NVS_SPECIAL_ATE_ID
+ && step_ate.expired == fs->erasestate)
+ {
+ break;
+ }
+
+ if (fs->step_addr == fs->ate_wra)
+ {
+ return -ENOENT;
+ }
+ }
+ while (true);
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+ rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+ key, MIN(step_ate.key_len, CONFIG_MTD_CONFIG_NAME_LEN));
+ if (rc)
+ {
+ ferr("Key read failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ key[CONFIG_MTD_CONFIG_NAME_LEN - 1] = 0;
+#else
+ rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+ key, MIN(sizeof(key), step_ate.key_len));
+ if (rc)
+ {
+ ferr("Key read failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ memcpy(pdata->id, key, sizeof(pdata->id));
+ memcpy(pdata->instance, key + sizeof(pdata->id), sizeof(pdata->instance));
+#endif
+
+ rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset +
+ step_ate.key_len, pdata->configdata,
+ MIN(pdata->len, step_ate.len));
+ if (rc)
+ {
+ ferr("Value read failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ pdata->len = MIN(pdata->len, step_ate.len);
+ return OK;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_open
+ ****************************************************************************/
+
+static int mtdconfig_open(FAR struct file *filep)
+{
+ return OK;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_close
+ ****************************************************************************/
+
+static int mtdconfig_close(FAR struct file *filep)
+{
+ return OK;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_read
+ ****************************************************************************/
+
+static ssize_t mtdconfig_read(FAR struct file *filep, FAR char *buffer,
+ size_t buflen)
+{
+ return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_ioctl
+ ****************************************************************************/
+
+static int mtdconfig_ioctl(FAR struct file *filep, int cmd,
+ unsigned long arg)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct nvs_fs *fs = (FAR struct nvs_fs *)inode->i_private;
+ FAR struct config_data_s *pdata = (FAR struct config_data_s *)arg;
+ int ret = -ENOTTY;
+
+ ret = nxmutex_lock(&fs->nvs_lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ switch (cmd)
+ {
+ case CFGDIOC_GETCONFIG:
+
+ /* Read a nvs item. */
+
+ ret = nvs_read(fs, pdata);
+ break;
+
+ case CFGDIOC_SETCONFIG:
+
+ /* Write a nvs item. */
+
+ ret = nvs_write(fs, pdata);
+ break;
+
+ case CFGDIOC_DELCONFIG:
+
+ /* Delete a nvs item. */
+
+ ret = nvs_delete(fs, pdata);
+ break;
+
+ case CFGDIOC_FIRSTCONFIG:
+
+ /* Get the first item. */
+
+ ret = nvs_next(fs, pdata, true);
+ break;
+
+ case CFGDIOC_NEXTCONFIG:
+
+ /* Get the next item. */
+
+ ret = nvs_next(fs, pdata, false);
+ break;
+
+ case MTDIOC_BULKERASE:
+
+ /* Call the MTD's ioctl for this. */
+
+ ret = MTD_IOCTL(fs->mtd, cmd, arg);
+ if (ret >= 0)
+ {
+ ret = nvs_startup(fs);
+ }
+
+ break;
+ }
+
+ nxmutex_unlock(&fs->nvs_lock);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_poll
+ ****************************************************************************/
+
+static int mtdconfig_poll(FAR struct file *filep, FAR struct pollfd *fds,
+ bool setup)
+{
+ if (setup)
+ {
+ fds->revents |= fds->events & (POLLIN | POLLOUT);
+ if (fds->revents != 0)
+ {
+ nxsem_post(fds->sem);
+ }
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: mtdconfig_register
+ *
+ * Description:
+ * Register a /dev/config device backed by an fail-safe NVS.
+ *
+ ****************************************************************************/
+
+int mtdconfig_register(FAR struct mtd_dev_s *mtd)
+{
+ int ret;
+ FAR struct nvs_fs *fs;
+
+ fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+ if (fs == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ /* Initialize the mtdnvs device structure */
+
+ fs->mtd = mtd;
+ ret = nxmutex_init(&fs->nvs_lock);
+ if (ret < 0)
+ {
+ ferr("ERROR: nxmutex_init failed: %d\n", ret);
+ goto errout;
+ }
+
+ ret = nvs_startup(fs);
+ if (ret < 0)
+ {
+ ferr("ERROR: nvs_init failed: %d\n", ret);
+ goto mutex_err;
+ }
+
+ ret = register_driver("/dev/config", &g_mtdnvs_fops, 0666, fs);
+ if (ret < 0)
+ {
+ ferr("ERROR: register mtd config failed: %d\n", ret);
+ goto mutex_err;
+ }
+
+ return ret;
+
+mutex_err:
+ nxmutex_destroy(&fs->nvs_lock);
+
+errout:
+ kmm_free(fs);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_unregister
+ *
+ * Description:
+ * Unregister a /dev/config device backed by an fail-safe NVS.
+ *
+ ****************************************************************************/
+
+int mtdconfig_unregister(void)
+{
+ int ret;
+ struct file file;
+ FAR struct inode *inode;
+ FAR struct nvs_fs *fs;
+
+ ret = file_open(&file, "/dev/config", 0);
+ if (ret < 0)
+ {
+ ferr("ERROR: open /dev/config failed: %d\n", ret);
+ return ret;
+ }
+
+ inode = file.f_inode;
+ fs = (FAR struct nvs_fs *)inode->i_private;
+ nxmutex_destroy(&fs->nvs_lock);
+ kmm_free(fs);
+ file_close(&file);
+ unregister_driver("/dev/config");
+
+ return OK;
+}
+
diff --git a/include/nuttx/mtd/configdata.h b/include/nuttx/mtd/configdata.h
index aa6967dd7e..af76333977 100644
--- a/include/nuttx/mtd/configdata.h
+++ b/include/nuttx/mtd/configdata.h
@@ -120,6 +120,21 @@ extern "C"
struct mtd_dev_s;
int mtdconfig_register(FAR struct mtd_dev_s *mtd);
+/****************************************************************************
+ * Name: mtdconfig_unregister
+ *
+ * Description:
+ * This function unregisters /dev/config device.
+ *
+ * Input Parameters:
+ *
+ * Returned Value:
+ * Zero on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int mtdconfig_unregister(void);
+
#undef EXTERN
#ifdef __cplusplus
}