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-unsubscr...@nuttx.apache.org

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

Reply via email to