When nvs_write is called, the nvs_flash_block_cmp is used to check if the new data to be written matches the data already on flash. This check always fail when CONFIG_NVS_DATA_CRC is enabled, caused by the NVS_DATA_CRC_SIZE being added to the len parameter. The pointer to the new data does not already have the CRC part added, while the data on flash does, and the size to be compared includes CRC section. By removing the addition of NVS_DATA_CRC_SIZE to the compare size, only the data without CRC is compared, which will make the compare work in both cases. Signed-off-by: Yonas Alizadeh <yonas.alizadeh@alfalaval.com>
1406 lines
32 KiB
C
1406 lines
32 KiB
C
/* NVS: non volatile storage in flash
|
|
*
|
|
* Copyright (c) 2018 Laczen
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/flash.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <zephyr/fs/nvs.h>
|
|
#include <zephyr/sys/crc.h>
|
|
#include "nvs_priv.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(fs_nvs, CONFIG_NVS_LOG_LEVEL);
|
|
|
|
static int nvs_prev_ate(struct nvs_fs *fs, uint32_t *addr, struct nvs_ate *ate);
|
|
static int nvs_ate_valid(struct nvs_fs *fs, const struct nvs_ate *entry);
|
|
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
|
|
static inline size_t nvs_lookup_cache_pos(uint16_t id)
|
|
{
|
|
uint16_t hash;
|
|
|
|
/* 16-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
|
|
hash = id;
|
|
hash ^= hash >> 8;
|
|
hash *= 0x88b5U;
|
|
hash ^= hash >> 7;
|
|
hash *= 0xdb2dU;
|
|
hash ^= hash >> 9;
|
|
|
|
return hash % CONFIG_NVS_LOOKUP_CACHE_SIZE;
|
|
}
|
|
|
|
static int nvs_lookup_cache_rebuild(struct nvs_fs *fs)
|
|
{
|
|
int rc;
|
|
uint32_t addr, 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 != 0xFFFF && *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)
|
|
{
|
|
uint32_t *cache_entry = fs->lookup_cache;
|
|
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_NVS_LOOKUP_CACHE */
|
|
|
|
/* basic routines */
|
|
/* nvs_al_size returns size aligned to fs->write_block_size */
|
|
static inline size_t nvs_al_size(struct nvs_fs *fs, size_t len)
|
|
{
|
|
size_t write_block_size = fs->flash_parameters->write_block_size;
|
|
|
|
if (write_block_size <= 1U) {
|
|
return len;
|
|
}
|
|
return (len + (write_block_size - 1U)) & ~(write_block_size - 1U);
|
|
}
|
|
/* end basic routines */
|
|
|
|
/* flash routines */
|
|
/* basic aligned flash write to nvs address */
|
|
static int nvs_flash_al_wrt(struct nvs_fs *fs, uint32_t addr, const void *data,
|
|
size_t len)
|
|
{
|
|
const uint8_t *data8 = (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->offset;
|
|
offset += fs->sector_size * (addr >> ADDR_SECT_SHIFT);
|
|
offset += addr & ADDR_OFFS_MASK;
|
|
|
|
blen = len & ~(fs->flash_parameters->write_block_size - 1U);
|
|
if (blen > 0) {
|
|
rc = flash_write(fs->flash_device, 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, fs->flash_parameters->erase_value,
|
|
fs->flash_parameters->write_block_size - len);
|
|
|
|
rc = flash_write(fs->flash_device, offset, buf,
|
|
fs->flash_parameters->write_block_size);
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
/* basic flash read from nvs address */
|
|
static int nvs_flash_rd(struct nvs_fs *fs, uint32_t addr, void *data,
|
|
size_t len)
|
|
{
|
|
int rc;
|
|
off_t offset;
|
|
|
|
offset = fs->offset;
|
|
offset += fs->sector_size * (addr >> ADDR_SECT_SHIFT);
|
|
offset += addr & ADDR_OFFS_MASK;
|
|
|
|
rc = flash_read(fs->flash_device, offset, data, len);
|
|
return rc;
|
|
}
|
|
|
|
/* allocation entry write */
|
|
static int nvs_flash_ate_wrt(struct nvs_fs *fs, const struct nvs_ate *entry)
|
|
{
|
|
int rc;
|
|
|
|
rc = nvs_flash_al_wrt(fs, fs->ate_wra, entry,
|
|
sizeof(struct nvs_ate));
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
/* 0xFFFF is a special-purpose identifier. Exclude it from the cache */
|
|
if (entry->id != 0xFFFF) {
|
|
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(struct nvs_fs *fs, const void *data, size_t len, bool compute_crc)
|
|
{
|
|
int rc;
|
|
|
|
/* Only add the CRC if required (ignore deletion requests, i.e. when len is 0) */
|
|
if (IS_ENABLED(CONFIG_NVS_DATA_CRC) && compute_crc && (len > 0)) {
|
|
size_t aligned_len, data_len = len;
|
|
uint8_t *data8 = (uint8_t *)data, buf[NVS_BLOCK_SIZE + NVS_DATA_CRC_SIZE], *pbuf;
|
|
uint32_t data_crc;
|
|
|
|
/* Write as much aligned data as possible, so the CRC can be concatenated at
|
|
* the end of the unaligned data later
|
|
*/
|
|
aligned_len = len & ~(fs->flash_parameters->write_block_size - 1U);
|
|
rc = nvs_flash_al_wrt(fs, fs->data_wra, data8, aligned_len);
|
|
fs->data_wra += aligned_len;
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
data8 += aligned_len;
|
|
len -= aligned_len;
|
|
|
|
/* Create a buffer with the unaligned data if any */
|
|
pbuf = buf;
|
|
if (len) {
|
|
memcpy(pbuf, data8, len);
|
|
pbuf += len;
|
|
}
|
|
|
|
/* Append the CRC */
|
|
data_crc = crc32_ieee(data, data_len);
|
|
memcpy(pbuf, &data_crc, sizeof(data_crc));
|
|
len += sizeof(data_crc);
|
|
|
|
rc = nvs_flash_al_wrt(fs, fs->data_wra, buf, len);
|
|
} else {
|
|
rc = nvs_flash_al_wrt(fs, fs->data_wra, data, len);
|
|
}
|
|
fs->data_wra += nvs_al_size(fs, len);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* flash ate read */
|
|
static int nvs_flash_ate_rd(struct nvs_fs *fs, uint32_t addr,
|
|
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(struct nvs_fs *fs, uint32_t addr, const void *data,
|
|
size_t len)
|
|
{
|
|
const uint8_t *data8 = (const uint8_t *)data;
|
|
int rc;
|
|
size_t bytes_to_cmp, block_size;
|
|
uint8_t buf[NVS_BLOCK_SIZE];
|
|
|
|
block_size =
|
|
NVS_BLOCK_SIZE & ~(fs->flash_parameters->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_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(struct nvs_fs *fs, uint32_t addr, uint8_t value,
|
|
size_t len)
|
|
{
|
|
int rc;
|
|
size_t bytes_to_cmp, block_size;
|
|
uint8_t buf[NVS_BLOCK_SIZE];
|
|
|
|
block_size =
|
|
NVS_BLOCK_SIZE & ~(fs->flash_parameters->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;
|
|
}
|
|
|
|
for (size_t i = 0; i < bytes_to_cmp; i++) {
|
|
if (buf[i] != value) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
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(struct nvs_fs *fs, uint32_t addr, size_t len)
|
|
{
|
|
int rc;
|
|
size_t bytes_to_copy, block_size;
|
|
uint8_t buf[NVS_BLOCK_SIZE];
|
|
|
|
block_size =
|
|
NVS_BLOCK_SIZE & ~(fs->flash_parameters->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;
|
|
}
|
|
/* Just rewrite the whole record, no need to recompute the CRC as the data
|
|
* did not change
|
|
*/
|
|
rc = nvs_flash_data_wrt(fs, buf, bytes_to_copy, false);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
len -= bytes_to_copy;
|
|
addr += bytes_to_copy;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* erase a sector and verify erase was OK.
|
|
* return 0 if OK, errorcode on error.
|
|
*/
|
|
static int nvs_flash_erase_sector(struct nvs_fs *fs, uint32_t addr)
|
|
{
|
|
int rc;
|
|
off_t offset;
|
|
|
|
addr &= ADDR_SECT_MASK;
|
|
|
|
offset = fs->offset;
|
|
offset += fs->sector_size * (addr >> ADDR_SECT_SHIFT);
|
|
|
|
LOG_DBG("Erasing flash at %lx, len %d", (long int) offset,
|
|
fs->sector_size);
|
|
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
nvs_lookup_cache_invalidate(fs, addr >> ADDR_SECT_SHIFT);
|
|
#endif
|
|
rc = flash_flatten(fs->flash_device, offset, fs->sector_size);
|
|
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
if (nvs_flash_cmp_const(fs, addr, fs->flash_parameters->erase_value,
|
|
fs->sector_size)) {
|
|
rc = -ENXIO;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* crc update on allocation entry */
|
|
static void nvs_ate_crc8_update(struct nvs_ate *entry)
|
|
{
|
|
uint8_t crc8;
|
|
|
|
crc8 = crc8_ccitt(0xff, entry, offsetof(struct nvs_ate, crc8));
|
|
entry->crc8 = crc8;
|
|
}
|
|
|
|
/* crc check on allocation entry
|
|
* returns 0 if OK, 1 on crc fail
|
|
*/
|
|
static int nvs_ate_crc8_check(const struct nvs_ate *entry)
|
|
{
|
|
uint8_t crc8;
|
|
|
|
crc8 = crc8_ccitt(0xff, entry, offsetof(struct nvs_ate, crc8));
|
|
if (crc8 == 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(const struct nvs_ate *entry, uint8_t value)
|
|
{
|
|
const uint8_t *data8 = (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, offset and length are valid,
|
|
* 0 otherwise
|
|
*/
|
|
static int nvs_ate_valid(struct nvs_fs *fs, const struct nvs_ate *entry)
|
|
{
|
|
size_t ate_size;
|
|
uint32_t position;
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
position = entry->offset + entry->len;
|
|
|
|
if ((nvs_ate_crc8_check(entry)) ||
|
|
(position >= (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 = 0xFFFF
|
|
* - offset points to location at ate multiple from sector size
|
|
* return 1 if valid, 0 otherwise
|
|
*/
|
|
static int nvs_close_ate_valid(struct nvs_fs *fs, const struct nvs_ate *entry)
|
|
{
|
|
size_t ate_size;
|
|
|
|
if ((!nvs_ate_valid(fs, entry)) || (entry->len != 0U) ||
|
|
(entry->id != 0xFFFF)) {
|
|
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(struct nvs_fs *fs, uint16_t id, 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.part = 0xff;
|
|
|
|
rc = nvs_flash_data_wrt(fs, data, len, true);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
#ifdef CONFIG_NVS_DATA_CRC
|
|
/* No CRC has been added if this is a deletion write request */
|
|
if (len > 0) {
|
|
entry.len += NVS_DATA_CRC_SIZE;
|
|
}
|
|
#endif
|
|
nvs_ate_crc8_update(&entry);
|
|
|
|
rc = nvs_flash_ate_wrt(fs, &entry);
|
|
|
|
return rc;
|
|
}
|
|
/* 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(struct nvs_fs *fs, uint32_t *addr)
|
|
{
|
|
uint32_t data_end_addr, ate_end_addr;
|
|
struct nvs_ate end_ate;
|
|
size_t ate_size;
|
|
int rc;
|
|
|
|
LOG_DBG("Recovering last ate from sector %d",
|
|
(*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;
|
|
*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(struct nvs_fs *fs, uint32_t *addr, 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, fs->flash_parameters->erase_value);
|
|
/* 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(struct nvs_fs *fs, 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(struct nvs_fs *fs)
|
|
{
|
|
struct nvs_ate close_ate;
|
|
size_t ate_size;
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
|
|
close_ate.id = 0xFFFF;
|
|
close_ate.len = 0U;
|
|
close_ate.offset = (uint16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK);
|
|
close_ate.part = 0xff;
|
|
|
|
fs->ate_wra &= ADDR_SECT_MASK;
|
|
fs->ate_wra += (fs->sector_size - ate_size);
|
|
|
|
nvs_ate_crc8_update(&close_ate);
|
|
|
|
(void)nvs_flash_ate_wrt(fs, &close_ate);
|
|
|
|
nvs_sector_advance(fs, &fs->ate_wra);
|
|
|
|
fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvs_add_gc_done_ate(struct nvs_fs *fs)
|
|
{
|
|
struct nvs_ate gc_done_ate;
|
|
|
|
LOG_DBG("Adding gc done ate at %x", fs->ate_wra & ADDR_OFFS_MASK);
|
|
gc_done_ate.id = 0xffff;
|
|
gc_done_ate.len = 0U;
|
|
gc_done_ate.part = 0xff;
|
|
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);
|
|
}
|
|
|
|
/* 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(struct nvs_fs *fs)
|
|
{
|
|
int rc;
|
|
struct nvs_ate close_ate, gc_ate, wlk_ate;
|
|
uint32_t sec_addr, gc_addr, gc_prev_addr, wlk_addr, wlk_prev_addr,
|
|
data_addr, stop_addr;
|
|
size_t ate_size;
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
|
|
sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
|
|
nvs_sector_advance(fs, &sec_addr);
|
|
gc_addr = sec_addr + fs->sector_size - ate_size;
|
|
|
|
/* 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, fs->flash_parameters->erase_value);
|
|
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 (!nvs_ate_valid(fs, &gc_ate)) {
|
|
continue;
|
|
}
|
|
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(gc_ate.id)];
|
|
|
|
if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR) {
|
|
wlk_addr = fs->ate_wra;
|
|
}
|
|
#else
|
|
wlk_addr = fs->ate_wra;
|
|
#endif
|
|
do {
|
|
wlk_prev_addr = wlk_addr;
|
|
rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
/* if ate with same id is reached we might need to copy.
|
|
* only consider valid wlk_ate's. Something wrong might
|
|
* have been written that has the same ate but is
|
|
* invalid, don't consider these as a match.
|
|
*/
|
|
if ((wlk_ate.id == gc_ate.id) &&
|
|
(nvs_ate_valid(fs, &wlk_ate))) {
|
|
break;
|
|
}
|
|
} while (wlk_addr != fs->ate_wra);
|
|
|
|
/* if walk has reached the same address as gc_addr copy is
|
|
* needed unless it is a deleted item.
|
|
*/
|
|
if ((wlk_prev_addr == gc_prev_addr) && gc_ate.len) {
|
|
/* copy needed */
|
|
LOG_DBG("Moving %d, len %d", gc_ate.id, 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.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);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int nvs_startup(struct nvs_fs *fs)
|
|
{
|
|
int rc;
|
|
struct nvs_ate last_ate;
|
|
size_t ate_size, empty_len;
|
|
/* Initialize addr to 0 for the case fs->sector_count == 0. This
|
|
* should never happen as this is verified in nvs_mount() but both
|
|
* Coverity and GCC believe the contrary.
|
|
*/
|
|
uint32_t addr = 0U;
|
|
uint16_t i, closed_sectors = 0;
|
|
uint8_t erase_value = fs->flash_parameters->erase_value;
|
|
|
|
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
|
|
|
|
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));
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
/* all sectors are closed, this is not a nvs fs or irreparably corrupted */
|
|
if (closed_sectors == fs->sector_count) {
|
|
#ifdef CONFIG_NVS_INIT_BAD_MEMORY_REGION
|
|
LOG_WRN("All sectors closed, erasing all sectors...");
|
|
rc = flash_flatten(fs->flash_device, fs->offset,
|
|
fs->sector_size * fs->sector_count);
|
|
if (rc) {
|
|
goto end;
|
|
}
|
|
|
|
i = fs->sector_count;
|
|
addr = ((fs->sector_count - 1) << ADDR_SECT_SHIFT) +
|
|
(uint16_t)(fs->sector_size - ate_size);
|
|
#else
|
|
rc = -EDEADLK;
|
|
goto end;
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
|
|
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 ff 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.len);
|
|
|
|
/* 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 == 0xffff) &&
|
|
(gc_done_ate.len == 0U)) {
|
|
gc_done_marker = true;
|
|
break;
|
|
}
|
|
addr += ate_size;
|
|
}
|
|
|
|
if (gc_done_marker) {
|
|
/* erase the next sector */
|
|
LOG_INF("GC Done marker found");
|
|
addr = fs->ate_wra & ADDR_SECT_MASK;
|
|
nvs_sector_advance(fs, &addr);
|
|
rc = nvs_flash_erase_sector(fs, addr);
|
|
goto end;
|
|
}
|
|
LOG_INF("No GC Done marker found: restarting gc");
|
|
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);
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
/**
|
|
* At this point, the lookup cache wasn't built but the gc function need to use it.
|
|
* So, temporarily, we set the lookup cache to the end of the fs.
|
|
* The cache will be rebuilt afterwards
|
|
**/
|
|
for (i = 0; i < CONFIG_NVS_LOOKUP_CACHE_SIZE; i++) {
|
|
fs->lookup_cache[i] = fs->ate_wra;
|
|
}
|
|
#endif
|
|
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 += fs->flash_parameters->write_block_size;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
end:
|
|
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
if (!rc) {
|
|
rc = nvs_lookup_cache_rebuild(fs);
|
|
}
|
|
#endif
|
|
/* 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);
|
|
}
|
|
k_mutex_unlock(&fs->nvs_lock);
|
|
return rc;
|
|
}
|
|
|
|
int nvs_clear(struct nvs_fs *fs)
|
|
{
|
|
int rc;
|
|
uint32_t addr;
|
|
|
|
if (!fs->ready) {
|
|
LOG_ERR("NVS not initialized");
|
|
return -EACCES;
|
|
}
|
|
|
|
for (uint16_t i = 0; i < fs->sector_count; i++) {
|
|
addr = i << ADDR_SECT_SHIFT;
|
|
rc = nvs_flash_erase_sector(fs, addr);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* nvs needs to be reinitialized after clearing */
|
|
fs->ready = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nvs_mount(struct nvs_fs *fs)
|
|
{
|
|
int rc;
|
|
struct flash_pages_info info;
|
|
size_t write_block_size;
|
|
|
|
k_mutex_init(&fs->nvs_lock);
|
|
|
|
fs->flash_parameters = flash_get_parameters(fs->flash_device);
|
|
if (fs->flash_parameters == NULL) {
|
|
LOG_ERR("Could not obtain flash parameters");
|
|
return -EINVAL;
|
|
}
|
|
|
|
write_block_size = flash_get_write_block_size(fs->flash_device);
|
|
|
|
/* check that the write block size is supported */
|
|
if (write_block_size > NVS_BLOCK_SIZE || write_block_size == 0) {
|
|
LOG_ERR("Unsupported write block size");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check that sector size is a multiple of pagesize */
|
|
rc = flash_get_page_info_by_offs(fs->flash_device, fs->offset, &info);
|
|
if (rc) {
|
|
LOG_ERR("Unable to get page info");
|
|
return -EINVAL;
|
|
}
|
|
if (!fs->sector_size || fs->sector_size % info.size) {
|
|
LOG_ERR("Invalid sector size");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check the number of sectors, it should be at least 2 */
|
|
if (fs->sector_count < 2) {
|
|
LOG_ERR("Configuration error - sector count");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = nvs_startup(fs);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
/* nvs is ready for use */
|
|
fs->ready = true;
|
|
|
|
LOG_INF("%d Sectors of %d bytes", fs->sector_count, fs->sector_size);
|
|
LOG_INF("alloc wra: %d, %x",
|
|
(fs->ate_wra >> ADDR_SECT_SHIFT),
|
|
(fs->ate_wra & ADDR_OFFS_MASK));
|
|
LOG_INF("data wra: %d, %x",
|
|
(fs->data_wra >> ADDR_SECT_SHIFT),
|
|
(fs->data_wra & ADDR_OFFS_MASK));
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t nvs_write(struct nvs_fs *fs, uint16_t id, const void *data, size_t len)
|
|
{
|
|
int rc, gc_count;
|
|
size_t ate_size, data_size;
|
|
struct nvs_ate wlk_ate;
|
|
uint32_t wlk_addr, rd_addr;
|
|
uint16_t required_space = 0U; /* no space, appropriate for delete ate */
|
|
bool prev_found = false;
|
|
|
|
if (!fs->ready) {
|
|
LOG_ERR("NVS not initialized");
|
|
return -EACCES;
|
|
}
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
data_size = nvs_al_size(fs, 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.
|
|
* Also take into account the data CRC that is appended at the end of the data field,
|
|
* if any.
|
|
*/
|
|
if ((len > (fs->sector_size - 4 * ate_size - NVS_DATA_CRC_SIZE)) ||
|
|
((len > 0) && (data == NULL))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* find latest entry with same id */
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(id)];
|
|
|
|
if (wlk_addr == NVS_LOOKUP_CACHE_NO_ADDR) {
|
|
goto no_cached_entry;
|
|
}
|
|
#else
|
|
wlk_addr = fs->ate_wra;
|
|
#endif
|
|
rd_addr = wlk_addr;
|
|
|
|
while (1) {
|
|
rd_addr = wlk_addr;
|
|
rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
if ((wlk_ate.id == id) && (nvs_ate_valid(fs, &wlk_ate))) {
|
|
prev_found = true;
|
|
break;
|
|
}
|
|
if (wlk_addr == fs->ate_wra) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
no_cached_entry:
|
|
#endif
|
|
|
|
if (prev_found) {
|
|
/* previous entry found */
|
|
rd_addr &= ADDR_SECT_MASK;
|
|
rd_addr += wlk_ate.offset;
|
|
|
|
if (len == 0) {
|
|
/* do not try to compare with empty data */
|
|
if (wlk_ate.len == 0U) {
|
|
/* skip delete entry as it is already the
|
|
* last one
|
|
*/
|
|
return 0;
|
|
}
|
|
} else if (len + NVS_DATA_CRC_SIZE == wlk_ate.len) {
|
|
/* do not try to compare if lengths are not equal */
|
|
/* compare the data and if equal return 0 */
|
|
/* note: data CRC is not taken into account here, as it has not yet been
|
|
* appended to the data buffer
|
|
*/
|
|
rc = nvs_flash_block_cmp(fs, rd_addr, data, 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 + NVS_DATA_CRC_SIZE;
|
|
}
|
|
|
|
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
|
|
|
|
gc_count = 0;
|
|
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)) {
|
|
|
|
rc = nvs_flash_wrt_entry(fs, id, data, len);
|
|
if (rc) {
|
|
goto end;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
rc = nvs_sector_close(fs);
|
|
if (rc) {
|
|
goto end;
|
|
}
|
|
|
|
rc = nvs_gc(fs);
|
|
if (rc) {
|
|
goto end;
|
|
}
|
|
gc_count++;
|
|
}
|
|
rc = len;
|
|
end:
|
|
k_mutex_unlock(&fs->nvs_lock);
|
|
return rc;
|
|
}
|
|
|
|
int nvs_delete(struct nvs_fs *fs, uint16_t id)
|
|
{
|
|
return nvs_write(fs, id, NULL, 0);
|
|
}
|
|
|
|
ssize_t nvs_read_hist(struct nvs_fs *fs, uint16_t id, void *data, size_t len,
|
|
uint16_t cnt)
|
|
{
|
|
int rc;
|
|
uint32_t wlk_addr, rd_addr;
|
|
uint16_t cnt_his;
|
|
struct nvs_ate wlk_ate;
|
|
size_t ate_size;
|
|
#ifdef CONFIG_NVS_DATA_CRC
|
|
uint32_t read_data_crc, computed_data_crc;
|
|
#endif
|
|
|
|
if (!fs->ready) {
|
|
LOG_ERR("NVS not initialized");
|
|
return -EACCES;
|
|
}
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
|
|
if (len > (fs->sector_size - 2 * ate_size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
cnt_his = 0U;
|
|
|
|
#ifdef CONFIG_NVS_LOOKUP_CACHE
|
|
wlk_addr = fs->lookup_cache[nvs_lookup_cache_pos(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;
|
|
rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate);
|
|
if (rc) {
|
|
goto err;
|
|
}
|
|
if ((wlk_ate.id == id) && (nvs_ate_valid(fs, &wlk_ate))) {
|
|
cnt_his++;
|
|
}
|
|
if (wlk_addr == fs->ate_wra) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != id)) ||
|
|
(wlk_ate.len == 0U) || (cnt_his < cnt)) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
#ifdef CONFIG_NVS_DATA_CRC
|
|
/* When data CRC is enabled, there should be at least the CRC stored in the data field */
|
|
if (wlk_ate.len < NVS_DATA_CRC_SIZE) {
|
|
return -ENOENT;
|
|
}
|
|
#endif
|
|
|
|
rd_addr &= ADDR_SECT_MASK;
|
|
rd_addr += wlk_ate.offset;
|
|
rc = nvs_flash_rd(fs, rd_addr, data, MIN(len, wlk_ate.len - NVS_DATA_CRC_SIZE));
|
|
if (rc) {
|
|
goto err;
|
|
}
|
|
|
|
/* Check data CRC (only if the whole element data has been read) */
|
|
#ifdef CONFIG_NVS_DATA_CRC
|
|
if (len >= (wlk_ate.len - NVS_DATA_CRC_SIZE)) {
|
|
rd_addr += wlk_ate.len - NVS_DATA_CRC_SIZE;
|
|
rc = nvs_flash_rd(fs, rd_addr, &read_data_crc, sizeof(read_data_crc));
|
|
if (rc) {
|
|
goto err;
|
|
}
|
|
|
|
computed_data_crc = crc32_ieee(data, wlk_ate.len - NVS_DATA_CRC_SIZE);
|
|
if (read_data_crc != computed_data_crc) {
|
|
LOG_ERR("Invalid data CRC: read_data_crc=0x%08X, computed_data_crc=0x%08X",
|
|
read_data_crc, computed_data_crc);
|
|
rc = -EIO;
|
|
goto err;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return wlk_ate.len - NVS_DATA_CRC_SIZE;
|
|
|
|
err:
|
|
return rc;
|
|
}
|
|
|
|
ssize_t nvs_read(struct nvs_fs *fs, uint16_t id, void *data, size_t len)
|
|
{
|
|
int rc;
|
|
|
|
rc = nvs_read_hist(fs, id, data, len, 0);
|
|
return rc;
|
|
}
|
|
|
|
ssize_t nvs_calc_free_space(struct nvs_fs *fs)
|
|
{
|
|
int rc;
|
|
struct nvs_ate step_ate, wlk_ate;
|
|
uint32_t step_addr, wlk_addr;
|
|
size_t ate_size, free_space;
|
|
|
|
if (!fs->ready) {
|
|
LOG_ERR("NVS not initialized");
|
|
return -EACCES;
|
|
}
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
|
|
/*
|
|
* There is always a closing ATE and a reserved ATE for
|
|
* deletion in each sector.
|
|
* Take into account one less sector because it is reserved for the
|
|
* garbage collection.
|
|
*/
|
|
free_space = (fs->sector_count - 1) * (fs->sector_size - (2 * ate_size));
|
|
|
|
step_addr = fs->ate_wra;
|
|
|
|
while (1) {
|
|
rc = nvs_prev_ate(fs, &step_addr, &step_ate);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
wlk_addr = fs->ate_wra;
|
|
|
|
while (1) {
|
|
rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
if ((wlk_ate.id == step_ate.id) ||
|
|
(wlk_addr == fs->ate_wra)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nvs_ate_valid(fs, &step_ate)) {
|
|
/* Take into account the GC done ATE if it is present */
|
|
if (step_ate.len == 0) {
|
|
if (step_ate.id == 0xFFFF) {
|
|
free_space -= ate_size;
|
|
}
|
|
} else if (wlk_addr == step_addr) {
|
|
/* count needed */
|
|
free_space -= nvs_al_size(fs, step_ate.len);
|
|
free_space -= ate_size;
|
|
}
|
|
}
|
|
|
|
if (step_addr == fs->ate_wra) {
|
|
break;
|
|
}
|
|
}
|
|
return free_space;
|
|
}
|
|
|
|
size_t nvs_sector_max_data_size(struct nvs_fs *fs)
|
|
{
|
|
size_t ate_size;
|
|
|
|
if (!fs->ready) {
|
|
LOG_ERR("NVS not initialized");
|
|
return -EACCES;
|
|
}
|
|
|
|
ate_size = nvs_al_size(fs, sizeof(struct nvs_ate));
|
|
|
|
return fs->ate_wra - fs->data_wra - ate_size - NVS_DATA_CRC_SIZE;
|
|
}
|
|
|
|
int nvs_sector_use_next(struct nvs_fs *fs)
|
|
{
|
|
int ret;
|
|
|
|
if (!fs->ready) {
|
|
LOG_ERR("NVS not initialized");
|
|
return -EACCES;
|
|
}
|
|
|
|
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
|
|
|
|
ret = nvs_sector_close(fs);
|
|
if (ret != 0) {
|
|
goto end;
|
|
}
|
|
|
|
ret = nvs_gc(fs);
|
|
|
|
end:
|
|
k_mutex_unlock(&fs->nvs_lock);
|
|
return ret;
|
|
}
|