You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by GitBox <gi...@apache.org> on 2022/08/10 14:42:54 UTC

[GitHub] [incubator-nuttx] XinStellaris opened a new pull request, #6829: drivers/mtd:init commit of power-loss resilient cfg

XinStellaris opened a new pull request, #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829

   Signed-off-by: 田昕 <ti...@xiaomi.com>
   
   ## Summary
   MTD non-volatile storage was originally implemented in Zephyr by Laczen. We made several modification to the original design.
   The main purpose of those modification was:
   1. support C-string key in nvs API(Original design only support uint16_t as key)
   2. Meanwhile achieve better performance by limiting flash read times(Theoratically
   better than Zephyr subsys/settings, which is based on original NVS). 
   
   This is the initial commit, I have tested its set/unset/get/list/erase with https://github.com/apache/incubator-nuttx-apps/tree/master/system/cfgdata, the result shows no obvious bug.
   
   More tests on power-loss behavior are needed, and I plan to add those test cases into apps/testing in the next patch  
   
   ## Impact
   config data only.
   
   ## Testing
   set/unset/get/list/erase tested on ESP32C3.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1211747978

   The major difference is power failure safe, it's very hard to support this feature on mtdconfig without breaking the binary combability.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960160114


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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 %lx, ate_size %d\n", fs->ate_wra,
+    sizeof(struct nvs_ate));
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->geo.erasesize - sizeof(struct nvs_ate);
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);

Review Comment:
   please compile you code in sim and ensure no warning happen.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966573472


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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;
+
+  expired = (uint8_t)~CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  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;
+    }
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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),
+                               erase_value, 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, erase_value);
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              return -ESPIPE;
+            }
+        }
+
+      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, erase_value, 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, erase_value, 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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 == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);

Review Comment:
   Yes, it will. But it is necessary.
   As a matter of fact, I am thinking removing the cache.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971467192


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;

Review Comment:
   I understand, that is why I suggest you change the loop to:
   ```
   while (1)
      {
        ...
        if (cnt_his++ == cnt)
         {
           break;
         }
       ...
       if (wlk_addr == fs->ate_wra)
         {
           return -ENOENT;
         }
      }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957948863


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF

Review Comment:
   Ok



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957937126


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&

Review Comment:
   because 0xffffffff can also be valid.
   I will define a macro for 0xffffffff



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960379975


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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 %lx, ate_size %d\n", fs->ate_wra,
+    sizeof(struct nvs_ate));
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->geo.erasesize - sizeof(struct nvs_ate);
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - sizeof(struct nvs_ate);
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)

Review Comment:
   Yes, it is not always 0xff. Any value other than 0xff means the ate is an expired one.
   This is to prevent a power loss when we are expiring an ate, at such case, only parts of bits are written. But we can tell it is  expired if it is not 0xff.
   Perhaps CONFIG_MTD_CONFIG_ERASEDVALUE is better. that is my intention.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945041497


##########
include/nuttx/mtd/configdata.h:
##########
@@ -120,6 +120,27 @@ extern "C"
 struct mtd_dev_s;
 int mtdconfig_register(FAR struct mtd_dev_s *mtd);
 
+/****************************************************************************
+ * Name: mtdnvs_register
+ *
+ * Description:
+ *   This function binds an instance of an MTD device to the /dev/config
+ *   device.
+ *
+ *   When this function is called, the MTD device pass in should already
+ *   be initialized appropriately to access the physical device or partition.
+ *
+ * Input Parameters:
+ *   mtd - Pointer to the MTD device to bind with the /dev/config device
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure.
+
+ *
+ ****************************************************************************/
+
+int mtdnvs_register(FAR struct mtd_dev_s *mtd);

Review Comment:
   let's use the same register function mtdconfig_register?



##########
boards/risc-v/esp32c3/esp32c3-devkit/Kconfig:
##########
@@ -109,6 +109,10 @@ choice ESP32C3_SPIFLASH_FS
 		bool "LittleFS"
 		select FS_LITTLEFS
 
+	config ESP32C3_SPIFLASH_NVCFGDATA

Review Comment:
   ```suggestion
   	config ESP32C3_SPIFLASH_MTD_CONFIG
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2244 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.c

Review Comment:
   mtd_config_fs.c



##########
drivers/mtd/Make.defs:
##########
@@ -29,6 +29,10 @@ ifeq ($(CONFIG_MTD),y)
 
 CSRCS += ftl.c mtd_config.c
 
+ifeq ($(CONFIG_MTD_NVS),y)
+CSRCS += mtd_nvs.c
+endif

Review Comment:
   ifeq ($(CONFIG_MTD_NVS),y)
   CSRCS += mtd_nvs.c
   else if($(CONFIG_MTD_CONFIG),y)
   CSRCS += mtd_config.c
   endif



##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,15 @@ config MTD_CONFIG_NAME_LEN
 	---help---
 		Sets the maximum length of config item names.
 
+config MTD_NVS
+	bool "Enable Dev Non-volatile storage (MTD based) device"

Review Comment:
   config MTD_CONFIG_FAIL_SAFE
   	bool "Enable Fail Safe"
   ---help---
   		Enable the new storage layout to support the resilient to power loss.



##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,15 @@ config MTD_CONFIG_NAME_LEN
 	---help---
 		Sets the maximum length of config item names.
 
+config MTD_NVS
+	bool "Enable Dev Non-volatile storage (MTD based) device"
+	default n
+	depends on MTD_CONFIG_NAMED

Review Comment:
   let's support no named case too



##########
drivers/mtd/mtd_nvs.h:
##########
@@ -0,0 +1,102 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.h

Review Comment:
   could merge mtd_nvs.h into mtd_nvs.c, since all info is the internal detail which doesn't need share with other party.



##########
drivers/mtd/mtd_nvs.h:
##########
@@ -0,0 +1,102 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __DRIVERS_MTD_MTD_NVS_H
+#define __DRIVERS_MTD_MTD_NVS_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <nuttx/compiler.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* MASKS AND SHIFT FOR ADDRESSES
+ * an address in nvs is an uint32_t where:
+ *   high 2 bytes represent the sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK           0xFFFF0000
+#define ADDR_SECT_SHIFT          16
+#define ADDR_OFFS_MASK           0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE       1
+
+#define NVS_BLOCK_SIZE           32
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param offset File system offset in flash
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct   mtd_dev_s *mtd;  /* mtd device */

Review Comment:
   ```suggestion
     FAR struct mtd_dev_s *mtd;  /* mtd device */
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957061981


##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,28 @@ 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
+	---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.
+
+config MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+	bool "Enable Fail Safe MTD Config lookup cache"
+	default n
+	depends on MTD_CONFIG_FAIL_SAFE
+	---help---
+		Enable ram buffer to speedup lookup process.
+
+config MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE
+	int "Fail Safe MTD Config lookup cache size"
+	default 8

Review Comment:
   okay



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960360686


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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))))

Review Comment:
   No, offset cannot equal that, it is illegal and therefore invalid.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962445845


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)

Review Comment:
   nvs_recover_last_ate is for recovering last ate, but it cannot recover from power-loss.
   logic after nvs_recover_last_ate intends to recover power-loss. 
   The while here is for one case: if power-loss happened when writing ate. We need to skip the dirty flash and update ate_wra



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962904408


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != NVS_SPECIAL_ATE_ID)

Review Comment:
   it is possible, gc ate(with id == NVS_SPECIAL_ATE_ID) can be anywhere in a block. 
   Close ate must be at the end of block. But gc ate is not.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971463342


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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;

Review Comment:
   we have to avoid certain value, like 0 or 0xfffffff. 0xffffffff is special id for close ate and gc_done ate. 0 is undefined and may be used in later version.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971492836


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* If gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->geo.neraseblocks - 1 -

Review Comment:
   But, it shouldn't have more than one duplicated entry?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1213161938

   I will review in the weekend.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957937919


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF

Review Comment:
   That is not necessary, NVS_LOOKUP_CACHE_NO_ADDR is a special value to indicate no addr is stored in the cache. It is not inherently related to flash erased value



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r959932678


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&

Review Comment:
   Ok.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957948586


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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 */

Review Comment:
   Ok



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962432195


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)

Review Comment:
   Okay, I will remove it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962679807


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;

Review Comment:
   We shouldn't. lookup cache always keeps record of newer ate.
   And newer ate is always in front of older ones.
   If wlk_addr is invalid, then it means no ate has related id. And we can safely return



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971451059


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;

Review Comment:
   Yes, will do



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] pkarashchenko commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954876230


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  FAR struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      fs->sector_count = geo.neraseblocks;
+      fs->sector_size  = geo.erasesize;
+      fs->page_size    = geo.blocksize;
+
+      ret = nvs_init(fs);
+
+      if (ret < 0)
+        {
+          ferr("ERROR: nvs_init failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      register_driver("/dev/config", &mtdnvs_fops, 0666, fs);
+    }
+
+errout:
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_unregister
+ *
+ * Description:
+ *   Unregister a /dev/config device backed by an fail-safe NVS.
+ *
+ ****************************************************************************/
+
+int mtdconfig_unregister(void)
+{
+  int ret = -ENOMEM;
+  FAR struct file mtdnvs_file;

Review Comment:
   ```suggestion
     struct file mtdnvs_file;
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] pkarashchenko commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954877344


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  FAR struct mtd_geometry_s geo;      /* Device geometry */

Review Comment:
   ```suggestion
     struct mtd_geometry_s geo;      /* Device geometry */
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957102375


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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 */

Review Comment:
   I think it has an intended usage in Zephyr NVS(maybe for putting data across multiple blocks?). So I'd rather leave it here. If original NVS later decides to make use of it, we won't need to modify our struct.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r959076767


##########
boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig:
##########
@@ -0,0 +1,50 @@
+#
+# 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_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

Review Comment:
   if enable , then:
     Normalize esp32c3-devkit/nvcfgdata
   51d50
   < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE=y



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962453494


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,

Review Comment:
   They don't fit in, id+instance requires 48bit. but ate id has 32bit. Is it necessary to change our structure and logic to support id/instance ?
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962425400


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   block written but not full is open



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962432513


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);

Review Comment:
   since nvs_read can access the same data through pdata->len, it's strange to pass it as an argument again.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962529136


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);

Review Comment:
   I mean nvs_init is very simple, we can merge the code to nvs_startup and remove nvs_init



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966590071


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   The traverse will not go through all blocks , only from current block to oldest block.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966590292


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)

Review Comment:
   delete cache 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966597260


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)))

Review Comment:
   There are some logic that needs to differentiate valid and expired.
   I will make sure all necessary checks are valid



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971497826


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* If gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->geo.neraseblocks - 1 -

Review Comment:
   there are more than one duplicated entries, when we are update an existing KV.
   It happens after we finished writing the new KV, and right before we expired the old KV.
   At that time, the old KV may be moved to someplaces else due to gc.
   
   And We expire the old one only after we finished writing the new KV. Expire old kv must be after writiing the new KV, so that if power-loss happens when we haven't finished writing the new KV, the old kv is still valid.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945405336


##########
include/nuttx/mtd/configdata.h:
##########
@@ -120,6 +120,27 @@ extern "C"
 struct mtd_dev_s;
 int mtdconfig_register(FAR struct mtd_dev_s *mtd);
 
+/****************************************************************************
+ * Name: mtdnvs_register
+ *
+ * Description:
+ *   This function binds an instance of an MTD device to the /dev/config
+ *   device.
+ *
+ *   When this function is called, the MTD device pass in should already
+ *   be initialized appropriately to access the physical device or partition.
+ *
+ * Input Parameters:
+ *   mtd - Pointer to the MTD device to bind with the /dev/config device
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure.
+
+ *
+ ****************************************************************************/
+
+int mtdnvs_register(FAR struct mtd_dev_s *mtd);

Review Comment:
   Isn't it confusing to use the same function name? After all, nvs is a different method. I don't see the need to make them look the same.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945405946


##########
include/nuttx/mtd/configdata.h:
##########
@@ -120,6 +120,27 @@ extern "C"
 struct mtd_dev_s;
 int mtdconfig_register(FAR struct mtd_dev_s *mtd);
 
+/****************************************************************************
+ * Name: mtdnvs_register
+ *
+ * Description:
+ *   This function binds an instance of an MTD device to the /dev/config
+ *   device.
+ *
+ *   When this function is called, the MTD device pass in should already
+ *   be initialized appropriately to access the physical device or partition.
+ *
+ * Input Parameters:
+ *   mtd - Pointer to the MTD device to bind with the /dev/config device
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure.
+
+ *
+ ****************************************************************************/
+
+int mtdnvs_register(FAR struct mtd_dev_s *mtd);

Review Comment:
   From the architecture perspective, The NVS is just a different backend.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960372923


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   NVS always make sure that the unused(or newest) block is not closed, and other blocks must be closed(which means they are written and full). So if we found the first not-closed block during backward-traverse,  then that block means we have traversed the end 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1211103608

   > In general I'm a bit lost in architecture here. The MTD layer is designed to provide unified "continuous" access across the underlying physical layer, so I expect that all alignment or any other hardware related stuff is hidden "under" MTD device, but here I observe an opposite when the MTD caller handle alignment and other stuff. I mean that if MTD is implemented on top of flash that is what I expect, but not implementing a flash on top of MTD what I see here. I will submit a negative feedback until all design points are not clarified
   
   Basically, MTD like a block device(but need erase before write, wear leveling and ECC), which is hard to use from user application. To fix this problem, we normally have two approach:
   
   1. Build a complex filesystem on top of MTD to support a tree like structure(e.g. spiffs, smartfs and littlefs).
   2. Build a simple key value storage on top of MTD
   
   NVS(and cfgdata) is the second approach. @pkarashchenko do you get the idea?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966582021


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))

Review Comment:
   delete cache



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966597885


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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;
+
+  expired = (uint8_t)~CONFIG_MTD_CONFIG_ERASEDVALUE;

Review Comment:
   Simple id will need hash check logic  in startup/list, because of crc8 conflicts.
    I don't want to introduce more computing cost.
   I will make sure all necessary checks are there.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962431088


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ate_wra = 0;
+        fs->data_wra = 0;

Review Comment:
   it should be in nvs_init. I will change it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962441180


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);

Review Comment:
   That means we need to add a CFGDIOC_LOADCONFIG?  is this necessary?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962706360


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)

Review Comment:
   if that happens, nvs_startup will do the recovering.
   nvs_startup will first read the last written ate, then try to find another ate with the same key and un-expired.
   If such an ate is found, then it is the old ate. All we need to do is expire it.
   
   Check the code  after following comment:
     /* 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.
      */



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r969308374


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2141 @@
+/****************************************************************************
+ * 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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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);
+  addr &= ADDR_BLOCK_MASK;

Review Comment:
   remove



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2141 @@
+/****************************************************************************
+ * 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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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);
+  addr &= ADDR_BLOCK_MASK;
+
+  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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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 */

Review Comment:
   ```suggestion
     /* Check the number of blocks, it should be at least 2 */
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2141 @@
+/****************************************************************************
+ * 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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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);
+  addr &= ADDR_BLOCK_MASK;
+
+  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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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;
+    }
+
+  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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              return -ESPIPE;
+            }
+        }
+
+      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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, pdata->len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * 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 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;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found

Review Comment:
   make all comment start with upper case



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2141 @@
+/****************************************************************************
+ * 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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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);
+  addr &= ADDR_BLOCK_MASK;
+
+  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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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;
+    }
+
+  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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              return -ESPIPE;
+            }
+        }
+
+      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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, pdata->len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * 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 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;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        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;
+
+  /* Get the device geometry. (casting to uintptr_t first eliminates
+   * complaints on some architectures where the sizeof long is different
+   * from the size of a pointer).
+   */
+
+  ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,

