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-unsubscr...@nuttx.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org