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 #include #include +#include #include #ifdef CONFIG_BCH #include @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * 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 }