Review Comment:
   let's keep MTDIOC_ERASEVALUE and MTDIOC_GEOMETRY in the same place



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2141 @@
+/****************************************************************************
+ * 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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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);
+  addr &= ADDR_BLOCK_MASK;
+
+  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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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;
+    }
+
+  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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)

Review Comment:
   remove? we don't have the special entry to mark delete



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2141 @@
+/****************************************************************************
+ * 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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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);
+  addr &= ADDR_BLOCK_MASK;
+
+  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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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;
+    }
+
+  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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              return -ESPIPE;
+            }
+        }
+
+      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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, pdata->len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * 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 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;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        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;
+
+  /* Get the device geometry. (casting to uintptr_t first eliminates
+   * complaints on some architectures where the sizeof long is different
+   * from the size of a pointer).
+   */
+
+  ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                  (unsigned long)((uintptr_t)&(fs->geo)));
+  if (ret < 0)
+    {
+      ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+      goto errout;
+    }
+
+  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);
+

Review Comment:
   remove the blank line



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r942587269


##########
include/nuttx/mtd/nvs_priv.h:
##########
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * include/nuttx/mtd/nvs_priv.h

Review Comment:
   move to drivers/mtd/mtd_nvs.h



##########
drivers/mtd/Kconfig:
##########
@@ -137,7 +137,7 @@ config MTD_PROGMEM
 		interfaces must be exported by chip-specific logic.
 
 if MTD_PROGMEM
-		
+

Review Comment:
   revert the change



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1221806810

   test cases are here:https://github.com/apache/incubator-nuttx-apps/pull/1288


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957967433


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;

Review Comment:
   it is redundant, I will remove it 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957947818


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&

Review Comment:
   If 0xffffffff is valid, why you check it here?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960401262


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);

Review Comment:
   No, the logic here is fine. We skip an ate specifically, then we try to recover the valid ate.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960159046


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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

Review Comment:
   the special value doesn't need to be the same as erase value. Here it is defined as 0xffffffff for simplicity.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945407575


##########
drivers/mtd/Make.defs:
##########
@@ -29,6 +29,10 @@ ifeq ($(CONFIG_MTD),y)
 
 CSRCS += ftl.c mtd_config.c
 
+ifeq ($(CONFIG_MTD_NVS),y)
+CSRCS += mtd_nvs.c
+endif

Review Comment:
   mtd_config.c was added in line 30 of this file. So this is not necessary



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1213159774

   Though this is a draft, please review it. Much appreciation to anyone helps to review it.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966592104


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,

Review Comment:
   To keep the logic same as c-string key. I prefer to keep it this way.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966597885


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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;
+
+  expired = (uint8_t)~CONFIG_MTD_CONFIG_ERASEDVALUE;

Review Comment:
   Simple id will need hash check logic  in startup/list, because of hash conflicts.
    I don't want to introduce more computing cost.
   I will make sure all necessary checks are there.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971467192


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;

Review Comment:
   I understand, that is why I suggest you change the loop to:
   ```
   while (1)
      {
        if (cnt_his++ == cnt)
         {
           break;
         }
      }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971474820


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  return nvs_write(fs, pdata);

Review Comment:
   If user wants to use mtd_config_fs driver directly, user needs to set pdata->len=0.
   Maybe it is better we set pdata->len =0 in nvs_delete, so that user doesn't need to do that?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1221732638

   I have rebased it


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1222550143

   @pkarashchenko could you review again?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962157624


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);

Review Comment:
   move the lock before line 2261 and unlock after line 2376



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;

Review Comment:
   ```suggestion
     expired = ~CONFIG_MTD_CONFIG_ERASEDVALUE;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);

Review Comment:
   MIN(step_ate.key_len, CONFIG_MTD_CONFIG_NAME_LEN)
   key[CONFIG_MTD_CONFIG_NAME_LEN - 1] = 0;



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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

Review Comment:
   ```suggestion
   #define ADDR_BLOCK_MASK                 0xFFFF0000
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   CONFIG_MTD_CONFIG_ERASEDVALUE->NVS_LOOKUP_CACHE_NO_ADDR



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,

Review Comment:
   ssize_t to int



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,

Review Comment:
   ssize_t->int



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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)

Review Comment:
   ```suggestion
                            FAR const void *data, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  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;
+
+  /* Get the device geometry. (casting to uintptr_t first eliminates
+   * complaints on some architectures where the sizeof long is different
+   * from the size of a pointer).
+   */
+
+  ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                  (unsigned long)((uintptr_t)&(fs->geo)));
+  if (ret < 0)
+    {
+      ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+      goto errout;
+    }
+
+  ret = nvs_init(fs);
+  if (ret < 0)
+    {
+      ferr("ERROR: nvs_init failed: %d\n", ret);
+      goto errout;
+    }
+
+  ret = register_driver("/dev/config", &g_mtdnvs_fops, 0666, fs);
+  if (ret < 0)
+    {
+      ferr("ERROR: register mtd config failed: %d\n", ret);
+      goto errout;
+    }
+
+  return ret;
+
+errout:
+  kmm_free(fs);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ate_wra = 0;
+        fs->data_wra = 0;

Review Comment:
   why need



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)

Review Comment:
   let's trust the driver?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;

Review Comment:
   ```suggestion
     int ret = -ENOTTY;
   ```
   and remove default case



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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,

Review Comment:
   ```suggestion
                                  FAR const uint8_t *key, size_t key_size,
                                  FAR const void *data, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)

Review Comment:
   The search is already done by nvs_recover_last_ate, why do here again?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);

Review Comment:
   why pass len field separately?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;

Review Comment:
   return -EDEADLK



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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;
+        }

Review Comment:
   should we break in case of invalid ate?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;

Review Comment:
   return rc



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);

Review Comment:
   why not call nvs_startup from mtdconfig_ioctl?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);

Review Comment:
   let's move nxmutex_init to mtdconfig_register and remove nxmutex_unlock/nxmutex_destroy



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;

Review Comment:
   move after line 1813



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;

Review Comment:
   return rc



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;

Review Comment:
   remove, done at line 1757



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */

Review Comment:
   let's use nuttx term erase size and block size



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)

Review Comment:
   merge to line 1826



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)

Review Comment:
   ```
   do
     {
     }
   while (addr != fs->ate_wra)
   ···
   and remove line 251-254.



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;

Review Comment:
   return rc



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;

Review Comment:
   return -ESPIPE



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+

Review Comment:
   remove blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);

Review Comment:
   return directly



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))

Review Comment:
   should support len == 0 entry?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;

Review Comment:
   return rc



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);

Review Comment:
   MIN(pdata->len, step_ate.len)



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:

Review Comment:
   remove the useless label



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)

Review Comment:
   how to handle the power lose before the old ate expire?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_next(FAR struct nvs_fs *fs,

Review Comment:
   nvs_first could be merged into nvs_next:
   nvs_next(FAR struct nvs_fs *fs, FAR struct config_data_s *pdata, bool first)



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,

Review Comment:
   why not save id/instance into id field?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))

Review Comment:
   why limit the user provided buffer size



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;

Review Comment:
   should we continue search if the entry can't find in cache



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)

Review Comment:
   change the loop to:
   ```
   for (; ;)
     {
       ....
       if (fs->step_addr == fs->ate_wra)
         {
           return -ENOENT;
         }
     }
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:

Review Comment:
   remove the useless label



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))

Review Comment:
   if expired item isn't returned, why caller need pass cnt argument?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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));

Review Comment:
   ```suggestion
                          MIN(len, wlk_ate.len));
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);

Review Comment:
   let's merge flash_write into nvs_flash_wrt?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);

Review Comment:
   let's merge flash_read into nvs_flash_rd



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+

Review Comment:
   remove blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);

Review Comment:
   merge flash_erase into nvs_flash_erase_block?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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));

Review Comment:
   ```suggestion
     fs->ate_wra += fs->geo.erasesize - sizeof(struct nvs_ate);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   should we trust the driver?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);

Review Comment:
   ```suggestion
       (fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),

Review Comment:
   why not call nvs_flash_wrt



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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))))

Review Comment:
   ```suggestion
         nvs_ate_crc8_check(entry) ||
         entry->offset >= fs->geo.erasesize - sizeof(struct nvs_ate))
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);

Review Comment:
   ```suggestion
             gc_ate.offset = fs->data_wra & ADDR_OFFS_MASK;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);

Review Comment:
   ```suggestion
     entry.offset = fs->data_wra & ADDR_OFFS_MASK;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these

Review Comment:
   which case the space can't contain gc ate?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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

Review Comment:
   but it's better to not same as erase value



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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) ||

Review Comment:
   ```suggestion
     if (!nvs_ate_valid(fs, entry) || entry->len != 0 ||
         entry->id != NVS_SPECIAL_ATE_ID)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);

Review Comment:
   ```suggestion
     gc_done_ate.offset = fs->data_wra & ADDR_OFFS_MASK;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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);

Review Comment:
   ```suggestion
             data_addr = gc_prev_addr & ADDR_BLOCK_MASK;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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);

Review Comment:
   how to guarantee the write never pass over the previous block?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != NVS_SPECIAL_ATE_ID)

Review Comment:
   remove the check, impossible



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   What about the block are written but not full?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962450823


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))

Review Comment:
   Remove it. Only close and gc_done ate has len == 0, but they are judged by step_ate.id != NVS_SPECIAL_ATE_ID later



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962571516


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;

Review Comment:
   just suggestion, could keep as.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971479979


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;

Review Comment:
   Got it, the original logic is redundant, I will optimize it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971452710


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;

Review Comment:
   we have to avoid certain value, like 0 or 0xfffffff. 0xffffffff is  special id for close ate and gc_done ate. 0 is undefined and may be used in later version.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960469471


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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 %lx, ate_size %d\n", fs->ate_wra,
+    sizeof(struct nvs_ate));
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->geo.erasesize - sizeof(struct nvs_ate);
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);

Review Comment:
   Compiled in sim:nsh, and I fixed all the warning.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962907429


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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);

Review Comment:
   I don't get it , what is the meaning of  "pass over"?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r958081982


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32

Review Comment:
   I will change its meaning to be a buffer. This buffer is for read and compare(or move) value.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945454223


##########
drivers/mtd/Make.defs:
##########
@@ -29,6 +29,10 @@ ifeq ($(CONFIG_MTD),y)
 
 CSRCS += ftl.c mtd_config.c
 
+ifeq ($(CONFIG_MTD_NVS),y)
+CSRCS += mtd_nvs.c
+endif

Review Comment:
   After some thinking, I will change it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945047880


##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2244 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include "mtd_nvs.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static uint32_t fnv_32_str(FAR const char *str)

Review Comment:
   fnv_hash_str



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2244 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include "mtd_nvs.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static uint32_t fnv_32_str(FAR const char *str)
+{
+  FAR unsigned char *s = (FAR unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s != 0)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE

Review Comment:
   where define this option?



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2244 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include "mtd_nvs.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static uint32_t fnv_32_str(FAR const char *str)
+{
+  FAR unsigned char *s = (FAR unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s != 0)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  size_t pos;
+
+#if CONFIG_NVS_LOOKUP_CACHE_SIZE <= UINT8_MAX
+  /* CRC8-CCITT is used for ATE checksums and it also acts well as a hash
+   * function, so it can be a good choice from the code size perspective.
+   * However, other hash functions can be used as well if proved better
+   * performance.
+   */
+
+  pos = crc8_ccitt(CRC8_CCITT_INITIAL_VALUE, &id, sizeof(id));
+#else
+  pos = crc16_ccitt(0xffff, (FAR const uint8_t *)&id, sizeof(id));
+#endif
+
+  return pos % CONFIG_NVS_LOOKUP_CACHE_SIZE;
+}
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_NVS_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+#ifdef CONFIG_MTD_BYTE_WRITE
+  MTD_WRITE(mtd, offset, len, data);

Review Comment:
   return error code



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966592607


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   Lets remove it. NVS has this check, but after some thinking, it should be the driver's work. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] pkarashchenko commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1222550817

   Yes, I will review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] CV-Bowen commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
