blob: dfb8190e7710d24e724aaee659863dfa89130a00 [file] [log] [blame]
/* NVS: non volatile storage in flash
*
* Copyright (c) 2018 Laczen
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <flash.h>
#include <crc16.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <nvs/nvs.h>
#include "nvs_priv.h"
static inline u16_t _nvs_len_in_flash(struct nvs_fs *fs, u16_t len)
{
if (fs->write_block_size <= 1) {
return len;
}
return (len + (fs->write_block_size - 1)) & ~(fs->write_block_size - 1);
}
u16_t _nvs_entry_len_in_flash(struct nvs_fs *fs, u16_t len)
{
return _nvs_len_in_flash(fs, len) +
_nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr)) +
_nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
}
off_t _nvs_head_addr_in_flash(struct nvs_fs *fs, const struct nvs_entry *entry)
{
return entry->data_addr -
_nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
}
off_t _nvs_slt_addr_in_flash(struct nvs_fs *fs, const struct nvs_entry *entry)
{
return entry->data_addr + _nvs_len_in_flash(fs, entry->len);
}
int _nvs_bd_check(struct nvs_fs *fs, off_t offset, size_t len)
{
if ((offset > fs->sector_size * fs->sector_count) ||
(offset + len > fs->sector_size * fs->sector_count)) {
/* operation outside nvs area */
return -EINVAL;
}
if ((offset & ~(fs->sector_size - 1)) !=
((offset + len - 1) & ~(fs->sector_size - 1))) {
/* operation over sector boundary */
return -EINVAL;
}
return 0;
}
void _nvs_addr_advance(struct nvs_fs *fs, off_t *addr, u16_t step)
{
*addr += step;
if (*addr >= (fs->sector_count * fs->sector_size)) {
*addr -= (fs->sector_count * fs->sector_size);
}
}
/* Getting the sector header for address given in offset */
int _nvs_sector_hdr_get(struct nvs_fs *fs, off_t offset,
struct _nvs_sector_hdr *sector_hdr)
{
return flash_read(fs->flash_device,
fs->offset + (offset & ~(fs->sector_size - 1)),
sector_hdr,
_nvs_len_in_flash(fs, sizeof(*sector_hdr)));
}
/* Initializing sector by writing magic and status to sector header,
* set fs->write_address just after header offset should be pointing
* to the sector
*/
int _nvs_sector_init(struct nvs_fs *fs, off_t offset)
{
struct _nvs_sector_hdr sector_hdr;
u16_t sector_hdr_len;
int rc;
/* just to be sure, align offset to sector_size */
offset &= ~(fs->sector_size - 1);
fs->sector_id += 1;
rc = _nvs_sector_hdr_get(fs, offset, &sector_hdr);
if (rc) {
SYS_LOG_DBG("Failed reading sector hdr");
return rc;
}
if (sector_hdr.fd_magic != 0xFFFFFFFF) {
SYS_LOG_DBG("Initializing non empty sector");
return -EIO;
}
sector_hdr.fd_magic = fs->magic;
sector_hdr.fd_id = fs->sector_id;
sector_hdr._pad = 0;
sector_hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_sector_hdr));
rc = nvs_flash_write(fs, offset, &sector_hdr, sector_hdr_len);
if (rc) {
return rc;
}
fs->write_location = offset + sector_hdr_len;
return 0;
}
/* check if a sector is used by looking at all elements of the sector.
* return 0 if sector is empty, 1 if it is not empty
*/
int _nvs_sector_is_used(struct nvs_fs *fs, off_t offset)
{
int rc;
off_t addr;
u8_t buf;
rc = 0;
offset &= ~(fs->sector_size - 1);
for (addr = 0; addr < fs->sector_size; addr += sizeof(buf)) {
rc = flash_read(fs->flash_device,
fs->offset + addr,
&buf, sizeof(buf));
if (rc) {
return rc;
}
if (buf != 0xFF) {
return 1;
}
}
return 0;
}
/* erase the flash starting at offset, erase length = len */
int _nvs_flash_erase(struct nvs_fs *fs, off_t offset, size_t len)
{
int rc;
rc = flash_write_protection_set(fs->flash_device, 0);
if (rc) {
/* flash protection set error */
return rc;
}
rc = flash_erase(fs->flash_device, fs->offset + offset, len);
if (rc) {
/* flash write error */
return rc;
}
SYS_LOG_DBG("Erasing flash at %"PRIx32", len %x", offset, len);
(void) flash_write_protection_set(fs->flash_device, 1);
return 0;
}
void _nvs_entry_sector_advance(struct nvs_fs *fs)
{
fs->entry_sector++;
if (fs->entry_sector == fs->sector_count) {
fs->entry_sector = 0;
}
}
/* garbage collection: addr is set to the start of the sector to be gc'ed,
* the entry sector has been updated to point to the sector just after the
* sector being gc'ed
*/
int _nvs_gc(struct nvs_fs *fs, off_t addr)
{
int rc, len, bytes_to_copy;
off_t rd_addr;
struct nvs_entry walker, walker_last, last_entry, search;
struct _nvs_data_hdr head;
u16_t sec_hdr_len, hdr_len, walker_len, walker_last_len;
u8_t buf[NVS_MOVE_BLOCK_SIZE];
walker.data_addr = addr;
sec_hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_sector_hdr));
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
_nvs_addr_advance(fs, &walker.data_addr, sec_hdr_len + hdr_len);
while (1) {
rd_addr = _nvs_head_addr_in_flash(fs, &walker);
rc = nvs_flash_read(fs, rd_addr, &head, hdr_len);
if (rc) {
return rc;
}
if ((head.id == NVS_ID_SECTOR_END) ||
(head.id == NVS_ID_EMPTY)) {
return 0;
}
walker.len = head.len;
walker.id = head.id;
search.id = walker.id;
if (nvs_get_first_entry(fs, &search)) {
/* entry is not found, copy needed - but find the last
* entry first
*/
walker_last = walker;
while (walker_last.id != NVS_ID_SECTOR_END) {
rd_addr = _nvs_head_addr_in_flash(
fs, &walker_last);
rc = nvs_flash_read(
fs, rd_addr, &head, hdr_len);
if (rc) {
return rc;
}
walker_last.len = head.len;
walker_last.id = head.id;
if (walker_last.id == walker.id) {
last_entry = walker_last;
}
walker_last_len = _nvs_entry_len_in_flash(
fs, walker_last.len);
_nvs_addr_advance(
fs, &walker_last.data_addr,
walker_last_len);
}
if (last_entry.len == 0) {
SYS_LOG_DBG("Skipped move of removed entry id"
"%x", search.id);
} else {
rd_addr = _nvs_head_addr_in_flash(
fs, &last_entry);
len = _nvs_entry_len_in_flash(
fs, last_entry.len);
while (len > 0) {
bytes_to_copy =
min(NVS_MOVE_BLOCK_SIZE, len);
rc = nvs_flash_read(fs, rd_addr,
&buf, bytes_to_copy);
if (rc) {
return rc;
}
rc = nvs_flash_write(fs,
fs->write_location,
&buf, bytes_to_copy);
if (rc) {
return rc;
}
len -= bytes_to_copy;
rd_addr += bytes_to_copy;
fs->write_location += bytes_to_copy;
}
SYS_LOG_DBG("Entry with id %x moved to new "
"flash sector", search.id);
}
}
walker_len = _nvs_entry_len_in_flash(fs, walker.len);
_nvs_addr_advance(fs, &walker.data_addr, walker_len);
}
}
void nvs_set_start_entry(struct nvs_fs *fs, struct nvs_entry *entry)
{
u16_t sector_hdr_len, data_hdr_len;
entry->data_addr = fs->entry_sector * fs->sector_size;
sector_hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_sector_hdr));
data_hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
_nvs_addr_advance(fs, &entry->data_addr, sector_hdr_len + data_hdr_len);
}
int nvs_get_first_entry(struct nvs_fs *fs, struct nvs_entry *entry)
{
int rc;
struct _nvs_data_hdr head;
off_t hdr_addr;
u16_t hdr_len, adv_len;
nvs_set_start_entry(fs, entry);
while (1) {
hdr_addr = _nvs_head_addr_in_flash(fs, entry);
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
rc = nvs_flash_read(fs, hdr_addr, &head, hdr_len);
if (rc) {
return rc;
}
if (head.id == NVS_ID_EMPTY) {
return -ENOENT;
}
if (head.id == entry->id) {
entry->len = head.len;
return 0;
}
adv_len = _nvs_entry_len_in_flash(fs, head.len);
_nvs_addr_advance(fs, &entry->data_addr, adv_len);
}
}
int nvs_get_last_entry(struct nvs_fs *fs, struct nvs_entry *entry)
{
int rc;
struct nvs_entry latest;
struct _nvs_data_hdr head;
off_t hdr_addr;
u16_t hdr_len, adv_len;
rc = nvs_get_first_entry(fs, entry);
if (rc) {
return rc;
}
latest.id = entry->id;
latest.data_addr = entry->data_addr;
latest.len = entry->len;
while (1) {
hdr_addr = _nvs_head_addr_in_flash(fs, entry);
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
rc = nvs_flash_read(fs, hdr_addr, &head, hdr_len);
if (rc) {
return rc;
}
if (head.id == NVS_ID_EMPTY) {
entry->id = latest.id;
entry->data_addr = latest.data_addr;
entry->len = latest.len;
return 0;
}
if (head.id == latest.id) {
latest.len = head.len;
latest.data_addr = entry->data_addr;
}
adv_len = _nvs_entry_len_in_flash(fs, head.len);
_nvs_addr_advance(fs, &entry->data_addr, adv_len);
}
}
/* walking over entries, stops on empty or entry with same entry id */
int nvs_walk_entry(struct nvs_fs *fs, struct nvs_entry *entry)
{
int rc;
struct _nvs_data_hdr head;
off_t hdr_addr;
u16_t hdr_len, adv_len;
if (entry->id != NVS_ID_EMPTY) {
adv_len = _nvs_entry_len_in_flash(fs, entry->len);
_nvs_addr_advance(fs, &entry->data_addr, adv_len);
}
while (1) {
hdr_addr = _nvs_head_addr_in_flash(fs, entry);
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
rc = nvs_flash_read(fs, hdr_addr, &head, hdr_len);
if (rc) {
return rc;
}
if (head.id == entry->id) {
entry->len = head.len;
return 0;
}
if (head.id == NVS_ID_EMPTY) {
return -ENOENT;
}
adv_len = _nvs_entry_len_in_flash(fs, head.len);
_nvs_addr_advance(fs, &entry->data_addr, adv_len);
}
}
int nvs_init(struct nvs_fs *fs, const char *dev_name, u32_t magic)
{
int i, rc, active_sector_cnt = 0;
int entry_sector = -1;
u16_t entry_sector_id, active_sector_id, max_item_len;
struct _nvs_sector_hdr sector_hdr;
struct nvs_entry entry;
off_t addr;
fs->magic = magic;
fs->sector_id = 0;
fs->free_space = fs->max_len;
fs->flash_device = device_get_binding(dev_name);
if (!fs->flash_device) {
SYS_LOG_ERR("No valid flash device found");
return -ENXIO;
}
fs->write_block_size = flash_get_write_block_size(fs->flash_device);
/* check the sector size, should be power of 2 */
if (!((fs->sector_size != 0) &&
!(fs->sector_size & (fs->sector_size - 1)))) {
SYS_LOG_ERR("Configuration error - sector size");
return -EINVAL;
}
/* check the number of sectors, it should be at least 2 */
if (fs->sector_count < 2) {
SYS_LOG_ERR("Configuration error - sector count");
return -EINVAL;
}
/* check the maximum item size, it should be at least smaller than the
* sector_size - sector_hdr size - item_hdr_size - item_slt_size
*/
max_item_len = fs->sector_size
- _nvs_len_in_flash(fs, sizeof(struct _nvs_sector_hdr))
- _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr))
- _nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
if (fs->max_len > max_item_len) {
SYS_LOG_ERR("Configuration error - max item size");
return -EINVAL;
}
for (i = 0; i < fs->sector_count; i++) {
rc = _nvs_sector_hdr_get(fs, i * fs->sector_size, &sector_hdr);
if (rc) {
return rc;
}
if (sector_hdr.fd_magic != fs->magic) {
continue;
}
active_sector_cnt++;
if (entry_sector < 0) {
entry_sector = i;
entry_sector_id = sector_hdr.fd_id;
active_sector_id = sector_hdr.fd_id;
continue;
}
if (NVS_ID_GT(sector_hdr.fd_id, active_sector_id)) {
active_sector_id = sector_hdr.fd_id;
}
if (NVS_ID_GT(entry_sector_id, sector_hdr.fd_id)) {
entry_sector = i;
entry_sector_id = sector_hdr.fd_id;
}
}
if (entry_sector < 0) { /* No valid sectors found */
SYS_LOG_DBG("No valid sectors found, initializing sectors");
for (addr = 0; addr < (fs->sector_count * fs->sector_size);
addr += fs->sector_size) {
/* first check if used, only erase if it is */
if (_nvs_sector_is_used(fs, addr)) {
rc = _nvs_flash_erase(fs, addr,
fs->sector_size);
if (rc) {
return rc;
}
}
}
rc = _nvs_sector_init(fs, 0);
if (rc) {
return rc;
}
entry_sector = 0;
active_sector_id = fs->sector_id;
}
fs->entry_sector = entry_sector;
fs->sector_id = active_sector_id;
/* Find the first empty entry */
nvs_set_start_entry(fs, &entry);
entry.id = NVS_ID_EMPTY;
rc = nvs_walk_entry(fs, &entry);
if (rc) {
return rc;
}
fs->write_location = _nvs_head_addr_in_flash(fs, &entry);
if (active_sector_cnt == fs->sector_count) {
/* one sector should always be empty, unless power was cut
* during garbage collection start gc again on the last
* sector.
*/
SYS_LOG_DBG("Restarting garbage collection");
addr = fs->entry_sector * fs->sector_size;
_nvs_entry_sector_advance(fs);
rc = _nvs_gc(fs, addr);
if (rc) {
return rc;
}
rc = _nvs_flash_erase(fs, addr, fs->sector_size);
if (rc) {
return rc;
}
}
SYS_LOG_INF("maximum storage length %d bytes", fs->max_len);
SYS_LOG_INF("write-align: %d, write-addr: %"PRIx32"",
fs->write_block_size, fs->write_location);
SYS_LOG_INF("entry sector: %d, entry sector ID: %d",
fs->entry_sector, fs->sector_id);
k_mutex_init(&fs->nvs_lock);
return 0;
}
int nvs_append(struct nvs_fs *fs, struct nvs_entry *entry)
{
int rc;
u16_t required_len, extended_len, hdr_len, slt_len;
struct _nvs_data_hdr data_hdr;
rc = 0;
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
required_len = _nvs_entry_len_in_flash(fs, entry->len);
/* the space available should be big enough to fit the data + header
* and slot of next data
*/
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
slt_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
extended_len = required_len + hdr_len + slt_len;
if ((fs->sector_size - (fs->write_location & (fs->sector_size - 1))) <
extended_len) {
rc = NVS_STATUS_NOSPACE;
goto err;
}
data_hdr.id = entry->id;
data_hdr.len = _nvs_len_in_flash(fs, entry->len);
rc = nvs_flash_write(fs, fs->write_location, &data_hdr, hdr_len);
if (rc) {
goto err;
}
entry->data_addr = fs->write_location + hdr_len;
fs->write_location += required_len;
rc = 0;
err:
k_mutex_unlock(&fs->nvs_lock);
return rc;
}
/* close the append by applying a seal to the data that includes a crc */
int nvs_append_close(struct nvs_fs *fs, const struct nvs_entry *entry)
{
int rc;
struct _nvs_data_slt data_slt;
off_t addr;
u16_t slt_len;
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
/* crc16_ccitt is calculated on flash data, set correct offset */
addr = entry->data_addr + fs->offset;
data_slt.crc16 = crc16_ccitt(0xFFFF, (const u8_t *) addr, entry->len);
data_slt._pad = 0xFFFF;
addr = _nvs_slt_addr_in_flash(fs, entry);
slt_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
rc = nvs_flash_write(fs, addr, &data_slt, slt_len);
k_mutex_unlock(&fs->nvs_lock);
return rc;
}
/* check the crc of an entry */
int nvs_check_crc(struct nvs_fs *fs, struct nvs_entry *entry)
{
int rc;
struct _nvs_data_slt data_slt;
off_t addr;
u16_t crc16, slt_len;
/* crc16_ccitt is calculated on flash data, set correct offset */
addr = entry->data_addr + fs->offset;
crc16 = crc16_ccitt(0xFFFF, (const u8_t *) addr, entry->len);
addr = _nvs_slt_addr_in_flash(fs, entry);
slt_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
rc = nvs_flash_read(fs, addr, &data_slt, slt_len);
if (rc || (crc16 != data_slt.crc16)) {
return -EIO;
}
return 0;
}
/* rotate the nvs, frees the next sector (based on fs->write_location) */
int nvs_rotate(struct nvs_fs *fs)
{
int rc;
off_t addr;
struct _nvs_data_hdr head;
u16_t hdr_len, slt_len, sec_hdr_len;
rc = 0;
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
/* fill previous sector with data to jump to the next sector */
head.id = NVS_ID_SECTOR_END;
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
slt_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
sec_hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_sector_hdr));
head.len = fs->sector_size
- (fs->write_location & (fs->sector_size - 1))
- slt_len - hdr_len + sec_hdr_len;
rc = nvs_flash_write(fs, fs->write_location, &head, hdr_len);
if (rc) {
goto out;
}
/* advance to next sector for writing */
addr = (fs->write_location & ~(fs->sector_size - 1));
_nvs_addr_advance(fs, &addr, fs->sector_size);
/* initialize the new sector */
rc = _nvs_sector_init(fs, addr);
if (rc) {
goto out;
}
/* data copy */
addr = (fs->write_location & ~(fs->sector_size - 1));
_nvs_addr_advance(fs, &addr, fs->sector_size);
/* Do we need to advance the entry_sector, if so we need to copy */
if ((addr & ~(fs->sector_size - 1)) ==
fs->entry_sector * fs->sector_size) {
SYS_LOG_DBG("Starting data copy...");
_nvs_entry_sector_advance(fs);
rc = _nvs_gc(fs, addr);
if (rc) {
SYS_LOG_DBG("Quit data copy - gc error");
goto out;
}
rc = _nvs_flash_erase(fs, addr, fs->sector_size);
if (rc) {
SYS_LOG_DBG("Quit data copy - flash erase error");
goto out;
}
SYS_LOG_DBG("Done data copy - no error");
}
rc = 0;
out:
k_mutex_unlock(&fs->nvs_lock);
return rc;
}
int nvs_clear(struct nvs_fs *fs)
{
int rc;
off_t addr;
k_mutex_lock(&fs->nvs_lock, K_FOREVER);
for (addr = 0; addr < fs->sector_count * fs->sector_size;
addr += fs->sector_size) {
rc = _nvs_flash_erase(fs, addr, fs->sector_size);
if (rc) {
goto out;
}
}
rc = 0;
out:
k_mutex_unlock(&fs->nvs_lock);
return rc;
}
int nvs_flash_read(struct nvs_fs *fs, off_t offset, void *data, size_t len)
{
int rc;
rc = _nvs_bd_check(fs, offset, len);
if (rc) {
return rc;
}
rc = flash_read(fs->flash_device, fs->offset + offset, data, len);
if (rc) {
/* flash read error */
return rc;
}
return 0;
}
int nvs_flash_write(struct nvs_fs *fs, off_t offset, const void *data,
size_t len)
{
int rc;
rc = _nvs_bd_check(fs, offset, len);
if (rc) {
return rc;
}
rc = flash_write_protection_set(fs->flash_device, 0);
if (rc) {
/* flash protection set error */
return rc;
}
rc = flash_write(fs->flash_device, fs->offset + offset, data, len);
if (rc) {
/* flash write error */
return rc;
}
(void) flash_write_protection_set(fs->flash_device, 1);
/* don't mind about this error */
return 0;
}
ssize_t nvs_write(struct nvs_fs *fs, u16_t id, const void *data, size_t len)
{
int rc, entry_cmp;
int rot_cnt = 0;
u16_t free_up, entry_len_fl, hdr_len, slt_len;
struct nvs_entry entry, stored_entry;
off_t stored_entry_addr;
if ((id == NVS_ID_EMPTY) ||
(id == NVS_ID_SECTOR_END) ||
(len > fs->max_len) ||
((len > 0) && (data == NULL))) {
return -EINVAL;
}
if ((len > 0) && (fs->free_space < fs->max_len)) {
return -ENOSPC;
}
if ((!len) || IS_ENABLED(CONFIG_NVS_PROTECT_FLASH)) {
/* find item to delete or check rewriting same data */
stored_entry.id = id;
rc = nvs_get_last_entry(fs, &stored_entry);
if (!rc) {
entry_len_fl = _nvs_len_in_flash(fs, len);
if (stored_entry.len == entry_len_fl) {
stored_entry_addr = stored_entry.data_addr
+ fs->offset;
entry_cmp = memcmp(data,
(void *) stored_entry_addr,
len);
if (!entry_cmp) {
return 0;
}
}
} else {
return rc;
}
}
entry.id = id;
entry.len = len;
/* new data or data has changed */
while (1) {
rc = nvs_append(fs, &entry);
if (rc) {
if (rc == NVS_STATUS_NOSPACE) {
if (fs->free_space == 0) {
/* if we get here when there is no
* free_space available this means
* even deletes fails, we just
* give up and say the file system
* is full
*/
rc = -ENOSPC;
goto err;
}
if (rot_cnt == fs->sector_count) {
fs->free_space = 0;
rc = -ENOSPC;
goto err;
}
if (nvs_rotate(fs)) {
goto err;
}
rot_cnt++;
} else {
rc = -ENOSPC;
goto err;
}
} else {
break;
}
}
rc = nvs_flash_write(fs, entry.data_addr, data, entry.len);
if (rc) {
goto err;
}
rc = nvs_append_close(fs, &entry);
if (rc) {
goto err;
}
if ((!entry.len) && (fs->free_space < fs->max_len)) {
/* freeing up space by deleting */
hdr_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_hdr));
slt_len = _nvs_len_in_flash(fs, sizeof(struct _nvs_data_slt));
free_up = stored_entry.len + hdr_len + slt_len;
fs->free_space = min(fs->max_len, fs->free_space + free_up);
}
rc = entry.len;
err:
return rc;
}
int nvs_delete(struct nvs_fs *fs, u16_t id)
{
return nvs_write(fs, id, NULL, 0);
}
ssize_t nvs_read(struct nvs_fs *fs, u16_t id, void *data, size_t len)
{
int rc;
struct nvs_entry entry;
if ((id == NVS_ID_EMPTY) || (id == NVS_ID_SECTOR_END)) {
return -EINVAL;
}
entry.id = id;
/* Read last entry */
rc = nvs_get_last_entry(fs, &entry);
if (entry.len == 0) {
return -ENOENT;
}
if (rc) {
goto err;
}
rc = nvs_check_crc(fs, &entry);
if (rc) {
goto err;
}
rc = nvs_flash_read(fs, entry.data_addr, data,
min(entry.len, len));
if (rc) {
goto err;
}
rc = entry.len;
err:
return rc;
}
ssize_t nvs_read_hist(struct nvs_fs *fs, u16_t id, void *data, size_t len,
u16_t cnt)
{
int rc;
struct nvs_entry entry;
u16_t cnt_his;
if ((id == NVS_ID_EMPTY) || (id == NVS_ID_SECTOR_END)) {
return -EINVAL;
}
entry.id = id;
/* Read history entry */
/* First find out how many entries are in the history */
rc = nvs_get_first_entry(fs, &entry);
if (rc) {
goto err;
}
cnt_his = 0;
while (1) {
rc = nvs_walk_entry(fs, &entry);
if (rc) {
break;
}
/* disregard items with zero length */
if (entry.len > 0) {
cnt_his++;
}
}
if (cnt_his < cnt) {
/* Trying to read history item that is not available */
return -ENOENT;
}
/* Now get the correct item by decreasing cnt_his until cnt
* is reached
*/
rc = nvs_get_first_entry(fs, &entry);
if (rc) {
goto err;
}
while (1) {
if (cnt_his == cnt) {
break;
}
rc = nvs_walk_entry(fs, &entry);
if (rc) {
break;
}
/* disregard items with zero length */
if (entry.len > 0) {
cnt_his--;
}
}
if (rc) {
goto err;
}
rc = nvs_check_crc(fs, &entry);
if (rc) {
goto err;
}
rc = nvs_flash_read(fs, entry.data_addr, data,
min(entry.len, len));
if (rc) {
goto err;
}
rc = entry.len;
err:
return rc;
}