CV-Bowen commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r951266153


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2435 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/* flash routines
+ * basic aligned flash write to nvs address
+ */
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/* 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/* 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  return rc;
+}
+
+/* data write */
+
+static int nvs_flash_data_wrt(FAR struct nvs_fs *fs,
+                              FAR const void *data, size_t len)
+{
+  int rc;
+
+  rc = nvs_flash_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\n",
+    fs->data_wra);
+
+  return rc;
+}
+
+/* flash ate read */
+
+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));
+}
+
+/* end of basic flash routines */
+
+/* advanced flash routines */
+
+/* nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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;
+}
+
+/* nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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;
+}
+
+/* nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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;
+}
+
+/* flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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;
+}
+
+/* erase a sector by first checking it is used and then erasing if required
+ * return 0 if OK, errorcode on error.
+ */
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* nvs_ate_cmp_const 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;
+}
+
+/* nvs_ate_valid validates an ate:
+ *     return 1 if crc8 and offset valid, and id != 0(not expired)
+ *            0 otherwise
+ */
+
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                            FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/* nvs_close_ate_valid validates an sector close ate:
+ * a valid sector close ate:
+ * - valid ate
+ * - len = 0 and id = 0xFFFFFFFF
+ * - offset points to location at ate multiple from sector size
+ * return 1 if valid, 0 otherwise
+ */
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/* 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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  rc = nvs_flash_ate_wrt(fs, &entry);
+  if (rc)
+    {
+      ferr("Write ate failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  return 0;
+}
+
+/* end of flash routines */
+
+/* If the closing ate is invalid, its offset cannot be trusted and
+ * the last valid ate of the sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  return 0;
+}
+
+/* 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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/* allocation entry close (this closes the current sector) by writing offset
+ * of last ate to the sector end.
+ */
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\n", fs->data_wra);
+
+  return 0;
+}
+
+static int nvs_add_gc_done_ate(FAR struct nvs_fs *fs)
+{
+  struct nvs_ate gc_done_ate;
+
+  finfo("Adding gc done ate at %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  nvs_ate_crc8_update(&gc_done_ate);
+
+  return nvs_flash_ate_wrt(fs, &gc_done_ate);
+}
+
+static int nvs_expire_ate(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  uint8_t expired = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/* garbage collection: the address ate_wra has been updated to the new sector
+ * that has just been started. The data to gc is in the sector after this new
+ * sector.
+ */
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(fs, sec_addr);
+  if (rc)
+    {
+      return rc;
+    }
+
+  return 0;
+}
+
+static int nvs_startup(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate last_ate;
+  size_t ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  rc = nxmutex_lock(&fs->nvs_lock);
+  if (rc < 0)
+    {
+      return rc;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  nxmutex_unlock(&fs->nvs_lock);
+  return rc;
+}
+
+/**
+ * @brief nvs_init
+ *
+ * Initializes a NVS file system in flash.
+ *
+ * @param fs Pointer to file system
+ * @retval 0 Success
+ * @retval -ERRNO errno code if error
+ */
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/**
+ * @brief nvs_read_hist
+ *
+ * Read a history entry from the file system. But expired ones will return
+ * -ENOENT.
+ * Only when we are updating an existing kv with new data and
+ * right before the prev one hasn't been expired, will there
+ * be two newest entries. At that time, nvs_read_hist is used to search
+ * for prev one.
+ *
+ * @param fs Pointer to file system
+ * @param key Key of the entry to be read
+ * @param key_size Size of key
+ * @param data Pointer to data buffer
+ * @param len Number of bytes to be read
+ * @param cnt History counter: 0: latest entry, 1:one before latest ...
+ * @param ate_addr the addr of found ate
+ *
+ * @return 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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/**
+ * @brief nvs_write
+ *
+ * Write an entry to the file system.
+ *
+ * @param fs Pointer to file system
+ * @param pdata Pointer to data buffer
+ * @param len Number of bytes to be written
+ *
+ * @return 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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)
+    {
+      fwarn("Previous found\n");
+
+      /* previous entry found */
+
+      rd_addr &= ADDR_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  rc = nxmutex_lock(&fs->nvs_lock);
+  if (rc < 0)
+    {
+      return rc;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          fwarn("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",

Review Comment:
   fwarn --> finfo?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r970735619


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        ret = nvs_startup(fs);
+
+        break;
+    }
+
+  nxmutex_unlock(&fs->nvs_lock);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item. */
+
+        ret = nvs_write(fs, pdata);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)

Review Comment:
   move line 1315-1318 to line 1256 and remove line 1262-1265



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* If gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->geo.neraseblocks - 1 -

Review Comment:
   why not directly expire the old entry



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  return nvs_write(fs, pdata);

Review Comment:
   do use need set pdata->len = 0 before call nvs_write



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        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));

Review Comment:
   ```suggestion
         fds->revents |= fds->events & (POLLIN | POLLOUT);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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)

Review Comment:
   ```
   if (ret >= 0)
     {
       ret = nvs_startup(fs);
     }
   
   break;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        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);
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, 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);
+        if (ret > 0)
+          {
+            finfo("nvs_read return %d\n", ret);
+            pdata->len = ret;

Review Comment:
   should we set pdata->len in nvs_read like nvs_next?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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,

Review Comment:
   align to line 1286



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;

Review Comment:
   ```suggestion
     hash_id = nvs_fnv_hash(key, key_size) % 0xfffffff;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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)))

Review Comment:
   ```suggestion
                                        (rd_addr & ADDR_BLOCK_MASK) + wlk_ate.offset, key, key_size)))
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;

Review Comment:
   why not return -ENOENT and remove line 1421-1427?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)

Review Comment:
   let' cnt start from 1, 2?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;

Review Comment:
   goto end



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;

Review Comment:
   goto end



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  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;
+
+#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
+
+  return nvs_read_hist(fs, key, key_size, pdata->configdata, pdata->len, 0,
+                       NULL);
+}
+
+/****************************************************************************
+ * 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, step_ate.key_len);

Review Comment:
   ```suggestion
                       key, MIN(sizeof(key), step_ate.key_len));
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;

Review Comment:
   if (cnt_his++ == cnt)
     {
       break;
     }



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)

Review Comment:
   remove, impossible



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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,

Review Comment:
   align line 1288-1290 to fs at line 1287



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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");
+

Review Comment:
   remove the blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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;
+            }

Review Comment:
   why not directly expire here?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)

Review Comment:
   merge line 1610-1632 to line 1572



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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;

Review Comment:
   ```suggestion
     hash_id = nvs_fnv_hash(key, key_size) % 0xffffffff;
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971492836


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* If gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->geo.neraseblocks - 1 -

Review Comment:
   But, it shouldn't have more than one duplicated entry at least nvs_start assume this in recovery process.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r959077188


##########
boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig:
##########
@@ -0,0 +1,50 @@
+#
+# 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_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

Review Comment:
   for CI to succeed, I didn't enable the testing



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957971720


##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,28 @@ 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

Review Comment:
   id only is supported now.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r945405946


##########
include/nuttx/mtd/configdata.h:
##########
@@ -120,6 +120,27 @@ extern "C"
 struct mtd_dev_s;
 int mtdconfig_register(FAR struct mtd_dev_s *mtd);
 
+/****************************************************************************
+ * Name: mtdnvs_register
+ *
+ * Description:
+ *   This function binds an instance of an MTD device to the /dev/config
+ *   device.
+ *
+ *   When this function is called, the MTD device pass in should already
+ *   be initialized appropriately to access the physical device or partition.
+ *
+ * Input Parameters:
+ *   mtd - Pointer to the MTD device to bind with the /dev/config device
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure.
+
+ *
+ ****************************************************************************/
+
+int mtdnvs_register(FAR struct mtd_dev_s *mtd);

Review Comment:
   The NVS is just a different backend.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954911528


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   will remove it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954958544


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, sizeof(struct nvs_ate));

Review Comment:
   The original zephyr implementation is exactly the same. I guess it tolerates some flash write  error, and moves on.
   So I'd rather not change its behavior.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962666236


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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;
+        }

Review Comment:
   We need to traverse all the ate, in case an invalid date is in the middle (Possible but unlikely).
   For robustness, I think it is better to traverse all.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962873332


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   It is stopped here, as nvs_ate_cmp_const returns 0.
   The discussion is misleading, so I put cases here to clarify.
   
   There are two cases we need to stop:
   1. nvs_prev_ate reached an empty block. Now last ate will be all 0xff
   2. all the blocks have been written, nvs_prev_ate reached gc block. The block for gc is empty, so last ate will be all 0xff.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962435247


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);

Review Comment:
   Alright, I will remove pdata->len param



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962527574


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   In this case the last ate only contain erase value?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962893265


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   How about we keep it here, if flash itself malfunctions, we can detect it?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1213154712

   I have found a few bugs, and will keep on fixing. So I convert this to draft.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r959610188


##########
boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig:
##########
@@ -0,0 +1,50 @@
+#
+# 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_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

Review Comment:
   Enabled after app/testing/nvcfgdata is merged



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r959936676


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;

Review Comment:
   move after line 94



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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

Review Comment:
   uthis id need same as erase value?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000

Review Comment:
   all SECT/sector to BLOCK/block?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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))))

Review Comment:
   ```suggestion
         (entry->offset > (fs->geo.erasesize - sizeof(struct nvs_ate))))
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;

Review Comment:
   ```suggestion
             data_end_addr += end_ate.offset + end_ate.key_len + end_ate.len;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\n",
+        fs->data_wra);

Review Comment:
   change lx to PRIxxx



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;

Review Comment:
   move to flash_erase



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)

Review Comment:
   ```suggestion
   static int nvs_flash_erase_block(FAR struct nvs_fs *fs, uint32_t addr)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);

Review Comment:
   ditto



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  if (ret < 0)

Review Comment:
   let move ret > 0 => 0 to flash_write, flash_read and flash_erase.



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;

Review Comment:
   let call memset with erase value and remove all 0xff



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   why can identify the file system end here?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)

Review Comment:
   sector->block? nuttx mtd don't have sector concept



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;

Review Comment:
   ~erase value



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);

Review Comment:
   remove? done by line 832



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)

Review Comment:
   all 0U to 0?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);

Review Comment:
   what about part field?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||

Review Comment:
   ```suggestion
     if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0) ||
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = NVS_SPECIAL_ATE_ID;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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 %lx, ate_size %d\n", fs->ate_wra,
+    sizeof(struct nvs_ate));
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->geo.erasesize - sizeof(struct nvs_ate);
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - sizeof(struct nvs_ate);
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)

Review Comment:
   not always 0xff



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r957964754


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&

Review Comment:
   0xffffffff is for gc_done ate or close ate. They indicate certain writing stages.
   But we won't need them for key/data, So we will just skip them during the cache process.
   
   An example of close ate is here, you can see that 0xffffffff is its id.
   static int nvs_sector_close(FAR struct nvs_fs *fs)
   {
     int rc;
     struct nvs_ate close_ate;
     size_t ate_size;
   
     ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
   
     close_ate.id = NVS_SPECIAL_ATE_ID;
     close_ate.len = 0U;
     close_ate.key_len = 0U;
     close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
   
     fs->ate_wra &= ADDR_SECT_MASK;
     fs->ate_wra += (fs->geo.erasesize - ate_size);
   
     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_sector_advance(fs, &fs->ate_wra);
   
     fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
     finfo("sector close, data_wra=0x%lx\n", fs->data_wra);
   
     return 0;
   }



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r956072084


##########
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

Review Comment:
   else ifeq ($(CONFIG_MTD_CONFIG),y)



##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,28 @@ 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
+	---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.
+
+config MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+	bool "Enable Fail Safe MTD Config lookup cache"
+	default n
+	depends on MTD_CONFIG_FAIL_SAFE
+	---help---
+		Enable ram buffer to speedup lookup process.
+
+config MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE
+	int "Fail Safe MTD Config lookup cache size"
+	default 8

Review Comment:
   can we remove MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE and indicate the disable by MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE == 0



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>

Review Comment:
   move after line 40



##########
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);

Review Comment:
   let's implement for mtd_config.c too
   



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);

Review Comment:
   move after line 2614



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      fs->sector_count = geo.neraseblocks;
+      fs->sector_size  = geo.erasesize;
+      fs->page_size    = geo.blocksize;

Review Comment:
   let's use NuttX term(sector/page v.s. erase/block) why not save geo in fs directly?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */

Review Comment:
   ```suggestion
         /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(FAR const uint8_t *input, uint32_t len)

Review Comment:
   fnv_hash_32->nvs_fnv_hash



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;

Review Comment:
   ```suggestion
         hval ^= *input++;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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 */

Review Comment:
   merge with line 119



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF

Review Comment:
   need compute through CONFIG_MTD_CONFIG_ERASEDVALUE



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&

Review Comment:
   let's define a macro for 0xffffffff. why not check ate.id in nvs_ate_valid



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */

Review Comment:
   add one blank line



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      fs->sector_count = geo.neraseblocks;
+      fs->sector_size  = geo.erasesize;
+      fs->page_size    = geo.blocksize;
+
+      ret = nvs_init(fs);
+
+      if (ret < 0)
+        {
+          ferr("ERROR: nvs_init failed: %d\n", ret);
+          kmm_free(fs);

Review Comment:
   remove



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);

Review Comment:
   all mtdnvs_ to mtdconfig_



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);

Review Comment:
   return directly



##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,28 @@ config MTD_CONFIG_NAME_LEN
 	---help---
 		Sets the maximum length of config item names.
 
+config MTD_CONFIG_FAIL_SAFE

Review Comment:
   let's unify the term either FAIL_SAFE or FAILSAFE



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4

Review Comment:
   why use 4 as block size not 1?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);

Review Comment:
   why not directly return here



##########
boards/risc-v/esp32c3/esp32c3-devkit/configs/nvcfgdata/defconfig:
##########
@@ -0,0 +1,50 @@
+#
+# 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_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

Review Comment:
   should we enable the testing too?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */

Review Comment:
   let's use nuttx term(erase v.s. sector) to avoid the confusion.



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;

Review Comment:
   why need ready flag



##########
drivers/mtd/Kconfig:
##########
@@ -192,6 +192,28 @@ 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

Review Comment:
   should we depend on MTD_CONFIG_NAMED or let's failsafe support id only too?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =

Review Comment:
   add g_ prefix



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32

Review Comment:
   why not define to sizeof(struct nvs_ate)



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      fs->sector_count = geo.neraseblocks;
+      fs->sector_size  = geo.erasesize;
+      fs->page_size    = geo.blocksize;
+
+      ret = nvs_init(fs);
+
+      if (ret < 0)
+        {
+          ferr("ERROR: nvs_init failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      register_driver("/dev/config", &mtdnvs_fops, 0666, fs);

Review Comment:
   ret = register_driver("/dev/config", &mtdnvs_fops, 0666, fs);
   if (ret < 0)
     {
       goto errout;
     }
   
   return OK;



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)

Review Comment:
   if (fs == NULL)
     {
       return -ENOMEM;
     }



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)

Review Comment:
   don't need, nuttx only support either byte write or block write.



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];

Review Comment:
   remove the unnecessary alignment



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      fs->sector_count = geo.neraseblocks;
+      fs->sector_size  = geo.erasesize;
+      fs->page_size    = geo.blocksize;
+
+      ret = nvs_init(fs);
+
+      if (ret < 0)
+        {
+          ferr("ERROR: nvs_init failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      register_driver("/dev/config", &mtdnvs_fops, 0666, fs);
+    }
+
+errout:
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_unregister
+ *
+ * Description:
+ *   Unregister a /dev/config device backed by an fail-safe NVS.
+ *
+ ****************************************************************************/
+
+int mtdconfig_unregister(void)
+{
+  int ret = -ENOMEM;
+  struct file mtdnvs_file;

Review Comment:
   ```suggestion
     struct file file;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);
+            pdata->len = ret;
+            ret = OK;
+          }
+
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_SETCONFIG:
+
+        /* Write a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_write(fs, pdata, pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_DELCONFIG:
+
+        /* Delete a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_delete(fs, pdata);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_FIRSTCONFIG:
+
+        /* Get the first item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_first(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case CFGDIOC_NEXTCONFIG:
+
+        /* Get the next item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_next(fs, pdata, &pdata->len);
+        nxmutex_unlock(&fs->nvs_lock);
+
+        break;
+
+      case MTDIOC_BULKERASE:
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        /* Call the MTD's ioctl for this */
+
+        ret = MTD_IOCTL(fs->mtd, cmd, arg);
+        if (ret < 0)
+          {
+            nxmutex_unlock(&fs->nvs_lock);
+            return ret;
+          }
+
+        fs->ready = false;
+        fs->ate_wra = 0;
+        fs->data_wra = 0;
+        nxmutex_unlock(&fs->nvs_lock);
+
+        nxmutex_destroy(&fs->nvs_lock);
+
+        ret = nvs_init(fs);
+
+        break;
+
+      default:
+        ret = -EINVAL;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_poll
+ ****************************************************************************/
+
+static int mtdnvs_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 = -ENOMEM;
+  FAR struct nvs_fs *fs;
+  struct mtd_geometry_s geo;      /* Device geometry */
+
+  fs = (FAR struct nvs_fs *)kmm_malloc(sizeof(struct nvs_fs));
+  if (fs != NULL)
+    {
+      /* Initialize the mtdnvs device structure */
+
+      fs->mtd = mtd;
+
+      /* Get the device geometry. (casting to uintptr_t first eliminates
+       * complaints on some architectures where the sizeof long is different
+       * from the size of a pointer).
+       */
+
+      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
+                      (unsigned long)((uintptr_t)&geo));
+      if (ret < 0)
+        {
+          ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      fs->sector_count = geo.neraseblocks;
+      fs->sector_size  = geo.erasesize;
+      fs->page_size    = geo.blocksize;
+
+      ret = nvs_init(fs);
+
+      if (ret < 0)
+        {
+          ferr("ERROR: nvs_init failed: %d\n", ret);
+          kmm_free(fs);
+          goto errout;
+        }
+
+      register_driver("/dev/config", &mtdnvs_fops, 0666, fs);
+    }
+
+errout:
+  return ret;
+}
+
+/****************************************************************************
+ * Name: mtdconfig_unregister
+ *
+ * Description:
+ *   Unregister a /dev/config device backed by an fail-safe NVS.
+ *
+ ****************************************************************************/
+
+int mtdconfig_unregister(void)
+{
+  int ret = -ENOMEM;
+  struct file mtdnvs_file;
+  FAR struct inode *inode;
+  FAR struct nvs_fs *fs;
+
+  ret = file_open(&mtdnvs_file, "/dev/config", O_RDONLY);
+  if (ret < 0)
+    {
+      ferr("ERROR: open /dev/config failed: %d\n", ret);
+      goto errout;

Review Comment:
   ```suggestion
         return ret;
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2652 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (len == 0)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len > 0)
+    {
+      memcpy(buf, data8, len);
+      memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  memset(cmp, value, block_size);
+  while (len > 0)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+                     &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+            fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                               second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+                    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_open
+ ****************************************************************************/
+
+static int mtdnvs_open(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_close
+ ****************************************************************************/
+
+static int mtdnvs_close(FAR struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_read
+ ****************************************************************************/
+
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen)
+{
+  return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: mtdnvs_ioctl
+ ****************************************************************************/
+
+static int mtdnvs_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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);
+        if (ret > 0)
+          {
+            finfo("nvs_read_hist return %d\n", ret);

Review Comment:
   ```suggestion
               finfo("nvs_read return %d\n", ret);
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954938279


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */

Review Comment:
   okay, lets use uint8_t reserved[3];



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] pkarashchenko commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954861712


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */

Review Comment:
   why not just `uint8_t reserved[3];`?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)

Review Comment:
   ```suggestion
   static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
                             FAR void *data, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)

Review Comment:
   ```suggestion
   static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
                                           uint32_t sector)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)

Review Comment:
   ```suggestion
   static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
                          size_t nblocks)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(FAR struct nvs_fs *fs, uint32_t addr,
+                             FAR const void *data, size_t len)

Review Comment:
   ```suggestion
   static int nvs_flash_al_wrt(FAR struct nvs_fs *fs, uint32_t addr,
                               FAR const void *data, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)

Review Comment:
   ```suggestion
     if (len == 0)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));

Review Comment:
   ```suggestion
     memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
            sizeof(fs->lookup_cache));
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)

Review Comment:
   ```suggestion
     if (len > 0)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, sizeof(struct nvs_ate));

Review Comment:
   do we need to check `rc` in prior to this actions?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)

Review Comment:
   ```suggestion
     while (len > 0)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)

Review Comment:
   ```suggestion
     while (len > 0)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);

Review Comment:
   do we need a cast here?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)

Review Comment:
   ```suggestion
   static int nvs_ate_valid(FAR struct nvs_fs *fs,
                            FAR const struct nvs_ate *entry)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)

Review Comment:
   ```suggestion
   static int nvs_ate_cmp_const(FAR const struct nvs_ate *entry,
                                uint8_t value)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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)

Review Comment:
   ```suggestion
   static int nvs_recover_last_ate(FAR struct nvs_fs *fs,
                                   FAR uint32_t *addr)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\n", fs->ate_wra,
+    fs->data_wra);

Review Comment:
   ```suggestion
     finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\n", fs->ate_wra,
           fs->data_wra);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\n",
+            last_addr, last_ate.id, last_ate.key_len, last_ate.offset);

Review Comment:
   ```suggestion
             finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\n",
                   last_addr, last_ate.id, last_ate.key_len, last_ate.offset);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;

Review Comment:
   ```suggestion
     off_t offset;
   
     offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
     offset += addr & ADDR_OFFS_MASK;
   
     return flash_read(fs->mtd, offset, data, len);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   do we really need cast here?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)

Review Comment:
   ```suggestion
   static ssize_t nvs_read(FAR struct nvs_fs *fs,
                           FAR struct config_data_s *pdata, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\n",
+    fs->data_wra);

Review Comment:
   ```suggestion
     finfo("write data done, data_wra=0x%lx\n",
           fs->data_wra);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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)

Review Comment:
   ```suggestion
   static int nvs_flash_ate_wrt(FAR struct nvs_fs *fs,
                                FAR const struct nvs_ate *entry)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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)

Review Comment:
   ```suggestion
   static int nvs_flash_direct_cmp(FAR struct nvs_fs *fs, uint32_t addr1,
                                   uint32_t addr2, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);

Review Comment:
   ```suggestion
     rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
                       key, step_ate.key_len);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);

Review Comment:
   ```suggestion
     rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
                       key, step_ate.key_len);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)

Review Comment:
   ```suggestion
     while (len > 0)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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)

Review Comment:
   ```suggestion
   static int nvs_flash_block_move(FAR struct nvs_fs *fs, uint32_t addr,
                                   size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));

Review Comment:
   ```suggestion
     return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
                        &expired, sizeof(expired));
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);

Review Comment:
   ```suggestion
         finfo("update for powerloss data write, data_wra=0x%lx\n",
               fs->data_wra);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\n",
+        fs->data_wra);

Review Comment:
   ```suggestion
         finfo("erase due to no data, data_wra=0x%lx\n",
               fs->data_wra);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\n",
+                    second_addr, second_ate.key_len, second_ate.offset);

Review Comment:
   ```suggestion
                     finfo("same id at 0x%lx, key_len %d, offset %d\n",
                           second_addr, second_ate.key_len, second_ate.offset);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);

Review Comment:
   ```suggestion
                             ferr("expire ate failed, addr %lx\n",
                                  second_addr);
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                FAR struct config_data_s *pdata, FAR size_t *len)

Review Comment:
   ```suggestion
   static int nvs_first(FAR struct nvs_fs *fs,
                        FAR struct config_data_s *pdata, FAR size_t *len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)

Review Comment:
   ```suggestion
   static ssize_t nvs_write(FAR struct nvs_fs *fs,
                            FAR struct config_data_s *pdata, size_t len)
   ```



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   flash block move: 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_copy = MIN(block_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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->sector_size))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)
+{
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if ((nvs_ate_crc8_check(entry)) ||
+      (entry->offset >= (fs->sector_size - ate_size)))
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+/****************************************************************************
+ * Name: nvs_close_ate_valid
+ *
+ * Description:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector size
+ *   return 1 if valid, 0 otherwise
+ *
+ ****************************************************************************/
+
+static int nvs_close_ate_valid(FAR struct nvs_fs *fs,
+                               FAR const struct nvs_ate *entry)
+{
+  size_t ate_size;
+
+  if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
+      (entry->id != 0xffffffff))
+    {
+      return 0;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+  if ((fs->sector_size - entry->offset) % ate_size)
+    {
+      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;
+  uint8_t buf[NVS_BLOCK_SIZE];
+  size_t bytes_to_write;
+  size_t block_size;
+  size_t total_data_size;
+  size_t bytes_written = 0;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved = 0xff;
+  entry.reserved2 = 0xffff;
+
+  /* let's sew key and data into one, key comes first, then data */
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  total_data_size = key_size + len;
+
+  while (total_data_size)
+    {
+      bytes_to_write = MIN(block_size, total_data_size);
+      if (key_size >= bytes_written + bytes_to_write)
+        {
+          memcpy(buf, key + bytes_written, bytes_to_write);
+        }
+      else if (bytes_written < key_size && bytes_written + bytes_to_write
+        > key_size)
+        {
+          memcpy(buf, key + bytes_written, key_size - bytes_written);
+          memcpy(buf + key_size - bytes_written, data,
+            bytes_to_write - (key_size - bytes_written));
+        }
+      else
+        {
+          memcpy(buf, data + bytes_written - key_size, bytes_to_write);
+        }
+
+      (void)memset(buf + bytes_to_write, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         NVS_BLOCK_SIZE - bytes_to_write);
+
+      rc = nvs_flash_data_wrt(fs, buf, bytes_to_write);
+      if (rc)
+        {
+          ferr("Write data failed, rc=%d\n", rc);
+          return rc;
+        }
+
+      bytes_written += bytes_to_write;
+      total_data_size -= bytes_to_write;
+    }
+
+  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 sector 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;
+  size_t ate_size;
+  int rc;
+
+  finfo("Recovering last ate from sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  *addr -= ate_size;
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_len;
+          *addr = ate_end_addr;
+        }
+
+      ate_end_addr -= ate_size;
+    }
+
+  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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  rc = nvs_flash_ate_rd(fs, *addr, ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  *addr += ate_size;
+  if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size))
+    {
+      return 0;
+    }
+
+  /* last ate in sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count)
+    {
+      *addr -= (fs->sector_count << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  close_ate.id = 0xffffffff;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
+
+  fs->ate_wra &= ADDR_SECT_MASK;
+  fs->ate_wra += (fs->sector_size - ate_size);
+
+  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_sector_advance(fs, &fs->ate_wra);
+
+  fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+  finfo("sector close, data_wra=0x%lx\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 %lx\n", fs->ate_wra & ADDR_OFFS_MASK);
+  gc_done_ate.id = 0xffffffff;
+  gc_done_ate.len = 0U;
+  gc_done_ate.key_len = 0U;
+  gc_done_ate.offset = (uint16_t)(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 = 0x7f;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + offsetof(struct nvs_ate, expired),
+    &expired, sizeof(expired));
+}
+
+/****************************************************************************
+ * Name: nvs_gc
+ *
+ * Description:
+ *   garbage collection: the address ate_wra has been updated to the new
+ *   sector that has just been started. The data to gc is in the sector
+ *   after this new sector.
+ *
+ ****************************************************************************/
+
+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;
+  size_t ate_size;
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  finfo("gc: before gc, ate_wra %lx, ate_size %d\n", fs->ate_wra,
+    ate_size);
+
+  sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
+  nvs_sector_advance(fs, &sec_addr);
+  gc_addr = sec_addr + fs->sector_size - ate_size;
+
+  finfo("gc: set, sec_addr %lx, gc_addr %lx\n", sec_addr, gc_addr);
+
+  /* if the sector 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  if (!rc)
+    {
+      goto gc_done;
+    }
+
+  stop_addr = gc_addr - ate_size;
+
+  if (nvs_close_ate_valid(fs, &close_ate))
+    {
+      gc_addr &= ADDR_SECT_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 != 0xff)
+        {
+          /* deleted or old ate, ignore it */
+
+          continue;
+        }
+
+      if (!nvs_ate_valid(fs, &gc_ate))
+        {
+          continue;
+        }
+
+      if (gc_ate.id != 0xffffffff)
+        {
+          /* copy needed */
+
+          finfo("Moving %ld, key_len %d, len %d\n", gc_ate.id,
+            gc_ate.key_len, gc_ate.len);
+
+          data_addr = (gc_prev_addr & ADDR_SECT_MASK);
+          data_addr += gc_ate.offset;
+
+          gc_ate.offset = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the sector. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + ate_size))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+      if (rc)
+        {
+          return rc;
+        }
+    }
+
+  /* Erase the gc'ed sector */
+
+  rc = nvs_flash_erase_sector(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 ate_size;
+  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->sector_count == 0. This
+   * should never happen as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0U;
+  uint16_t i;
+  uint16_t closed_sectors = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  /* Step through the sectors to find a open sector following
+   * a closed sector, this is where NVS can write.
+   */
+
+  for (i = 0; i < fs->sector_count; i++)
+    {
+      addr = (i << ADDR_SECT_SHIFT) + (uint16_t)(fs->sector_size - ate_size);
+      rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                               sizeof(struct nvs_ate));
+      fwarn("rc=%d\n", rc);
+      if (rc)
+        {
+          /* Closed sector */
+
+          closed_sectors++;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_cmp_const(fs, addr, erase_value,
+                                   sizeof(struct nvs_ate));
+          if (!rc)
+            {
+              /* Open sector */
+
+              break;
+            }
+        }
+
+      fwarn("i=%d, closed_sectors=%d, addr=0x%lx\n",
+            i, closed_sectors, addr);
+    }
+
+  /* All sectors are closed, this is not a nvs fs */
+
+  if (closed_sectors == fs->sector_count)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  if (i == fs->sector_count)
+    {
+      /* None of the sectors where closed, in most cases we can set
+       * the address to the first sector, except when there are only
+       * two sectors. Then we can only set it to the first sector if
+       * the last sector contains no ate's. So we check this first
+       */
+
+      rc = nvs_flash_cmp_const(fs, addr - ate_size, erase_value,
+                               sizeof(struct nvs_ate));
+      if (!rc)
+        {
+          /* Empty ate */
+
+          nvs_sector_advance(fs, &addr);
+        }
+    }
+
+  /* Addr contains address of closing ate in the most recent sector,
+   * search for the last valid ate using the recover_last_ate routine
+   */
+
+  rc = nvs_recover_last_ate(fs, &addr);
+  if (rc)
+    {
+      goto end;
+    }
+
+  /* Addr contains address of the last valid ate in the most recent sector
+   * 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_SECT_MASK;
+  finfo("recovered ate ate_wra=0x%lx, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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_SECT_MASK;
+
+          /* Align the data write address to the current
+           * write block size so that it is possible to write to
+           * the sector even if the block size has changed after
+           * a software upgrade (unless the physical ATE size
+           * will change)."
+           */
+
+          fs->data_wra += nvs_al_size(fs,
+            last_ate.offset + last_ate.key_len + last_ate.len);
+          finfo("recovered data_wra=0x%lx\n", fs->data_wra);
+
+          /* Ate on the last position within the sector is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      fs->ate_wra -= ate_size;
+    }
+
+  /* If the sector after the write sector is not empty, gc was interrupted
+   * we might need to restart gc if it has not yet finished. Otherwise
+   * just erase the sector.
+   * When gc needs to be restarted, first erase the sector otherwise the
+   * data might not fit into the sector.
+   */
+
+  addr = fs->ate_wra & ADDR_SECT_MASK;
+  nvs_sector_advance(fs, &addr);
+  rc = nvs_flash_cmp_const(fs, addr, erase_value, fs->sector_size);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  if (rc)
+    {
+      /* The sector 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 + ate_size;
+      while ((addr & ADDR_OFFS_MASK) < (fs->sector_size - ate_size))
+        {
+          rc = nvs_flash_ate_rd(fs, addr, &gc_done_ate);
+          if (rc)
+            {
+              goto end;
+            }
+
+          if (nvs_ate_valid(fs, &gc_done_ate) &&
+              (gc_done_ate.id == 0xffffffff) &&
+              (gc_done_ate.len == 0U))
+            {
+              gc_done_marker = true;
+              break;
+            }
+
+          addr += ate_size;
+        }
+
+      if (gc_done_marker)
+        {
+          /* Erase the next sector */
+
+          fwarn("GC Done marker found\n");
+          addr = fs->ate_wra & ADDR_SECT_MASK;
+          nvs_sector_advance(fs, &addr);
+          rc = nvs_flash_erase_sector(fs, addr);
+          goto end;
+        }
+
+      fwarn("No GC Done marker found: restarting gc\n");
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->ate_wra &= ADDR_SECT_MASK;
+      fs->ate_wra += (fs->sector_size - 2 * ate_size);
+      fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
+      finfo("GC when data_wra=0x%lx\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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      if (!rc)
+        {
+          break;
+        }
+
+      fs->data_wra += CONFIG_MTD_NVS_WRITE_BLOCK_SIZE;
+      finfo("update for powerloss data write, data_wra=0x%lx\n",
+        fs->data_wra);
+    }
+
+  /* If the ate_wra is pointing to the first ate write location in a
+   * sector and data_wra is not 0, erase the sector as it contains no
+   * valid data (this also avoids closing a sector without any data).
+   */
+
+  if (((fs->ate_wra + 2 * ate_size) == fs->sector_size) &&
+      (fs->data_wra != (fs->ate_wra & ADDR_SECT_MASK)))
+    {
+      rc = nvs_flash_erase_sector(fs, fs->ate_wra);
+      if (rc)
+        {
+          goto end;
+        }
+
+      fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
+      finfo("erase due to no data, data_wra=0x%lx\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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate) && (last_ate.id != 0xffffffff))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          finfo("ate found at 0x%lx, id %lx, key_len %d, offset %d\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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == 0xff)
+                {
+                  finfo("same id at 0x%lx, key_len %d, offset %d\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_SECT_MASK) + last_ate.offset,
+                    (second_addr & ADDR_SECT_MASK) + second_ate.offset,
+                    last_ate.key_len)))
+                    {
+                      finfo("old ate found at 0x%lx\n", second_addr);
+                      rc = nvs_expire_ate(fs, second_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_addr);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+end:
+  /* If the sector is empty, add a gc done ate to avoid having insufficient
+   * space when doing gc.
+   */
+
+  if ((!rc) && ((fs->ate_wra & ADDR_OFFS_MASK) ==
+      (fs->sector_size - 2 * ate_size)))
+    {
+      rc = nvs_add_gc_done_ate(fs);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+  size_t write_block_size;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  /* check that the write block size is supported */
+
+  if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0)
+    {
+      ferr("Unsupported write block size\n");
+      return -EINVAL;
+    }
+
+  /* check that sector size is a multiple of pagesize */
+
+  if (!fs->sector_size || fs->sector_size % fs->page_size)
+    {
+      ferr("Invalid sector size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of sectors, it should be at least 2 */
+
+  if (fs->sector_count < 2)
+    {
+      ferr("Configuration error - sector count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  /* nvs is ready for use */
+
+  fs->ready = true;
+
+  finfo("%d Sectors of %d bytes\n", fs->sector_count, fs->sector_size);
+  finfo("alloc wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK));
+  finfo("data wra: %" PRIu32 ", 0x%" PRIx32 "\n",
+        (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  size_t ate_size;
+  uint32_t hash_id;
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+  if (len > (fs->sector_size - 2 * ate_size))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0U;
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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_SECT_MASK) + wlk_ate.offset, key, key_size)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != 0xff) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  if (data)
+    {
+      rd_addr &= ADDR_SECT_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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  int rc;
+  int gc_count;
+  size_t ate_size;
+  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 = 0U; /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t sector_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (!fs->ready)
+    {
+      ferr("NVS not initialized");
+      return -EACCES;
+    }
+
+  ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
+
+#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 = nvs_al_size(fs, key_size + len);
+
+  /* The maximum data size is sector size - 4 ate
+   * where: 1 ate for data, 1 ate for sector close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%d, len=%d\n", key_size, len);
+
+  if ((data_size > (fs->sector_size - 4 * ate_size)) ||
+      ((len > 0) && (pdata->configdata == NULL)))
+    {
+      return -EINVAL;
+    }
+
+  /* calc hash id of key */
+
+  hash_id = fnv_hash_32(key, key_size) % 0xfffffffd + 1;
+
+  /* find latest entry with same id */
+
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  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_SECT_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_SECT_MASK;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != 0xff)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len && wlk_ate.expired == 0xff)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + ate_size;
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      finfo("nvs_delete success\n");
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      goto end;
+    }
+
+  gc_count = 0;
+  sector_to_write_befor_gc = fs->ate_wra >> ADDR_SECT_SHIFT;
+  while (1)
+    {
+      if (gc_count == fs->sector_count)
+        {
+          /* gc'ed all sectors, no extra space will be created
+           * by extra gc.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          finfo("Write entry, ate_wra=0x%lx, data_wra=0x%lx\n",
+                fs->ate_wra, fs->data_wra);
+          rc = nvs_flash_wrt_entry(fs, hash_id, key, key_size,
+            pdata->configdata, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* if gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->sector_count - 1 -
+                (sector_to_write_befor_gc + fs->sector_count -
+                (hist_addr >> ADDR_SECT_SHIFT))
+                % fs->sector_count)
+                {
+                  rc = nvs_read_hist(fs, key, key_size, NULL, 0, 1,
+                    &second_latest_ate_addr);
+                  finfo("search for prev entry, %lx, rc %d\n",
+                    second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %lx\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  rc = nvs_expire_ate(fs, hist_addr);
+                  finfo("expir prev entry, %lx, rc %d\n",
+                    hist_addr, rc);
+
+                  if (rc < 0)
+                    {
+                      ferr("expire ate failed, addr %lx\n", hist_addr);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_sector_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      fwarn("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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 (!fs->ready)
+    {
+      ferr("NVS not initialized\n");
+      return -EACCES;
+    }
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+        && step_ate.id != 0xffffffff && step_ate.expired == 0xff)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_SECT_MASK) + step_ate.offset,
+    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_SECT_MASK) + step_ate.offset + step_ate.key_len,
+    pdata->configdata, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)

Review Comment:
   ```suggestion
   static int nvs_next(FAR struct nvs_fs *fs,
                       FAR struct config_data_s *pdata, FAR size_t *len)
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] pkarashchenko commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r942601486


##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);

Review Comment:
   ```suggestion
   static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
                             FAR void *data, size_t len);
   static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
                              FAR const uint8_t *data, size_t len);
   static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)

Review Comment:
   ```suggestion
   uint32_t fnv_32_str(FAR const char *str)
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  size_t pos;
+
+#if CONFIG_NVS_LOOKUP_CACHE_SIZE <= UINT8_MAX
+  /* CRC8-CCITT is used for ATE checksums and it also acts well as a hash
+   * function, so it can be a good choice from the code size perspective.
+   * However, other hash functions can be used as well if proved better
+   * performance.
+   */
+
+  pos = crc8_ccitt(CRC8_CCITT_INITIAL_VALUE, &id, sizeof(id));
+#else
+  pos = crc16_ccitt(0xffff, (const uint8_t *)&id, sizeof(id));
+#endif
+
+  return pos % CONFIG_NVS_LOOKUP_CACHE_SIZE;
+}
+
+static int nvs_lookup_cache_rebuild(struct nvs_fs *fs)

Review Comment:
   ```suggestion
   static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  size_t pos;
+
+#if CONFIG_NVS_LOOKUP_CACHE_SIZE <= UINT8_MAX
+  /* CRC8-CCITT is used for ATE checksums and it also acts well as a hash
+   * function, so it can be a good choice from the code size perspective.
+   * However, other hash functions can be used as well if proved better
+   * performance.
+   */
+
+  pos = crc8_ccitt(CRC8_CCITT_INITIAL_VALUE, &id, sizeof(id));
+#else
+  pos = crc16_ccitt(0xffff, (const uint8_t *)&id, sizeof(id));
+#endif
+
+  return pos % CONFIG_NVS_LOOKUP_CACHE_SIZE;
+}
+
+static int nvs_lookup_cache_rebuild(struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  uint32_t *cache_entry;

Review Comment:
   ```suggestion
     FAR uint32_t *cache_entry;
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s)

Review Comment:
   Optional
   ```suggestion
     while (*s != 0)
   ```



##########
include/nuttx/mtd/nvs_priv.h:
##########
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * include/nuttx/mtd/nvs_priv.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+#define __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <assert.h>
+#include <nuttx/compiler.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* MASKS AND SHIFT FOR ADDRESSES
+ * an address in nvs is an uint32_t where:
+ *   high 2 bytes represent the sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK           0xFFFF0000
+#define ADDR_SECT_SHIFT          16
+#define ADDR_OFFS_MASK           0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE       1
+
+#define NVS_BLOCK_SIZE           32
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param offset File system offset in flash
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  struct   mtd_dev_s *mtd;   /* mtd device */
+  off_t    offset;           /* filesystem offset in flash */
+  uint32_t ate_wra;          /* next alloc table entry write address */
+  uint32_t data_wra;         /* next data write address */
+  uint16_t sector_size;      /* filesystem is divided into sectors,
+                              * sector size should be multiple of pagesize
+                              */
+  uint16_t page_size;        /* page size */
+  uint16_t sector_count;     /* amount of sectors in the filesystem */
+  bool     ready;            /* is the filesystem initialized ? */
+
+  uint32_t step_addr;        /* for traverse */
+  sem_t    nvs_lock;
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_MTD_NVS_PRIV_H_ */

Review Comment:
   ```suggestion
   #endif /* __INCLUDE_NUTTX_MTD_NVS_PRIV_H */
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */

Review Comment:
   Why do we need this comments? I mean especially some that are not followed by any APIs



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,

Review Comment:
   ```suggestion
   static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;

Review Comment:
   ```suggestion
     FAR unsigned char *s = (FAR unsigned char *)str;
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  size_t pos;
+
+#if CONFIG_NVS_LOOKUP_CACHE_SIZE <= UINT8_MAX
+  /* CRC8-CCITT is used for ATE checksums and it also acts well as a hash
+   * function, so it can be a good choice from the code size perspective.
+   * However, other hash functions can be used as well if proved better
+   * performance.
+   */
+
+  pos = crc8_ccitt(CRC8_CCITT_INITIAL_VALUE, &id, sizeof(id));
+#else
+  pos = crc16_ccitt(0xffff, (const uint8_t *)&id, sizeof(id));
+#endif
+
+  return pos % CONFIG_NVS_LOOKUP_CACHE_SIZE;
+}
+
+static int nvs_lookup_cache_rebuild(struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, 0xff, sizeof(fs->lookup_cache));

Review Comment:
   should we have `0xff` to be a define or erased value?



##########
include/nuttx/mtd/nvs_priv.h:
##########
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * include/nuttx/mtd/nvs_priv.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+#define __INCLUDE_NUTTX_MTD_NVS_PRIV_H_

Review Comment:
   ```suggestion
   #ifndef __INCLUDE_NUTTX_MTD_NVS_PRIV_H
   #define __INCLUDE_NUTTX_MTD_NVS_PRIV_H
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  size_t pos;
+
+#if CONFIG_NVS_LOOKUP_CACHE_SIZE <= UINT8_MAX
+  /* CRC8-CCITT is used for ATE checksums and it also acts well as a hash
+   * function, so it can be a good choice from the code size perspective.
+   * However, other hash functions can be used as well if proved better
+   * performance.
+   */
+
+  pos = crc8_ccitt(CRC8_CCITT_INITIAL_VALUE, &id, sizeof(id));
+#else
+  pos = crc16_ccitt(0xffff, (const uint8_t *)&id, sizeof(id));
+#endif
+
+  return pos % CONFIG_NVS_LOOKUP_CACHE_SIZE;
+}
+
+static int nvs_lookup_cache_rebuild(struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, 0xff, sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+static void nvs_lookup_cache_invalidate(struct nvs_fs *fs, uint32_t sector)

Review Comment:
   ```suggestion
   static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs, uint32_t sector)
   ```
   here and other places



##########
include/nuttx/mtd/nvs_priv.h:
##########
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * include/nuttx/mtd/nvs_priv.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+#define __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <assert.h>
+#include <nuttx/compiler.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* MASKS AND SHIFT FOR ADDRESSES
+ * an address in nvs is an uint32_t where:
+ *   high 2 bytes represent the sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK           0xFFFF0000
+#define ADDR_SECT_SHIFT          16
+#define ADDR_OFFS_MASK           0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE       1
+
+#define NVS_BLOCK_SIZE           32
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param offset File system offset in flash
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  struct   mtd_dev_s *mtd;   /* mtd device */

Review Comment:
   ```suggestion
     FAR struct   mtd_dev_s *mtd;   /* mtd device */
   ```



##########
drivers/mtd/mtd_nvs.c:
##########
@@ -0,0 +1,2241 @@
+/****************************************************************************
+ * drivers/mtd/mtd_nvs.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 <crc8.h>
+#include <debug.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+#include <nuttx/mtd/nvs_priv.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#define MIN(a, b)                       ((a) > (b) ? (b) : (a))
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, struct pollfd *fds,
+                           bool setup);
+
+/* Nvs external api, called in mtdnvs_ioctl() and mtdnvs_register() */
+
+/* Nvs internal api */
+
+/* Nvs flash operation api */
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(struct mtd_dev_s *mtd, off_t offset,
+                          void *data, size_t len);
+static ssize_t flash_write(struct mtd_dev_s *mtd, off_t offset,
+                           const uint8_t *data, size_t len);
+static int flash_erase(struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+uint32_t fnv_32_str(const char *str)
+{
+  unsigned char *s = (unsigned char *)str;
+  uint32_t hval = 2166136261;
+
+  /* FNV-1 hash each octet in the buffer */
+
+  while (*s)
+    {
+      /* multiply by the 32 bit FNV magic prime mod 2^32 */
+
+      hval *= 0x01000193;
+
+      /* xor the bottom with the current octet */
+
+      hval ^= (uint32_t)*s++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_NVS_LOOKUP_CACHE
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  size_t pos;
+
+#if CONFIG_NVS_LOOKUP_CACHE_SIZE <= UINT8_MAX
+  /* CRC8-CCITT is used for ATE checksums and it also acts well as a hash
+   * function, so it can be a good choice from the code size perspective.
+   * However, other hash functions can be used as well if proved better
+   * performance.
+   */
+
+  pos = crc8_ccitt(CRC8_CCITT_INITIAL_VALUE, &id, sizeof(id));
+#else
+  pos = crc16_ccitt(0xffff, (const uint8_t *)&id, sizeof(id));

Review Comment:
   ```suggestion
     pos = crc16_ccitt(0xffff, (FAR const uint8_t *)&id, sizeof(id));
   ```



##########
include/nuttx/mtd/nvs_priv.h:
##########
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * include/nuttx/mtd/nvs_priv.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+#define __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <assert.h>

Review Comment:
   ```suggestion
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] pkarashchenko commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
pkarashchenko commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1211630200

   > > In general I'm a bit lost in architecture here. The MTD layer is designed to provide unified "continuous" access across the underlying physical layer, so I expect that all alignment or any other hardware related stuff is hidden "under" MTD device, but here I observe an opposite when the MTD caller handle alignment and other stuff. I mean that if MTD is implemented on top of flash that is what I expect, but not implementing a flash on top of MTD what I see here. I will submit a negative feedback until all design points are not clarified
   > 
   > Basically, MTD like a block device(but need erase before write, wear leveling and ECC), which is hard to use from user application. To fix this problem, we normally have two approach:
   > 
   >     1. Build a complex filesystem on top of MTD to support a tree like structure(e.g. spiffs, smartfs and littlefs).
   > 
   >     2. Build a simple key value storage on top of MTD
   > 
   > 
   > NVS(and cfgdata) is the second approach. @pkarashchenko do you get the idea?
   
   @xiaoxiang781216 we already have KVS on top of MTD and that is mtdconfig. Could you please bring some more light on key differences between mtdconfig and NVS?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#issuecomment-1221038042

   Alright, I have fixed the bugs and passed zephyr test cases. Please review.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962647425


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   So, this block may contain the valid ate, why not stop search here?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962643230


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   Yes, if the block is open, then the last ate(which is close ate) only contains erase value.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962426325


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;

Review Comment:
   why remove the default case? I don't understand.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962697589


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)

Review Comment:
   if , else if without else?
   I can do that, but it seems strange, and may lead to some code checking tools complaining



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971466943


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* If gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->geo.neraseblocks - 1 -

Review Comment:
   The old entry will be moved after several gc. So in such case we cannot directly expire it (it is not there anymore), we have to search for the old one again.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971588707


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire it\n");
+
+              /* If gc touched second latest ate, search for it again */
+
+              if (gc_count >= fs->geo.neraseblocks - 1 -

Review Comment:
   Optimize the nvs_read_hist, remove cnt.
   Now the work flow will be gc, search for prev, write, expire prev. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] anjiahao1 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
anjiahao1 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r943305478


##########
include/nuttx/mtd/nvs_priv.h:
##########
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * include/nuttx/mtd/nvs_priv.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+#define __INCLUDE_NUTTX_MTD_NVS_PRIV_H_
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+#include <assert.h>
+#include <nuttx/compiler.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* MASKS AND SHIFT FOR ADDRESSES
+ * an address in nvs is an uint32_t where:
+ *   high 2 bytes represent the sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK           0xFFFF0000
+#define ADDR_SECT_SHIFT          16
+#define ADDR_OFFS_MASK           0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE       1
+
+#define NVS_BLOCK_SIZE           32
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param offset File system offset in flash
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  struct   mtd_dev_s *mtd;   /* mtd device */
+  off_t    offset;           /* filesystem offset in flash */
+  uint32_t ate_wra;          /* next alloc table entry write address */
+  uint32_t data_wra;         /* next data write address */
+  uint16_t sector_size;      /* filesystem is divided into sectors,
+                              * sector size should be multiple of pagesize
+                              */
+  uint16_t page_size;        /* page size */
+  uint16_t sector_count;     /* amount of sectors in the filesystem */
+  bool     ready;            /* is the filesystem initialized ? */
+
+  uint32_t step_addr;        /* for traverse */
+  sem_t    nvs_lock;

Review Comment:
   mutex_t ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r960366396


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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_SECT_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 sector *addr
+   * is kept at sector_end - 2*ate_size, the next read will contain
+   * invalid data and continue with a sector jump
+   */
+
+  return nvs_recover_last_ate(fs, addr);
+}
+
+/****************************************************************************
+ * Name: nvs_sector_advance
+ ****************************************************************************/
+
+static void nvs_sector_advance(FAR struct nvs_fs *fs, FAR uint32_t *addr)
+{
+  *addr += (1 << ADDR_SECT_SHIFT);
+  if ((*addr >> ADDR_SECT_SHIFT) == fs->geo.neraseblocks)
+    {
+      *addr -= (fs->geo.neraseblocks << ADDR_SECT_SHIFT);
+    }
+}
+
+/****************************************************************************
+ * Name: nvs_sector_close
+ *
+ * Description:
+ *   allocation entry close (this closes the current sector) by writing
+ *   offset of last ate to the sector end.
+ *
+ ****************************************************************************/
+
+static int nvs_sector_close(FAR struct nvs_fs *fs)
+{
+  int rc;
+  struct nvs_ate close_ate;
+
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0U;
+  close_ate.key_len = 0U;
+  close_ate.offset =
+    (uint16_t)((fs->ate_wra + sizeof(struct nvs_ate)) & ADDR_OFFS_MASK);

Review Comment:
   I will memset all to 0xff first



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r954958805


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2654 @@
+/****************************************************************************
+ * 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 <nuttx/crc8.h>
+#include <debug.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mtd/mtd.h>
+#include <nuttx/mtd/configdata.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define CONFIG_MTD_NVS_WRITE_BLOCK_SIZE 4
+#define NVS_WRITE_BLOCK_SIZE            CONFIG_MTD_NVS_WRITE_BLOCK_SIZE
+#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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_SHIFT                 16
+#define ADDR_OFFS_MASK                  0x0000FFFF
+
+/* Status return values */
+
+#define NVS_STATUS_NOSPACE              1
+
+#define NVS_BLOCK_SIZE                  32
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xFFFFFFFF
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/**
+ * @brief Non-volatile Storage File system structure
+ *
+ * @param ate_wra: Allocation table entry write address. Addresses are stored
+ * as uint32_t: high 2 bytes are sector, low 2 bytes are offset in sector,
+ * @param data_wra: Data write address.
+ * @param sector_size File system is divided into sectors each sector
+ * should be multiple of pagesize
+ * @param sector_count Amount of sectors in the file systems
+ * @param write_block_size Alignment size
+ * @param nvs_lock Mutex
+ * @param flash_device Flash Device
+ */
+
+struct nvs_fs
+{
+  FAR struct mtd_dev_s *mtd;          /* mtd device */
+  uint32_t             ate_wra;       /* next alloc table entry
+                                       * write address
+                                       */
+  uint32_t             data_wra;      /* next data write address */
+  uint16_t             sector_size;   /* filesystem is divided into
+                                       * sectors, sector size should be
+                                       * multiple of pagesize
+                                       */
+  uint16_t             page_size;     /* page size */
+  uint16_t             sector_count;  /* amount of sectors */
+  bool                 ready;         /* is the filesystem initialized */
+
+  uint32_t             step_addr;     /* for traverse */
+  mutex_t              nvs_lock;
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;         /* data id */
+  uint16_t offset;     /* data offset within sector */
+  uint16_t len;        /* data len within sector */
+  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;   /* for future extension */
+  uint16_t reserved2;  /* for future extension */
+} end_packed_struct;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* MTD NVS opeation api */
+
+static int     mtdnvs_open(FAR struct file *filep);
+static int     mtdnvs_close(FAR struct file *filep);
+static ssize_t mtdnvs_read(FAR struct file *filep, FAR char *buffer,
+                           size_t buflen);
+static int     mtdnvs_ioctl(FAR struct file *filep, int cmd,
+                            unsigned long arg);
+static int     mtdnvs_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                           bool setup);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations mtdnvs_fops =
+{
+  mtdnvs_open,  /* open */
+  mtdnvs_close, /* close */
+  mtdnvs_read,  /* read */
+  NULL,         /* write */
+  NULL,         /* seek */
+  mtdnvs_ioctl, /* ioctl */
+  mtdnvs_poll   /* poll */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+  , NULL        /* unlink */
+#endif
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: fnv_hash_32
+ ****************************************************************************/
+
+static uint32_t fnv_hash_32(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 ^= (uint32_t)*input++;
+    }
+
+  return hval;
+}
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+    sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_pref_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != 0xffffffff && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR &&
+          nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                                uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                                 FAR void *data, size_t len)
+{
+  int ret;
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret = -ENOTSUP;
+
+#ifdef CONFIG_MTD_BYTE_WRITE
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+  else
+    {
+      ret = OK;
+    }
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                    size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * Name: nvs_al_size
+ *
+ * Description:
+ *   nvs_al_size() returns size aligned to NVS_WRITE_BLOCK_SIZE.
+ *
+ * Parameter:
+ *   fs  - offset from 0 of internal flash
+ *   len - avaiable size for NVM
+ *
+ ****************************************************************************/
+
+static inline size_t nvs_al_size(FAR struct nvs_fs *fs, size_t len)
+{
+  uint8_t write_block_size = NVS_WRITE_BLOCK_SIZE;
+
+  if (write_block_size <= 1U)
+    {
+      return len;
+    }
+
+  return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
+}
+
+/****************************************************************************
+ * Name: nvs_flash_al_wrt
+ *
+ * Description:
+ *   flash routines, basic aligned flash write to nvs address.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_al_wrt(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 = 0;
+  off_t offset;
+  size_t blen;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  if (!len)
+    {
+      /* Nothing to write, avoid changing the flash protection */
+
+      return 0;
+    }
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  blen = len & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+  if (blen > 0)
+    {
+      rc = flash_write(fs->mtd, offset, data8, blen);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+
+      len -= blen;
+      offset += blen;
+      data8 += blen;
+    }
+
+  if (len)
+    {
+      memcpy(buf, data8, len);
+      (void)memset(buf + len, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                   NVS_WRITE_BLOCK_SIZE - len);
+
+      rc = flash_write(fs->mtd, offset, buf,
+                       NVS_WRITE_BLOCK_SIZE);
+      if (rc)
+        {
+          /* flash write error */
+
+          goto end;
+        }
+    }
+
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  int rc;
+  off_t offset;
+
+  offset = fs->sector_size * (addr >> ADDR_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  rc = flash_read(fs->mtd, offset, data, len);
+  return rc;
+}
+
+/****************************************************************************
+ * 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_al_wrt(fs, fs->ate_wra, entry, sizeof (struct nvs_ate));
+
+#ifdef CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != 0xffffffff)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  fs->ate_wra -= nvs_al_size(fs, 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_al_wrt(fs, fs->data_wra, data, len);
+  fs->data_wra += nvs_al_size(fs, len);
+  finfo("write data done, data_wra=0x%lx\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:
+ *   nvs_flash_block_cmp 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;
+  size_t block_size;
+  uint8_t buf[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_direct_cmp 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;
+  size_t block_size;
+  uint8_t buf1[NVS_BLOCK_SIZE];
+  uint8_t buf2[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  while (len)
+    {
+      bytes_to_cmp = MIN(block_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:
+ *   nvs_flash_cmp_const 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;
+  size_t block_size;
+  uint8_t cmp[NVS_BLOCK_SIZE];
+
+  block_size = NVS_BLOCK_SIZE & ~(NVS_WRITE_BLOCK_SIZE - 1U);
+
+  (void)memset(cmp, value, block_size);

Review Comment:
   remove it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 merged pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 merged PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971455118


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;

Review Comment:
   wlk_addr == fs->ate_wra doesn't mean  -ENOENT. 
   wlk_addr in line 1413 points to the next ate, not the current read one.
   1413 says we will reach the end after current read, current read can be matched, or mismatch
   Line 1421-1427 is for mismatch case.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971464472


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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;
+            }

Review Comment:
   The case is if we want to delete one , and prev one is already deleted, so nothing needs to be done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971466191


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;

Review Comment:
   If you want to resolve 0 for the future use, it's ok to keep + 1.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971486977


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  return nvs_write(fs, pdata);

Review Comment:
   But, mtdconfig_s doesn't use pdata->len in mtdconfig_deleteconfig, we need keep the consistent behavior, otherwise apps/system/cfgdata/ will stop work.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971503999


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  return nvs_write(fs, pdata);

Review Comment:
   But cfgdatacmd_unset has the following code:
     cfg.configdata = NULL;
     cfg.len = 0;
   So, len=0 is used in apps/system/cfgdata?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r971538600


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2126 @@
+/****************************************************************************
+ * 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* Already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+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_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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.
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+  wlk_addr = fs->ate_wra;
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* Skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != fs->erasestate) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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;
+  uint32_t second_latest_ate_addr;
+
+#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 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;
+        }
+    }
+
+  /* Calculate required space if the entry contains data. */
+
+  if (data_size)
+    {
+      /* Leave space for gc_done ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (pdata->len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* Comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate.
+       */
+
+      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;
+    }
+
+  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)
+        {
+          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");
+
+          /* Nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * 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)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* Set second latest ate id to all 0
+                       * so that only the latest ate is valid.
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          return rc;
+                        }
+                    }
+                }
+              else
+                {
+                  /* Set second latest ate id as old entry
+                   * so that only the latest ate is valid.
+                   */
+
+                  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)
+{
+  return nvs_write(fs, pdata);

Review Comment:
   if so, ok.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962684290


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))

Review Comment:
   After we update a KV, we need to expire the old ate.
   This is where we want to read cnt==1. 
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962689819


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))

Review Comment:
   But, the first match is old ate, why need search the next one?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962912334


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these

Review Comment:
   You are right, it never happend. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962430546


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))
+    {
+      /* Not find the entry id, return no this entry */
+
+      return -ENOENT;
+    }
+
+  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);
+          goto err;
+        }
+    }
+
+  if (ate_addr)
+    {
+      *ate_addr = hist_addr;
+    }
+
+  return wlk_ate.len;
+
+err:
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_write
+ *
+ * Description:
+ *   Write an entry to the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be written
+ *
+ * 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, size_t len)
+{
+  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;  /* no space, appropriate for delete ate */
+  bool prev_found = false;
+  uint32_t hash_id;
+  uint16_t block_to_write_befor_gc;
+  uint32_t second_latest_ate_addr;
+
+#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 + len;
+
+  /* The maximum data size is block size - 4 ate
+   * where: 1 ate for data, 1 ate for block close, 1 ate for gc done,
+   * and 1 ate to always allow a delete.
+   */
+
+  finfo("key_size=%zu, len=%zu\n", key_size, len);
+
+  if ((data_size > (fs->geo.erasesize - 4 * sizeof(struct nvs_ate))) ||
+      ((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;
+  rd_addr = wlk_addr;
+
+  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;
+
+      /* skip key, read data */
+
+      rd_addr += wlk_ate.offset + wlk_ate.key_len;
+
+      if (len == 0)
+        {
+          /* if prev ate is expired, it is deleted */
+
+          if (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE)
+            {
+              /* skip delete entry as it is already the
+               * last one
+               */
+
+              return 0;
+            }
+        }
+      else if (len == wlk_ate.len &&
+               wlk_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* do not try to compare if lengths are not equal
+           * or prev one is deleted.
+           * compare the data and if equal return 0
+           */
+
+          rc = nvs_flash_block_cmp(fs, rd_addr, pdata->configdata, len);
+          if (rc <= 0)
+            {
+              return rc;
+            }
+        }
+    }
+  else
+    {
+      /* skip delete entry for non-existing entry */
+
+      if (len == 0)
+        {
+          return 0;
+        }
+    }
+
+  /* calculate required space if the entry contains data */
+
+  if (data_size)
+    {
+      /* Leave space for delete ate */
+
+      required_space = data_size + sizeof(struct nvs_ate);
+    }
+
+  if (len == 0)
+    {
+      DEBUGASSERT(prev_found);
+
+      /* comes into this branch only when a prev ate is found
+       * and it is not a delete entry.
+       * So lets delete by expiring the prev ate
+       */
+
+      rc = nvs_expire_ate(fs, hist_addr);
+      if (rc < 0)
+        {
+          ferr("expire ate failed, addr %" PRIx32 "\n", hist_addr);
+          goto end;
+        }
+
+      /* delete now requires no extra space, so skip write and gc */
+
+      finfo("nvs_delete success\n");
+
+      goto end;
+    }
+
+  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.
+           */
+
+          rc = -ENOSPC;
+          goto end;
+        }
+
+      if (fs->ate_wra >= fs->data_wra + required_space)
+        {
+          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, len);
+          if (rc)
+            {
+              fwarn("Write entry failed\n");
+              goto end;
+            }
+
+          finfo("Write entry success\n");
+
+          /* nvs is changed after gc, we will look for the second latest ate
+           * and expire it.
+           * After this operation, only the latest ate is valid.
+           */
+
+          if (prev_found)
+            {
+              finfo("prev entry exists, expire 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_hist(fs, key, key_size, NULL, 0, 1,
+                                     &second_latest_ate_addr);
+                  finfo("search for prev entry, %" PRIx32 ", "
+                        "rc %d\n",
+                        second_latest_ate_addr, rc);
+                  if (rc > 0)
+                    {
+                      /* set second latest ate id to all 0
+                       * so that only the latest ate is valid
+                       */
+
+                      rc = nvs_expire_ate(fs, second_latest_ate_addr);
+                      if (rc < 0)
+                        {
+                          ferr("expire ate failed, addr %" PRIx32 "\n",
+                            second_latest_ate_addr);
+                          goto end;
+                        }
+                    }
+                }
+              else
+                {
+                  /* set second latest ate id as old entry.
+                   * so that only the latest ate is valid
+                   */
+
+                  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);
+                      goto end;
+                    }
+                }
+            }
+          break;
+        }
+
+      rc = nvs_block_close(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      rc = nvs_gc(fs);
+      if (rc)
+        {
+          goto end;
+        }
+
+      gc_count++;
+      finfo("Gc count=%d\n", gc_count);
+    }
+
+  finfo("nvs_write success\n");
+
+  rc = OK;
+end:
+  return rc;
+}
+
+/****************************************************************************
+ * 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)
+{
+  return nvs_write(fs, pdata, 0);
+}
+
+/****************************************************************************
+ * Name: nvs_read
+ *
+ * Description:
+ *   Read an entry from the file system.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Number of bytes to be read
+ *
+ * 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 len)
+{
+  int rc;
+  size_t key_size;
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  FAR const uint8_t *key;
+#else
+  uint8_t key[sizeof(pdata->id) + sizeof(pdata->instance)];
+#endif
+
+  if (pdata == NULL || 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
+
+  rc = nvs_read_hist(fs, key, key_size, pdata->configdata, len, 0, NULL);
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_first
+ *
+ * Description:
+ *   Get the newest KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * Returned Value:
+ *   0 on success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_first(FAR struct nvs_fs *fs,
+                     FAR struct config_data_s *pdata, FAR size_t *len)
+{
+  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
+
+  fs->step_addr = fs->ate_wra;
+
+  do
+    {
+      rd_addr = fs->step_addr;
+      rc = nvs_prev_ate(fs, &(fs->step_addr), &step_ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      if (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *len = step_ate.len;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nvs_next
+ *
+ * Description:
+ *   Get the next KV in database.
+ *
+ * Input Parameters:
+ *   fs    - Pointer to file system
+ *   pdata - Pointer to data buffer
+ *   len   - Return number of bytes read
+ *
+ * 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, FAR size_t *len)
+{
+  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 (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 (step_ate.len && (nvs_ate_valid(fs, &step_ate))
+          && step_ate.id != NVS_SPECIAL_ATE_ID
+          && step_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          break;
+        }
+    }
+  while (fs->step_addr != fs->ate_wra);
+
+  if (fs->step_addr == fs->ate_wra)
+    {
+      return -ENOENT;
+    }
+
+#ifdef CONFIG_MTD_CONFIG_NAMED
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    key, step_ate.key_len);
+  if (rc)
+    {
+      ferr("Key read failed, rc=%d\n", rc);
+      return rc;
+    }
+#else
+  rc = nvs_flash_rd(fs, (rd_addr & ADDR_BLOCK_MASK) + step_ate.offset,
+                    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, step_ate.len);
+  if (rc)
+    {
+      ferr("Value read failed, rc=%d\n", rc);
+      return rc;
+    }
+
+  *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;
+
+  switch (cmd)
+    {
+      case CFGDIOC_GETCONFIG:
+
+        /* Read a nvs item */
+
+        ret = nxmutex_lock(&fs->nvs_lock);
+        if (ret < 0)
+          {
+            return ret;
+          }
+
+        ret = nvs_read(fs, pdata, pdata->len);

Review Comment:
   original NVS has the param len.
   I keep it this way so that NVS implementation won't need changing. 
   And I think it is okay to leave NVS code unchanged, it is better if NVS has some upgrades we need to merge. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] xiaoxiang781216 commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
xiaoxiang781216 commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r966183535


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)

Review Comment:
   why not fill the cache on demand to improve the startup speed?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))

Review Comment:
   should we check expired here



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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)))

Review Comment:
   should we check expired



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2484 @@
+/****************************************************************************
+ * 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 sector number
+ *   low 2 bytes represent the offset in a sector
+ */
+
+#define ADDR_SECT_MASK                  0xFFFF0000
+#define ADDR_SECT_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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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 */
+  uint32_t              ate_wra;       /* next alloc table entry
+                                        * write address
+                                        */
+  uint32_t              data_wra;      /* next data write address */
+  struct mtd_geometry_s geo;
+  uint32_t              step_addr;     /* for traverse */
+  mutex_t               nvs_lock;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* Allocation Table Entry */
+
+begin_packed_struct struct nvs_ate
+{
+  uint32_t id;           /* data id */
+  uint16_t offset;       /* data offset within sector */
+  uint16_t len;          /* data len within sector */
+  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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t sector)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_SECT_SHIFT) == sector)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  return MTD_READ(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  return MTD_WRITE(mtd, offset, len, data);
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  return MTD_ERASE(mtd, startblock, nblocks);
+}
+
+/****************************************************************************
+ * 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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_write(fs->mtd, offset, data, len);
+  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_SECT_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  ret = flash_read(fs->mtd, offset, data, len);
+  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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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%lx\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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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_sector
+ *
+ * Description:
+ *   erase a sector by first checking it is used and then erasing if required
+ *   return 0 if OK, errorcode on error.
+ *
+ ****************************************************************************/
+
+static int nvs_flash_erase_sector(FAR struct nvs_fs *fs, uint32_t addr)
+{
+  int rc;
+
+  ferr("Erasing addr %lx\n", addr);
+  addr &= ADDR_SECT_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_SECT_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+  else
+    {
+      rc = OK;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an sector close ate:
+ *   a valid sector close ate:
+ *   - valid ate
+ *   - len = 0 and id = 0xFFFFFFFF
+ *   - offset points to location at ate multiple from sector 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 != 0U) ||
+      (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;
+
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)key_size;
+  entry.part = 0xff;
+
+  nvs_ate_crc8_update(&entry);
+
+  entry.expired = 0xff;
+  entry.reserved[0] = 0xff;
+  entry.reserved[1] = 0xff;
+  entry.reserved[2] = 0xff;
+
+  /* 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 sector 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 sector %" PRIu32 "\n",
+        (*addr >> ADDR_SECT_SHIFT));
+
+  *addr -= sizeof(struct nvs_ate);
+  ate_end_addr = *addr;
+  data_end_addr = *addr & ADDR_SECT_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_SECT_MASK;
+          data_end_addr += end_ate.offset + end_ate.len + end_ate.key_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 sector, do jump to previous sector */
+
+  if (((*addr) >> ADDR_SECT_SHIFT) == 0U)
+    {
+      *addr += ((fs->geo.neraseblocks - 1) << ADDR_SECT_SHIFT);
+    }
+  else
+    {
+      *addr -= (1 << ADDR_SECT_SHIFT);
+    }
+
+  rc = nvs_flash_ate_rd(fs, *addr, &close_ate);
+  if (rc)
+    {
+      return rc;
+    }
+
+  rc = nvs_ate_cmp_const(&close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* at the end of filesystem */

Review Comment:
   But if we write some ate but not all in a block, the last ate is all 0xff. In this case should we continue to find the valid ate?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,

Review Comment:
   let's guard by some debug macro. BTW, do you need check write too?



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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;

Review Comment:
   remove () around *addr



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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;
+
+  expired = (uint8_t)~CONFIG_MTD_CONFIG_ERASEDVALUE;

Review Comment:
   I still think it's simpler to clear id field than expired, because you have to check expired in many places.



##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2262 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+  int i;
+
+  for (i < 0; i < CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE; i++)
+    {
+      fs->lookup_cache[i] = NVS_LOOKUP_CACHE_NO_ADDR;
+    }
+
+  addr = fs->ate_wra;
+
+  do
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+    }
+  while (addr != fs->ate_wra)
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+
+  rc = MTD_ERASE(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  rc = OK;
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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;
+
+  expired = (uint8_t)~CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  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;
+    }
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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),
+                               erase_value, 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, erase_value);
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              return -ESPIPE;
+            }
+        }
+
+      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, erase_value, 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, erase_value, 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;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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 == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);

Review Comment:
   this will slow the startup time



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962673194


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))

Review Comment:
   Useless logic copied from Zephyr, I will remove it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [incubator-nuttx] XinStellaris commented on a diff in pull request #6829: drivers/mtd:init commit of power-loss resilient cfg

Posted by GitBox <gi...@apache.org>.
XinStellaris commented on code in PR #6829:
URL: https://github.com/apache/incubator-nuttx/pull/6829#discussion_r962876470


##########
drivers/mtd/mtd_config_fs.c:
##########
@@ -0,0 +1,2494 @@
+/****************************************************************************
+ * 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
+
+#define NVS_LOOKUP_CACHE_NO_ADDR        0xffffffff
+
+/* 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;
+  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;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  uint32_t lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+#endif
+};
+
+/* 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);
+
+/* Basic flash operation api */
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len);
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len);
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t offset, size_t len);
+
+/* NVS operations needed by lookup cache */
+
+static int nvs_prev_ate(FAR struct nvs_fs *fs, FAR uint32_t *addr,
+                        FAR struct nvs_ate *ate);
+static int nvs_ate_valid(FAR struct nvs_fs *fs,
+                         FAR const struct nvs_ate *entry);
+
+/****************************************************************************
+ * 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;
+}
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_pos
+ ****************************************************************************/
+
+static inline size_t nvs_lookup_cache_pos(uint32_t id)
+{
+  return id % CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_rebuild
+ ****************************************************************************/
+
+static int nvs_lookup_cache_rebuild(FAR struct nvs_fs *fs)
+{
+  int rc;
+  uint32_t addr;
+  uint32_t ate_addr;
+  FAR uint32_t *cache_entry;
+  struct nvs_ate ate;
+
+  memset(fs->lookup_cache, CONFIG_MTD_CONFIG_ERASEDVALUE,
+         sizeof(fs->lookup_cache));
+  addr = fs->ate_wra;
+
+  while (true)
+    {
+      /* Make a copy of 'addr' as it will be advanced by nvs_prev_ate() */
+
+      ate_addr = addr;
+      rc = nvs_prev_ate(fs, &addr, &ate);
+
+      if (rc)
+        {
+          return rc;
+        }
+
+      cache_entry = &fs->lookup_cache[nvs_lookup_cache_pos(ate.id)];
+
+      if (ate.id != NVS_SPECIAL_ATE_ID
+          && *cache_entry == NVS_LOOKUP_CACHE_NO_ADDR
+          && nvs_ate_valid(fs, &ate))
+        {
+          *cache_entry = ate_addr;
+        }
+
+      if (addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: nvs_lookup_cache_invalidate
+ ****************************************************************************/
+
+static void nvs_lookup_cache_invalidate(FAR struct nvs_fs *fs,
+                                        uint32_t block)
+{
+  FAR uint32_t *cache_entry = fs->lookup_cache;
+  FAR uint32_t *const cache_end =
+    &fs->lookup_cache[CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE];
+
+  for (; cache_entry < cache_end; ++cache_entry)
+    {
+      if ((*cache_entry >> ADDR_BLOCK_SHIFT) == block)
+        {
+          *cache_entry = NVS_LOOKUP_CACHE_NO_ADDR;
+        }
+    }
+}
+
+#endif /* CONFIG_MTD_NVS_LOOKUP_CACHE */
+
+/****************************************************************************
+ * Name: flash_read
+ ****************************************************************************/
+
+static ssize_t flash_read(FAR struct mtd_dev_s *mtd, off_t offset,
+                          FAR void *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_READ(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_write
+ ****************************************************************************/
+
+static ssize_t flash_write(FAR struct mtd_dev_s *mtd, off_t offset,
+                           FAR const uint8_t *data, size_t len)
+{
+  int ret;
+
+  ret = MTD_WRITE(mtd, offset, len, data);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: flash_erase
+ ****************************************************************************/
+
+static int flash_erase(FAR struct mtd_dev_s *mtd, off_t startblock,
+                       size_t nblocks)
+{
+  int ret;
+
+  ret = MTD_ERASE(mtd, startblock, nblocks);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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;
+
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_read(fs->mtd, offset, data, len);
+}
+
+/****************************************************************************
+ * 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));
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  /* 0xffffffff is a special-purpose identifier. Exclude it from the cache */
+
+  if (entry->id != NVS_SPECIAL_ATE_ID)
+    {
+      fs->lookup_cache[nvs_lookup_cache_pos(entry->id)] = fs->ate_wra;
+    }
+#endif
+
+  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:
+ *   nvs_flash_block_cmp 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:
+ *   nvs_flash_direct_cmp 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:
+ *   nvs_flash_cmp_const 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:
+ *   flash block move: 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;
+
+  ferr("Erasing addr %" PRIx32 "\n", addr);
+  addr &= ADDR_BLOCK_MASK;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  nvs_lookup_cache_invalidate(fs, addr >> ADDR_BLOCK_SHIFT);
+#endif
+  rc = flash_erase(fs->mtd, addr >> ADDR_BLOCK_SHIFT, 1);
+
+  if (rc < 0)
+    {
+      ferr("Erasing failed %d\n", rc);
+      return rc;
+    }
+
+  if (nvs_flash_cmp_const(fs, addr, CONFIG_MTD_CONFIG_ERASEDVALUE,
+                          fs->geo.erasesize))
+    {
+      ferr("Erasing not complete\n");
+      rc = -ENXIO;
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * 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:
+ *   nvs_ate_cmp_const 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:
+ *   nvs_close_ate_valid validates an block close ate:
+ *   a valid block close ate:
+ *   - valid 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(entry));
+  entry.id = id;
+  entry.offset = (uint16_t)(fs->data_wra & ADDR_OFFS_MASK);
+  entry.len = (uint16_t)len;
+  entry.key_len = (uint16_t)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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+
+  /* 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, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(close_ate));
+  close_ate.id = NVS_SPECIAL_ATE_ID;
+  close_ate.len = 0;
+  close_ate.key_len = 0;
+  close_ate.offset =
+    (uint16_t)((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, CONFIG_MTD_CONFIG_ERASEDVALUE, 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 = (uint16_t)(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;
+  off_t offset;
+
+  expired = CONFIG_MTD_CONFIG_ERASEDVALUE;
+  expired = ~expired;
+  offset = fs->geo.erasesize * (addr >> ADDR_BLOCK_SHIFT);
+  offset += addr & ADDR_OFFS_MASK;
+
+  return flash_write(fs->mtd, offset + 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, CONFIG_MTD_CONFIG_ERASEDVALUE);
+  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 != CONFIG_MTD_CONFIG_ERASEDVALUE)
+        {
+          /* 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 = (uint16_t)(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:
+
+  /* Make it possible to detect that gc has finished by writing a
+   * gc done ate to the block. In the field we might have nvs systems
+   * that do not have sufficient space to add this ate, so for these
+   * situations avoid adding the gc done ate.
+   */
+
+  if (fs->ate_wra >= (fs->data_wra + sizeof(struct nvs_ate)))
+    {
+      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 as this is verified in nvs_init() but both
+   * Coverity and GCC believe the contrary.
+   */
+
+  uint32_t addr = 0;
+  uint16_t i;
+  uint16_t closed_blocks = 0;
+  uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE;
+
+  /* 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, erase_value,
+                               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, erase_value,
+                                   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)
+    {
+      rc = -EDEADLK;
+      goto end;
+    }
+
+  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),
+                               erase_value, 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)
+    {
+      goto end;
+    }
+
+  /* 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)
+        {
+          goto end;
+        }
+
+      rc = nvs_ate_cmp_const(&last_ate, erase_value);
+
+      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);
+
+          /* Ate on the last position within the block is
+           * reserved for deletion an entry
+           */
+
+          if (fs->ate_wra == fs->data_wra && last_ate.len)
+            {
+              /* not a delete ate */
+
+              rc = -ESPIPE;
+              goto end;
+            }
+        }
+
+      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, erase_value, fs->geo.erasesize);
+  if (rc < 0)
+    {
+      goto end;
+    }
+
+  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)
+            {
+              goto end;
+            }
+
+          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)
+        {
+          goto end;
+        }
+
+      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, erase_value, empty_len);
+      if (rc < 0)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      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)
+        {
+          goto end;
+        }
+
+      if (nvs_ate_valid(fs, &last_ate)
+          && (last_ate.id != NVS_SPECIAL_ATE_ID))
+        {
+          /* already last one, skip */
+
+          if (wlk_addr == fs->ate_wra)
+            {
+              break;
+            }
+
+          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)
+                {
+                  goto end;
+                }
+
+              if (nvs_ate_valid(fs, &second_ate)
+                  && second_ate.id == last_ate.id
+                  && second_ate.expired == CONFIG_MTD_CONFIG_ERASEDVALUE)
+                {
+                  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);
+                          goto end;
+                        }
+                      break;
+                    }
+                  else
+                    {
+                      fwarn("hash conflict\n");
+                    }
+                }
+
+              if (wlk_addr == fs->ate_wra)
+                {
+                  break;
+                }
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  rc = nvs_lookup_cache_rebuild(fs);
+#endif
+
+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);
+    }
+
+  return rc;
+}
+
+/****************************************************************************
+ * Name: nvs_init
+ *
+ * Description:
+ *   Initializes a NVS file system in flash.
+ *
+ * Input Parameters:
+ *   fs - Pointer to file system.
+ *
+ * Returned Value:
+ *   0 Success, -ERRNO errno code if error.
+ *
+ ****************************************************************************/
+
+static int nvs_init(FAR struct nvs_fs *fs)
+{
+  int rc;
+
+  nxmutex_init(&fs->nvs_lock);
+
+  /* check that block size is a multiple of pagesize */
+
+  if (!fs->geo.erasesize || fs->geo.erasesize % fs->geo.blocksize)
+    {
+      ferr("Invalid block size\n");
+      return -EINVAL;
+    }
+
+  /* check the number of blocks, it should be at least 2 */
+
+  if (fs->geo.neraseblocks < 2)
+    {
+      ferr("Configuration error - block count\n");
+      return -EINVAL;
+    }
+
+  rc = nvs_startup(fs);
+  if (rc)
+    {
+      return rc;
+    }
+
+  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 0;
+}
+
+/****************************************************************************
+ * Name: nvs_read_hist
+ *
+ * Description:
+ *   Read a history entry from the file system. But expired ones will return
+ *   -ENOENT.
+ *   Only when we are updating an existing kv with new data and
+ *   right before the prev one hasn't been expired, will there
+ *   be two newest entries. At that time, nvs_read_hist is used to search
+ *   for prev one.
+ *
+ * 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
+ *   cnt      - History counter: 0: latest entry, 1:one before latest ...
+ *   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_hist(FAR struct nvs_fs *fs, FAR const uint8_t *key,
+                size_t key_size, FAR void *data, size_t len, uint16_t cnt,
+                FAR uint32_t *ate_addr)
+{
+  int rc;
+  uint32_t wlk_addr;
+  uint32_t rd_addr;
+  uint32_t hist_addr;
+  uint16_t cnt_his;
+  struct nvs_ate wlk_ate;
+  uint32_t hash_id;
+
+  if (len > (fs->geo.erasesize - 2 * sizeof(struct nvs_ate)))
+    {
+      ferr("Read buffer is too large\n");
+      return -EINVAL;
+    }
+
+  cnt_his = 0;
+
+  hash_id = nvs_fnv_hash(key, key_size) % 0xfffffffd + 1;
+
+#if CONFIG_MTD_CONFIG_FAIL_SAFE_LOOKUP_CACHE_SIZE > 0
+  wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(hash_id)];
+  if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR)
+    {
+      rc = -ENOENT;
+      goto err;
+    }
+
+#else
+  wlk_addr = fs->ate_wra;
+#endif
+  rd_addr = wlk_addr;
+
+  while (cnt_his <= cnt)
+    {
+      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);
+          goto err;
+        }
+
+      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)))
+            {
+              cnt_his++;
+            }
+          else
+            {
+              fwarn("hash conflict\n");
+            }
+        }
+
+      if (wlk_addr == fs->ate_wra)
+        {
+          break;
+        }
+    }
+
+  /* skip the deleted or expired entry, or gc_done_ate(id mustn't match) */
+
+  if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != hash_id)) ||
+      (wlk_ate.expired != CONFIG_MTD_CONFIG_ERASEDVALUE) ||  (cnt_his < cnt))

Review Comment:
   the first match is the new ate in this case. 
   Because we just finished writing the new ate, but expiring is not done. Power lose happens before expiring.
   Next time we bootup nvs, there are two new ate in NVS. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org