zephyr/subsys/fs/zms/zms.c
Riadh Ghaddab e0f0256538 zms: optimize write function by skipping unnecessary reads
when performing a write ZMS checks if the data exists in the storage to
avoid double writing the same data and save some memory cycle life time.
However this downgrades the write performance.
Enable this feature only when CONFIG_ZMS_NO_DOUBLE_WRITE is enabled.

Signed-off-by: Riadh Ghaddab <rghaddab@baylibre.com>
2024-12-19 19:55:30 +01:00

1803 lines
45 KiB
C

/* Copyright (c) 2024 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*
* ZMS: Zephyr Memory Storage
*/
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <zephyr/fs/zms.h>
#include <zephyr/sys/crc.h>
#include "zms_priv.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(fs_zms, CONFIG_ZMS_LOG_LEVEL);
static int zms_prev_ate(struct zms_fs *fs, uint64_t *addr, struct zms_ate *ate);
static int zms_ate_valid(struct zms_fs *fs, const struct zms_ate *entry);
static int zms_get_sector_cycle(struct zms_fs *fs, uint64_t addr, uint8_t *cycle_cnt);
static int zms_get_sector_header(struct zms_fs *fs, uint64_t addr, struct zms_ate *empty_ate,
struct zms_ate *close_ate);
static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_ate *entry,
uint8_t cycle_cnt);
#ifdef CONFIG_ZMS_LOOKUP_CACHE
static inline size_t zms_lookup_cache_pos(uint32_t id)
{
uint32_t hash;
/* 32-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
hash = id;
hash ^= hash >> 16;
hash *= 0x7feb352dU;
hash ^= hash >> 15;
hash *= 0x846ca68bU;
hash ^= hash >> 16;
return hash % CONFIG_ZMS_LOOKUP_CACHE_SIZE;
}
static int zms_lookup_cache_rebuild(struct zms_fs *fs)
{
int rc;
int previous_sector_num = ZMS_INVALID_SECTOR_NUM;
uint64_t addr;
uint64_t ate_addr;
uint64_t *cache_entry;
uint8_t current_cycle;
struct zms_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 zms_prev_ate() */
ate_addr = addr;
rc = zms_prev_ate(fs, &addr, &ate);
if (rc) {
return rc;
}
cache_entry = &fs->lookup_cache[zms_lookup_cache_pos(ate.id)];
if (ate.id != ZMS_HEAD_ID && *cache_entry == ZMS_LOOKUP_CACHE_NO_ADDR) {
/* read the ate cycle only when we change the sector
* or if it is the first read
*/
if (SECTOR_NUM(ate_addr) != previous_sector_num) {
rc = zms_get_sector_cycle(fs, ate_addr, &current_cycle);
if (rc == -ENOENT) {
/* sector never used */
current_cycle = 0;
} else if (rc) {
/* bad flash read */
return rc;
}
}
if (zms_ate_valid_different_sector(fs, &ate, current_cycle)) {
*cache_entry = ate_addr;
}
previous_sector_num = SECTOR_NUM(ate_addr);
}
if (addr == fs->ate_wra) {
break;
}
}
return 0;
}
static void zms_lookup_cache_invalidate(struct zms_fs *fs, uint32_t sector)
{
uint64_t *cache_entry = fs->lookup_cache;
uint64_t *const cache_end = &fs->lookup_cache[CONFIG_ZMS_LOOKUP_CACHE_SIZE];
for (; cache_entry < cache_end; ++cache_entry) {
if (SECTOR_NUM(*cache_entry) == sector) {
*cache_entry = ZMS_LOOKUP_CACHE_NO_ADDR;
}
}
}
#endif /* CONFIG_ZMS_LOOKUP_CACHE */
/* Helper to compute offset given the address */
static inline off_t zms_addr_to_offset(struct zms_fs *fs, uint64_t addr)
{
return fs->offset + (fs->sector_size * SECTOR_NUM(addr)) + SECTOR_OFFSET(addr);
}
/* Helper to round down len to the closest multiple of write_block_size */
static inline size_t zms_round_down_write_block_size(struct zms_fs *fs, size_t len)
{
return len & ~(fs->flash_parameters->write_block_size - 1U);
}
/* Helper to round up len to multiple of write_block_size */
static inline size_t zms_round_up_write_block_size(struct zms_fs *fs, size_t len)
{
return (len + (fs->flash_parameters->write_block_size - 1U)) &
~(fs->flash_parameters->write_block_size - 1U);
}
/* zms_al_size returns size aligned to fs->write_block_size */
static inline size_t zms_al_size(struct zms_fs *fs, size_t len)
{
size_t write_block_size = fs->flash_parameters->write_block_size;
if (write_block_size <= 1U) {
return len;
}
return zms_round_up_write_block_size(fs, len);
}
/* Helper to get empty ATE address */
static inline uint64_t zms_empty_ate_addr(struct zms_fs *fs, uint64_t addr)
{
return (addr & ADDR_SECT_MASK) + fs->sector_size - fs->ate_size;
}
/* Helper to get close ATE address */
static inline uint64_t zms_close_ate_addr(struct zms_fs *fs, uint64_t addr)
{
return (addr & ADDR_SECT_MASK) + fs->sector_size - 2 * fs->ate_size;
}
/* Aligned memory write */
static int zms_flash_al_wrt(struct zms_fs *fs, uint64_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[ZMS_BLOCK_SIZE];
if (!len) {
/* Nothing to write, avoid changing the flash protection */
return 0;
}
offset = zms_addr_to_offset(fs, addr);
blen = zms_round_down_write_block_size(fs, len);
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 zms address */
static int zms_flash_rd(struct zms_fs *fs, uint64_t addr, void *data, size_t len)
{
off_t offset;
offset = zms_addr_to_offset(fs, addr);
return flash_read(fs->flash_device, offset, data, len);
}
/* allocation entry write */
static int zms_flash_ate_wrt(struct zms_fs *fs, const struct zms_ate *entry)
{
int rc;
rc = zms_flash_al_wrt(fs, fs->ate_wra, entry, sizeof(struct zms_ate));
if (rc) {
goto end;
}
#ifdef CONFIG_ZMS_LOOKUP_CACHE
/* 0xFFFFFFFF is a special-purpose identifier. Exclude it from the cache */
if (entry->id != ZMS_HEAD_ID) {
fs->lookup_cache[zms_lookup_cache_pos(entry->id)] = fs->ate_wra;
}
#endif
fs->ate_wra -= zms_al_size(fs, sizeof(struct zms_ate));
end:
return rc;
}
/* data write */
static int zms_flash_data_wrt(struct zms_fs *fs, const void *data, size_t len)
{
int rc;
rc = zms_flash_al_wrt(fs, fs->data_wra, data, len);
if (rc < 0) {
return rc;
}
fs->data_wra += zms_al_size(fs, len);
return 0;
}
/* flash ate read */
static int zms_flash_ate_rd(struct zms_fs *fs, uint64_t addr, struct zms_ate *entry)
{
return zms_flash_rd(fs, addr, entry, sizeof(struct zms_ate));
}
/* zms_flash_block_cmp compares the data in flash at addr to data
* in blocks of size ZMS_BLOCK_SIZE aligned to fs->write_block_size
* returns 0 if equal, 1 if not equal, errcode if error
*/
static int zms_flash_block_cmp(struct zms_fs *fs, uint64_t addr, const void *data, size_t len)
{
const uint8_t *data8 = (const uint8_t *)data;
int rc;
size_t bytes_to_cmp;
size_t block_size;
uint8_t buf[ZMS_BLOCK_SIZE];
block_size = zms_round_down_write_block_size(fs, ZMS_BLOCK_SIZE);
while (len) {
bytes_to_cmp = MIN(block_size, len);
rc = zms_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;
}
/* zms_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 zms_flash_cmp_const(struct zms_fs *fs, uint64_t addr, uint8_t value, size_t len)
{
int rc;
size_t bytes_to_cmp;
size_t block_size;
uint8_t cmp[ZMS_BLOCK_SIZE];
block_size = zms_round_down_write_block_size(fs, ZMS_BLOCK_SIZE);
(void)memset(cmp, value, block_size);
while (len) {
bytes_to_cmp = MIN(block_size, len);
rc = zms_flash_block_cmp(fs, addr, cmp, bytes_to_cmp);
if (rc) {
return rc;
}
len -= bytes_to_cmp;
addr += bytes_to_cmp;
}
return 0;
}
/* flash block move: move a block at addr to the current data write location
* and updates the data write location.
*/
static int zms_flash_block_move(struct zms_fs *fs, uint64_t addr, size_t len)
{
int rc;
size_t bytes_to_copy;
size_t block_size;
uint8_t buf[ZMS_BLOCK_SIZE];
block_size = zms_round_down_write_block_size(fs, ZMS_BLOCK_SIZE);
while (len) {
bytes_to_copy = MIN(block_size, len);
rc = zms_flash_rd(fs, addr, buf, bytes_to_copy);
if (rc) {
return rc;
}
rc = zms_flash_data_wrt(fs, buf, bytes_to_copy);
if (rc) {
return rc;
}
len -= bytes_to_copy;
addr += bytes_to_copy;
}
return 0;
}
/* erase a sector and verify erase was OK.
* return 0 if OK, errorcode on error.
*/
static int zms_flash_erase_sector(struct zms_fs *fs, uint64_t addr)
{
int rc;
off_t offset;
bool ebw_required =
flash_params_get_erase_cap(fs->flash_parameters) & FLASH_ERASE_C_EXPLICIT;
if (!ebw_required) {
/* Do nothing for devices that do not have erase capability */
return 0;
}
addr &= ADDR_SECT_MASK;
offset = zms_addr_to_offset(fs, addr);
LOG_DBG("Erasing flash at offset 0x%lx ( 0x%llx ), len %u", (long)offset, addr,
fs->sector_size);
#ifdef CONFIG_ZMS_LOOKUP_CACHE
zms_lookup_cache_invalidate(fs, SECTOR_NUM(addr));
#endif
rc = flash_erase(fs->flash_device, offset, fs->sector_size);
if (rc) {
return rc;
}
if (zms_flash_cmp_const(fs, addr, fs->flash_parameters->erase_value, fs->sector_size)) {
LOG_ERR("Failure while erasing the sector at offset 0x%lx", (long)offset);
rc = -ENXIO;
}
return rc;
}
/* crc update on allocation entry */
static void zms_ate_crc8_update(struct zms_ate *entry)
{
uint8_t crc8;
/* crc8 field is the first element of the structure, do not include it */
crc8 = crc8_ccitt(0xff, (uint8_t *)entry + SIZEOF_FIELD(struct zms_ate, crc8),
sizeof(struct zms_ate) - SIZEOF_FIELD(struct zms_ate, crc8));
entry->crc8 = crc8;
}
/* crc check on allocation entry
* returns 0 if OK, 1 on crc fail
*/
static int zms_ate_crc8_check(const struct zms_ate *entry)
{
uint8_t crc8;
/* crc8 field is the first element of the structure, do not include it */
crc8 = crc8_ccitt(0xff, (uint8_t *)entry + SIZEOF_FIELD(struct zms_ate, crc8),
sizeof(struct zms_ate) - SIZEOF_FIELD(struct zms_ate, crc8));
if (crc8 == entry->crc8) {
return 0;
}
return 1;
}
/* zms_ate_valid validates an ate in the current sector by checking if the ate crc is valid
* and its cycle cnt matches the cycle cnt of the active sector
*
* return 1 if ATE is valid,
* 0 otherwise
*
* see: zms_ate_valid_different_sector
*/
static int zms_ate_valid(struct zms_fs *fs, const struct zms_ate *entry)
{
return zms_ate_valid_different_sector(fs, entry, fs->sector_cycle);
}
/* zms_ate_valid_different_sector validates an ate that is in a different
* sector than the active one. It takes as argument the cycle_cnt of the
* sector where the ATE to be validated is stored
* return 1 if crc8 and cycle_cnt are valid,
* 0 otherwise
*/
static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_ate *entry,
uint8_t cycle_cnt)
{
if ((cycle_cnt != entry->cycle_cnt) || zms_ate_crc8_check(entry)) {
return 0;
}
return 1;
}
static inline int zms_get_cycle_on_sector_change(struct zms_fs *fs, uint64_t addr,
int previous_sector_num, uint8_t *cycle_cnt)
{
int rc;
/* read the ate cycle only when we change the sector
* or if it is the first read
*/
if (SECTOR_NUM(addr) != previous_sector_num) {
rc = zms_get_sector_cycle(fs, addr, cycle_cnt);
if (rc == -ENOENT) {
/* sector never used */
*cycle_cnt = 0;
} else if (rc) {
/* bad flash read */
return rc;
}
}
return 0;
}
/* zms_close_ate_valid validates a sector close ate.
* A valid sector close ate should be:
* - a valid ate
* - with len = 0 and id = ZMS_HEAD_ID
* - and offset points to location at ate multiple from sector size
* return true if valid, false otherwise
*/
static bool zms_close_ate_valid(struct zms_fs *fs, const struct zms_ate *entry)
{
return (zms_ate_valid_different_sector(fs, entry, entry->cycle_cnt) && (!entry->len) &&
(entry->id == ZMS_HEAD_ID) && !((fs->sector_size - entry->offset) % fs->ate_size));
}
/* zms_empty_ate_valid validates an sector empty ate.
* A valid sector empty ate should be:
* - a valid ate
* - with len = 0xffff and id = 0xffffffff
* return true if valid, false otherwise
*/
static bool zms_empty_ate_valid(struct zms_fs *fs, const struct zms_ate *entry)
{
return (zms_ate_valid_different_sector(fs, entry, entry->cycle_cnt) &&
(entry->len == 0xffff) && (entry->id == ZMS_HEAD_ID));
}
/* zms_gc_done_ate_valid validates a garbage collector done ATE
* Valid gc_done_ate:
* - valid ate
* - len = 0
* - id = 0xffffffff
* return true if valid, false otherwise
*/
static bool zms_gc_done_ate_valid(struct zms_fs *fs, const struct zms_ate *entry)
{
return (zms_ate_valid_different_sector(fs, entry, entry->cycle_cnt) && (!entry->len) &&
(entry->id == ZMS_HEAD_ID));
}
/* Read empty and close ATE of the sector where belongs address "addr" and
* validates that the sector is closed.
* retval: 0 if sector is not close
* retval: 1 is sector is closed
* retval: < 0 if read of the header failed.
*/
static int zms_validate_closed_sector(struct zms_fs *fs, uint64_t addr, struct zms_ate *empty_ate,
struct zms_ate *close_ate)
{
int rc;
/* read the header ATEs */
rc = zms_get_sector_header(fs, addr, empty_ate, close_ate);
if (rc) {
return rc;
}
if (zms_empty_ate_valid(fs, empty_ate) && zms_close_ate_valid(fs, close_ate) &&
(empty_ate->cycle_cnt == close_ate->cycle_cnt)) {
/* Closed sector validated */
return 1;
}
return 0;
}
/* store an entry in flash */
static int zms_flash_write_entry(struct zms_fs *fs, uint32_t id, const void *data, size_t len)
{
int rc;
struct zms_ate entry;
/* Initialize all members to 0 */
memset(&entry, 0, sizeof(struct zms_ate));
entry.id = id;
entry.len = (uint16_t)len;
entry.cycle_cnt = fs->sector_cycle;
if (len > ZMS_DATA_IN_ATE_SIZE) {
/* only compute CRC if len is greater than 8 bytes */
if (IS_ENABLED(CONFIG_ZMS_DATA_CRC)) {
entry.data_crc = crc32_ieee(data, len);
}
entry.offset = (uint32_t)SECTOR_OFFSET(fs->data_wra);
} else if ((len > 0) && (len <= ZMS_DATA_IN_ATE_SIZE)) {
/* Copy data into entry for small data ( < 8B) */
memcpy(&entry.data, data, len);
}
zms_ate_crc8_update(&entry);
if (len > ZMS_DATA_IN_ATE_SIZE) {
rc = zms_flash_data_wrt(fs, data, len);
if (rc) {
return rc;
}
}
rc = zms_flash_ate_wrt(fs, &entry);
if (rc) {
return rc;
}
return 0;
}
/* end of flash routines */
/* Search for the last valid ATE written in a sector and also update data write address
*/
static int zms_recover_last_ate(struct zms_fs *fs, uint64_t *addr, uint64_t *data_wra)
{
uint64_t data_end_addr;
uint64_t ate_end_addr;
struct zms_ate end_ate;
int rc;
LOG_DBG("Recovering last ate from sector %llu", SECTOR_NUM(*addr));
/* skip close and empty ATE */
*addr -= 2 * fs->ate_size;
ate_end_addr = *addr;
data_end_addr = *addr & ADDR_SECT_MASK;
/* Initialize the data_wra to the first address of the sector */
*data_wra = data_end_addr;
while (ate_end_addr > data_end_addr) {
rc = zms_flash_ate_rd(fs, ate_end_addr, &end_ate);
if (rc) {
return rc;
}
if (zms_ate_valid(fs, &end_ate)) {
/* found a valid ate, update data_end_addr and *addr */
data_end_addr &= ADDR_SECT_MASK;
if (end_ate.len > ZMS_DATA_IN_ATE_SIZE) {
data_end_addr += end_ate.offset + zms_al_size(fs, end_ate.len);
*data_wra = data_end_addr;
}
*addr = ate_end_addr;
}
ate_end_addr -= fs->ate_size;
}
return 0;
}
/* compute previous addr of ATE */
static int zms_compute_prev_addr(struct zms_fs *fs, uint64_t *addr)
{
int sec_closed;
struct zms_ate empty_ate;
struct zms_ate close_ate;
*addr += fs->ate_size;
if ((SECTOR_OFFSET(*addr)) != (fs->sector_size - 2 * fs->ate_size)) {
return 0;
}
/* last ate in sector, do jump to previous sector */
if (SECTOR_NUM(*addr) == 0U) {
*addr += ((uint64_t)(fs->sector_count - 1) << ADDR_SECT_SHIFT);
} else {
*addr -= (1ULL << ADDR_SECT_SHIFT);
}
/* verify if the sector is closed */
sec_closed = zms_validate_closed_sector(fs, *addr, &empty_ate, &close_ate);
if (sec_closed < 0) {
return sec_closed;
}
/* Non Closed Sector */
if (!sec_closed) {
/* at the end of filesystem */
*addr = fs->ate_wra;
return 0;
}
/* Update the address here because the header ATEs are valid.*/
(*addr) &= ADDR_SECT_MASK;
(*addr) += close_ate.offset;
return 0;
}
/* walking through allocation entry list, from newest to oldest entries
* read ate from addr, modify addr to the previous ate
*/
static int zms_prev_ate(struct zms_fs *fs, uint64_t *addr, struct zms_ate *ate)
{
int rc;
rc = zms_flash_ate_rd(fs, *addr, ate);
if (rc) {
return rc;
}
return zms_compute_prev_addr(fs, addr);
}
static void zms_sector_advance(struct zms_fs *fs, uint64_t *addr)
{
*addr += (1ULL << ADDR_SECT_SHIFT);
if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count) {
*addr -= ((uint64_t)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 zms_sector_close(struct zms_fs *fs)
{
int rc;
struct zms_ate close_ate;
struct zms_ate garbage_ate;
close_ate.id = ZMS_HEAD_ID;
close_ate.len = 0U;
close_ate.offset = (uint32_t)SECTOR_OFFSET(fs->ate_wra + fs->ate_size);
close_ate.metadata = 0xffffffff;
close_ate.cycle_cnt = fs->sector_cycle;
/* When we close the sector, we must write all non used ATE with
* a non valid (Junk) ATE.
* This is needed to avoid some corner cases where some ATEs are
* not overwritten and become valid when the cycle counter wrap again
* to the same cycle counter of the old ATE.
* Example :
* - An ATE.cycl_cnt == 0 is written as last ATE of the sector
- This ATE was never overwritten in the next 255 cycles because of
large data size
- Next 256th cycle the leading cycle_cnt is 0, this ATE becomes
valid even if it is not the case.
*/
memset(&garbage_ate, fs->flash_parameters->erase_value, sizeof(garbage_ate));
while (SECTOR_OFFSET(fs->ate_wra) && (fs->ate_wra >= fs->data_wra)) {
rc = zms_flash_ate_wrt(fs, &garbage_ate);
if (rc) {
return rc;
}
}
fs->ate_wra = zms_close_ate_addr(fs, fs->ate_wra);
zms_ate_crc8_update(&close_ate);
(void)zms_flash_ate_wrt(fs, &close_ate);
zms_sector_advance(fs, &fs->ate_wra);
rc = zms_get_sector_cycle(fs, fs->ate_wra, &fs->sector_cycle);
if (rc == -ENOENT) {
/* sector never used */
fs->sector_cycle = 0;
} else if (rc) {
/* bad flash read */
return rc;
}
fs->data_wra = fs->ate_wra & ADDR_SECT_MASK;
return 0;
}
static int zms_add_gc_done_ate(struct zms_fs *fs)
{
struct zms_ate gc_done_ate;
LOG_DBG("Adding gc done ate at %llx", fs->ate_wra);
gc_done_ate.id = ZMS_HEAD_ID;
gc_done_ate.len = 0U;
gc_done_ate.offset = (uint32_t)SECTOR_OFFSET(fs->data_wra);
gc_done_ate.metadata = 0xffffffff;
gc_done_ate.cycle_cnt = fs->sector_cycle;
zms_ate_crc8_update(&gc_done_ate);
return zms_flash_ate_wrt(fs, &gc_done_ate);
}
static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr)
{
struct zms_ate empty_ate;
uint8_t cycle_cnt;
int rc = 0;
uint64_t previous_ate_wra;
addr &= ADDR_SECT_MASK;
LOG_DBG("Adding empty ate at %llx", (uint64_t)(addr + fs->sector_size - fs->ate_size));
empty_ate.id = ZMS_HEAD_ID;
empty_ate.len = 0xffff;
empty_ate.offset = 0U;
empty_ate.metadata =
FIELD_PREP(ZMS_MAGIC_NUMBER_MASK, ZMS_MAGIC_NUMBER) | ZMS_DEFAULT_VERSION;
rc = zms_get_sector_cycle(fs, addr, &cycle_cnt);
if (rc == -ENOENT) {
/* sector never used */
cycle_cnt = 0;
} else if (rc) {
/* bad flash read */
return rc;
}
/* increase cycle counter */
empty_ate.cycle_cnt = (cycle_cnt + 1) % BIT(8);
zms_ate_crc8_update(&empty_ate);
/* Adding empty ate to this sector changes fs->ate_wra value
* Restore the ate_wra of the current sector after this
*/
previous_ate_wra = fs->ate_wra;
fs->ate_wra = zms_empty_ate_addr(fs, addr);
rc = zms_flash_ate_wrt(fs, &empty_ate);
if (rc) {
return rc;
}
fs->ate_wra = previous_ate_wra;
return 0;
}
static int zms_get_sector_cycle(struct zms_fs *fs, uint64_t addr, uint8_t *cycle_cnt)
{
int rc;
struct zms_ate empty_ate;
uint64_t empty_addr;
empty_addr = zms_empty_ate_addr(fs, addr);
/* read the cycle counter of the current sector */
rc = zms_flash_ate_rd(fs, empty_addr, &empty_ate);
if (rc < 0) {
/* flash error */
return rc;
}
if (zms_empty_ate_valid(fs, &empty_ate)) {
*cycle_cnt = empty_ate.cycle_cnt;
return 0;
}
/* there is no empty ATE in this sector */
return -ENOENT;
}
static int zms_get_sector_header(struct zms_fs *fs, uint64_t addr, struct zms_ate *empty_ate,
struct zms_ate *close_ate)
{
int rc;
uint64_t close_addr;
close_addr = zms_close_ate_addr(fs, addr);
/* read the second ate in the sector to get the close ATE */
rc = zms_flash_ate_rd(fs, close_addr, close_ate);
if (rc) {
return rc;
}
/* read the first ate in the sector to get the empty ATE */
rc = zms_flash_ate_rd(fs, close_addr + fs->ate_size, empty_ate);
if (rc) {
return rc;
}
return 0;
}
/**
* @brief Helper to find an ATE using its ID
*
* @param fs Pointer to file system
* @param id Id of the entry to be found
* @param start_addr Address from where the search will start
* @param end_addr Address where the search will stop
* @param ate pointer to the found ATE if it exists
* @param ate_addr Pointer to the address of the found ATE
*
* @retval 0 No ATE is found
* @retval 1 valid ATE with same ID found
* @retval < 0 An error happened
*/
static int zms_find_ate_with_id(struct zms_fs *fs, uint32_t id, uint64_t start_addr,
uint64_t end_addr, struct zms_ate *ate, uint64_t *ate_addr)
{
int rc;
int previous_sector_num = ZMS_INVALID_SECTOR_NUM;
uint64_t wlk_prev_addr;
uint64_t wlk_addr;
int prev_found = 0;
struct zms_ate wlk_ate;
uint8_t current_cycle;
wlk_addr = start_addr;
do {
wlk_prev_addr = wlk_addr;
rc = zms_prev_ate(fs, &wlk_addr, &wlk_ate);
if (rc) {
return rc;
}
if (wlk_ate.id == id) {
/* read the ate cycle only when we change the sector or if it is
* the first read ( previous_sector_num == ZMS_INVALID_SECTOR_NUM).
*/
rc = zms_get_cycle_on_sector_change(fs, wlk_prev_addr, previous_sector_num,
&current_cycle);
if (rc) {
return rc;
}
if (zms_ate_valid_different_sector(fs, &wlk_ate, current_cycle)) {
prev_found = 1;
break;
}
previous_sector_num = SECTOR_NUM(wlk_prev_addr);
}
} while (wlk_addr != end_addr);
*ate = wlk_ate;
*ate_addr = wlk_prev_addr;
return prev_found;
}
/* 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 zms_gc(struct zms_fs *fs)
{
int rc;
int sec_closed;
struct zms_ate close_ate;
struct zms_ate gc_ate;
struct zms_ate wlk_ate;
struct zms_ate empty_ate;
uint64_t sec_addr;
uint64_t gc_addr;
uint64_t gc_prev_addr;
uint64_t wlk_addr;
uint64_t wlk_prev_addr;
uint64_t data_addr;
uint64_t stop_addr;
uint8_t previous_cycle = 0;
rc = zms_get_sector_cycle(fs, fs->ate_wra, &fs->sector_cycle);
if (rc == -ENOENT) {
/* Erase this new unused sector if needed */
rc = zms_flash_erase_sector(fs, fs->ate_wra);
if (rc) {
return rc;
}
/* sector never used */
rc = zms_add_empty_ate(fs, fs->ate_wra);
if (rc) {
return rc;
}
/* At this step we are sure that empty ATE exist.
* If not, then there is an I/O problem.
*/
rc = zms_get_sector_cycle(fs, fs->ate_wra, &fs->sector_cycle);
if (rc) {
return rc;
}
} else if (rc) {
/* bad flash read */
return rc;
}
previous_cycle = fs->sector_cycle;
sec_addr = (fs->ate_wra & ADDR_SECT_MASK);
zms_sector_advance(fs, &sec_addr);
gc_addr = sec_addr + fs->sector_size - fs->ate_size;
/* verify if the sector is closed */
sec_closed = zms_validate_closed_sector(fs, gc_addr, &empty_ate, &close_ate);
if (sec_closed < 0) {
return sec_closed;
}
/* if the sector is not closed don't do gc */
if (!sec_closed) {
goto gc_done;
}
/* update sector_cycle */
fs->sector_cycle = empty_ate.cycle_cnt;
/* stop_addr points to the first ATE before the header ATEs */
stop_addr = gc_addr - 2 * fs->ate_size;
/* At this step empty & close ATEs are valid.
* let's start the GC
*/
gc_addr &= ADDR_SECT_MASK;
gc_addr += close_ate.offset;
do {
gc_prev_addr = gc_addr;
rc = zms_prev_ate(fs, &gc_addr, &gc_ate);
if (rc) {
return rc;
}
if (!zms_ate_valid(fs, &gc_ate) || !gc_ate.len) {
continue;
}
#ifdef CONFIG_ZMS_LOOKUP_CACHE
wlk_addr = fs->lookup_cache[zms_lookup_cache_pos(gc_ate.id)];
if (wlk_addr == ZMS_LOOKUP_CACHE_NO_ADDR) {
wlk_addr = fs->ate_wra;
}
#else
wlk_addr = fs->ate_wra;
#endif
/* Initialize the wlk_prev_addr as if no previous ID will be found */
wlk_prev_addr = gc_prev_addr;
/* Search for a previous valid ATE with the same ID. If it doesn't exist
* then wlk_prev_addr will be equal to gc_prev_addr.
*/
rc = zms_find_ate_with_id(fs, gc_ate.id, wlk_addr, fs->ate_wra, &wlk_ate,
&wlk_prev_addr);
if (rc < 0) {
return rc;
}
/* if walk_addr has reached the same address as gc_addr, a copy is
* needed unless it is a deleted item.
*/
if (wlk_prev_addr == gc_prev_addr) {
/* copy needed */
LOG_DBG("Moving %d, len %d", gc_ate.id, gc_ate.len);
if (gc_ate.len > ZMS_DATA_IN_ATE_SIZE) {
/* Copy Data only when len > 8
* Otherwise, Data is already inside ATE
*/
data_addr = (gc_prev_addr & ADDR_SECT_MASK);
data_addr += gc_ate.offset;
gc_ate.offset = (uint32_t)SECTOR_OFFSET(fs->data_wra);
rc = zms_flash_block_move(fs, data_addr, gc_ate.len);
if (rc) {
return rc;
}
}
gc_ate.cycle_cnt = previous_cycle;
zms_ate_crc8_update(&gc_ate);
rc = zms_flash_ate_wrt(fs, &gc_ate);
if (rc) {
return rc;
}
}
} while (gc_prev_addr != stop_addr);
gc_done:
/* restore the previous sector_cycle */
fs->sector_cycle = previous_cycle;
/* Write a GC_done ATE to mark the end of this operation
*/
rc = zms_add_gc_done_ate(fs);
if (rc) {
return rc;
}
/* Erase the GC'ed sector when needed */
rc = zms_flash_erase_sector(fs, sec_addr);
if (rc) {
return rc;
}
#ifdef CONFIG_ZMS_LOOKUP_CACHE
zms_lookup_cache_invalidate(fs, sec_addr >> ADDR_SECT_SHIFT);
#endif
rc = zms_add_empty_ate(fs, sec_addr);
return rc;
}
int zms_clear(struct zms_fs *fs)
{
int rc;
uint64_t addr;
if (!fs->ready) {
LOG_ERR("zms not initialized");
return -EACCES;
}
k_mutex_lock(&fs->zms_lock, K_FOREVER);
for (uint32_t i = 0; i < fs->sector_count; i++) {
addr = (uint64_t)i << ADDR_SECT_SHIFT;
rc = zms_flash_erase_sector(fs, addr);
if (rc) {
goto end;
}
rc = zms_add_empty_ate(fs, addr);
if (rc) {
goto end;
}
}
/* zms needs to be reinitialized after clearing */
fs->ready = false;
end:
k_mutex_unlock(&fs->zms_lock);
return 0;
}
static int zms_init(struct zms_fs *fs)
{
int rc;
int sec_closed;
struct zms_ate last_ate;
struct zms_ate first_ate;
struct zms_ate close_ate;
struct zms_ate empty_ate;
uint64_t addr = 0U;
uint64_t data_wra = 0U;
uint32_t i;
uint32_t closed_sectors = 0;
bool zms_magic_exist = false;
k_mutex_lock(&fs->zms_lock, K_FOREVER);
/* step through the sectors to find a open sector following
* a closed sector, this is where zms can write.
*/
for (i = 0; i < fs->sector_count; i++) {
addr = zms_close_ate_addr(fs, ((uint64_t)i << ADDR_SECT_SHIFT));
/* verify if the sector is closed */
sec_closed = zms_validate_closed_sector(fs, addr, &empty_ate, &close_ate);
if (sec_closed < 0) {
rc = sec_closed;
goto end;
}
/* update cycle count */
fs->sector_cycle = empty_ate.cycle_cnt;
if (sec_closed == 1) {
/* closed sector */
closed_sectors++;
/* Let's verify that this is a ZMS storage system */
if (ZMS_GET_MAGIC_NUMBER(empty_ate.metadata) == ZMS_MAGIC_NUMBER) {
zms_magic_exist = true;
/* Let's check that we support this ZMS version */
if (ZMS_GET_VERSION(empty_ate.metadata) != ZMS_DEFAULT_VERSION) {
LOG_ERR("ZMS Version is not supported");
rc = -ENOEXEC;
goto end;
}
}
zms_sector_advance(fs, &addr);
/* addr is pointing to the close ATE */
/* verify if the sector is Open */
sec_closed = zms_validate_closed_sector(fs, addr, &empty_ate, &close_ate);
if (sec_closed < 0) {
rc = sec_closed;
goto end;
}
/* update cycle count */
fs->sector_cycle = empty_ate.cycle_cnt;
if (!sec_closed) {
/* We found an Open sector following a closed one */
break;
}
}
}
/* all sectors are closed, and zms magic number not found. This is not a zms fs */
if ((closed_sectors == fs->sector_count) && !zms_magic_exist) {
rc = -EDEADLK;
goto end;
}
/* TODO: add a recovery mechanism here if the ZMS magic number exist but all
* sectors are closed
*/
if (i == fs->sector_count) {
/* none of the sectors were closed, which means that the first
* sector is the one in use, except if there are only 2 sectors.
* Let's check if the last sector has valid ATEs otherwise set
* the open sector to the first one.
*/
rc = zms_flash_ate_rd(fs, addr - fs->ate_size, &first_ate);
if (rc) {
goto end;
}
if (!zms_ate_valid(fs, &first_ate)) {
zms_sector_advance(fs, &addr);
}
rc = zms_get_sector_header(fs, addr, &empty_ate, &close_ate);
if (rc) {
goto end;
}
if (zms_empty_ate_valid(fs, &empty_ate)) {
/* Empty ATE is valid, let's verify that this is a ZMS storage system */
if (ZMS_GET_MAGIC_NUMBER(empty_ate.metadata) == ZMS_MAGIC_NUMBER) {
zms_magic_exist = true;
/* Let's check the version */
if (ZMS_GET_VERSION(empty_ate.metadata) != ZMS_DEFAULT_VERSION) {
LOG_ERR("ZMS Version is not supported");
rc = -ENOEXEC;
goto end;
}
}
} else {
rc = zms_flash_erase_sector(fs, addr);
if (rc) {
goto end;
}
rc = zms_add_empty_ate(fs, addr);
if (rc) {
goto end;
}
}
rc = zms_get_sector_cycle(fs, addr, &fs->sector_cycle);
if (rc == -ENOENT) {
/* sector never used */
fs->sector_cycle = 0;
} else if (rc) {
/* bad flash read */
goto end;
}
}
/* addr contains address of closing ate in the most recent sector,
* search for the last valid ate using the recover_last_ate routine
* and also update the data_wra
*/
rc = zms_recover_last_ate(fs, &addr, &data_wra);
if (rc) {
goto end;
}
/* addr contains address of the last valid ate in the most recent sector
* data_wra contains the data write address of the current sector
*/
fs->ate_wra = addr;
fs->data_wra = data_wra;
/* fs->ate_wra should point to the next available entry. This is normally
* the next position after the one found by the recovery function.
* Let's verify that it doesn't contain any valid ATE, otherwise search for
* an empty position
*/
while (fs->ate_wra >= fs->data_wra) {
rc = zms_flash_ate_rd(fs, fs->ate_wra, &last_ate);
if (rc) {
goto end;
}
if (!zms_ate_valid(fs, &last_ate)) {
/* found empty location */
break;
}
/* 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 -= fs->ate_size;
}
/* The sector after the write sector is either empty with a valid empty ATE (regular case)
* or it has never been used or it is a closed sector (GC didn't finish)
* If it is a closed sector we must look for a valid GC done ATE in the current write
* sector, if it is missing, we need to restart gc because it has been interrupted.
* If no valid empty ATE is found then it has never been used. Just erase it by adding
* a valid empty ATE.
* When gc needs to be restarted, first erase the sector by adding an empty
* ATE otherwise the data might not fit into the sector.
*/
addr = zms_close_ate_addr(fs, fs->ate_wra);
zms_sector_advance(fs, &addr);
/* verify if the sector is closed */
sec_closed = zms_validate_closed_sector(fs, addr, &empty_ate, &close_ate);
if (sec_closed < 0) {
rc = sec_closed;
goto end;
}
if (sec_closed == 1) {
/* The sector after fs->ate_wrt is closed.
* Look for a marker (gc_done_ate) that indicates that gc was finished.
*/
bool gc_done_marker = false;
struct zms_ate gc_done_ate;
fs->sector_cycle = empty_ate.cycle_cnt;
addr = fs->ate_wra + fs->ate_size;
while (SECTOR_OFFSET(addr) < (fs->sector_size - 2 * fs->ate_size)) {
rc = zms_flash_ate_rd(fs, addr, &gc_done_ate);
if (rc) {
goto end;
}
if (zms_gc_done_ate_valid(fs, &gc_done_ate)) {
break;
}
addr += fs->ate_size;
}
if (gc_done_marker) {
/* erase the next sector */
LOG_INF("GC Done marker found");
addr = fs->ate_wra & ADDR_SECT_MASK;
zms_sector_advance(fs, &addr);
rc = zms_flash_erase_sector(fs, addr);
if (rc < 0) {
goto end;
}
rc = zms_add_empty_ate(fs, addr);
goto end;
}
LOG_INF("No GC Done marker found: restarting gc");
rc = zms_flash_erase_sector(fs, fs->ate_wra);
if (rc) {
goto end;
}
rc = zms_add_empty_ate(fs, fs->ate_wra);
if (rc) {
goto end;
}
/* Let's point to the first writable position */
fs->ate_wra &= ADDR_SECT_MASK;
fs->ate_wra += (fs->sector_size - 3 * fs->ate_size);
fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK);
#ifdef CONFIG_ZMS_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_ZMS_LOOKUP_CACHE_SIZE; i++) {
fs->lookup_cache[i] = fs->ate_wra;
}
#endif
rc = zms_gc(fs);
goto end;
}
end:
#ifdef CONFIG_ZMS_LOOKUP_CACHE
if (!rc) {
rc = zms_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) && (SECTOR_OFFSET(fs->ate_wra) == (fs->sector_size - 3 * fs->ate_size))) {
rc = zms_add_gc_done_ate(fs);
}
k_mutex_unlock(&fs->zms_lock);
return rc;
}
int zms_mount(struct zms_fs *fs)
{
int rc;
struct flash_pages_info info;
size_t write_block_size;
k_mutex_init(&fs->zms_lock);
fs->flash_parameters = flash_get_parameters(fs->flash_device);
if (fs->flash_parameters == NULL) {
LOG_ERR("Could not obtain flash parameters");
return -EINVAL;
}
fs->ate_size = zms_al_size(fs, sizeof(struct zms_ate));
write_block_size = fs->flash_parameters->write_block_size;
/* check that the write block size is supported */
if (write_block_size > ZMS_BLOCK_SIZE || write_block_size == 0) {
LOG_ERR("Unsupported write block size");
return -EINVAL;
}
/* When the device need erase operations before write let's check that
* sector size is a multiple of pagesize
*/
if (flash_params_get_erase_cap(fs->flash_parameters) & FLASH_ERASE_C_EXPLICIT) {
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;
}
}
/* we need at least 5 aligned ATEs size as the minimum sector size
* 1 close ATE, 1 empty ATE, 1 GC done ATE, 1 Delete ATE, 1 ID/Value ATE
*/
if (fs->sector_size < ZMS_MIN_ATE_NUM * fs->ate_size) {
LOG_ERR("Invalid sector size, should be at least %zu",
ZMS_MIN_ATE_NUM * fs->ate_size);
}
/* check the number of sectors, it should be at least 2 */
if (fs->sector_count < 2) {
LOG_ERR("Configuration error - sector count below minimum requirement (2)");
return -EINVAL;
}
rc = zms_init(fs);
if (rc) {
return rc;
}
/* zms is ready for use */
fs->ready = true;
LOG_INF("%u Sectors of %u bytes", fs->sector_count, fs->sector_size);
LOG_INF("alloc wra: %llu, %llx", SECTOR_NUM(fs->ate_wra), SECTOR_OFFSET(fs->ate_wra));
LOG_INF("data wra: %llu, %llx", SECTOR_NUM(fs->data_wra), SECTOR_OFFSET(fs->data_wra));
return 0;
}
ssize_t zms_write(struct zms_fs *fs, uint32_t id, const void *data, size_t len)
{
int rc;
size_t data_size;
uint64_t wlk_addr;
uint64_t rd_addr;
uint32_t gc_count;
uint32_t required_space = 0U; /* no space, appropriate for delete ate */
if (!fs->ready) {
LOG_ERR("zms not initialized");
return -EACCES;
}
data_size = zms_al_size(fs, len);
/* The maximum data size is sector size - 5 ate
* where: 1 ate for data, 1 ate for sector close, 1 ate for empty,
* 1 ate for gc done, and 1 ate to always allow a delete.
* We cannot also store more than 64 KB of data
*/
if ((len > (fs->sector_size - 5 * fs->ate_size)) || (len > UINT16_MAX) ||
((len > 0) && (data == NULL))) {
return -EINVAL;
}
/* find latest entry with same id */
#ifdef CONFIG_ZMS_LOOKUP_CACHE
wlk_addr = fs->lookup_cache[zms_lookup_cache_pos(id)];
if (wlk_addr == ZMS_LOOKUP_CACHE_NO_ADDR) {
goto no_cached_entry;
}
#else
wlk_addr = fs->ate_wra;
#endif
rd_addr = wlk_addr;
#ifdef CONFIG_ZMS_NO_DOUBLE_WRITE
/* Search for a previous valid ATE with the same ID */
struct zms_ate wlk_ate;
int prev_found = zms_find_ate_with_id(fs, id, wlk_addr, fs->ate_wra, &wlk_ate, &rd_addr);
if (prev_found < 0) {
return prev_found;
}
if (prev_found) {
/* previous entry found */
if (len > ZMS_DATA_IN_ATE_SIZE) {
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 == wlk_ate.len) {
/* do not try to compare if lengths are not equal */
/* compare the data and if equal return 0 */
if (len <= ZMS_DATA_IN_ATE_SIZE) {
rc = memcmp(&wlk_ate.data, data, len);
if (!rc) {
return 0;
}
} else {
rc = zms_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;
}
}
#endif
#ifdef CONFIG_ZMS_LOOKUP_CACHE
no_cached_entry:
#endif
/* calculate required space if the entry contains data */
if (data_size) {
/* Leave space for delete ate */
if (len > ZMS_DATA_IN_ATE_SIZE) {
required_space = data_size + fs->ate_size;
} else {
required_space = fs->ate_size;
}
}
k_mutex_lock(&fs->zms_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;
}
/* We need to make sure that we leave the ATE at address 0x0 of the sector
* empty (even for delete ATE). Otherwise, the fs->ate_wra will be decremented
* after this write by ate_size and it will underflow.
* So the first position of a sector (fs->ate_wra = 0x0) is forbidden for ATEs
* and the second position could be written only be a delete ATE.
*/
if ((SECTOR_OFFSET(fs->ate_wra)) &&
(fs->ate_wra >= (fs->data_wra + required_space)) &&
(SECTOR_OFFSET(fs->ate_wra - fs->ate_size) || !len)) {
rc = zms_flash_write_entry(fs, id, data, len);
if (rc) {
goto end;
}
break;
}
rc = zms_sector_close(fs);
if (rc) {
LOG_ERR("Failed to close the sector, returned = %d", rc);
goto end;
}
rc = zms_gc(fs);
if (rc) {
LOG_ERR("Garbage collection failed, returned = %d", rc);
goto end;
}
gc_count++;
}
rc = len;
end:
k_mutex_unlock(&fs->zms_lock);
return rc;
}
int zms_delete(struct zms_fs *fs, uint32_t id)
{
return zms_write(fs, id, NULL, 0);
}
ssize_t zms_read_hist(struct zms_fs *fs, uint32_t id, void *data, size_t len, uint32_t cnt)
{
int rc;
int prev_found = 0;
uint64_t wlk_addr;
uint64_t rd_addr = 0;
uint64_t wlk_prev_addr = 0;
uint32_t cnt_his;
struct zms_ate wlk_ate;
#ifdef CONFIG_ZMS_DATA_CRC
uint32_t computed_data_crc;
#endif
if (!fs->ready) {
LOG_ERR("zms not initialized");
return -EACCES;
}
cnt_his = 0U;
#ifdef CONFIG_ZMS_LOOKUP_CACHE
wlk_addr = fs->lookup_cache[zms_lookup_cache_pos(id)];
if (wlk_addr == ZMS_LOOKUP_CACHE_NO_ADDR) {
rc = -ENOENT;
goto err;
}
#else
wlk_addr = fs->ate_wra;
#endif
while (cnt_his <= cnt) {
wlk_prev_addr = wlk_addr;
/* Search for a previous valid ATE with the same ID */
prev_found = zms_find_ate_with_id(fs, id, wlk_addr, fs->ate_wra, &wlk_ate,
&wlk_prev_addr);
if (prev_found < 0) {
return prev_found;
}
if (prev_found) {
cnt_his++;
/* wlk_prev_addr contain the ATE address of the previous found ATE. */
rd_addr = wlk_prev_addr;
/*
* compute the previous ATE address in case we need to start
* the research again.
*/
rc = zms_compute_prev_addr(fs, &wlk_prev_addr);
if (rc) {
return rc;
}
/* wlk_addr will be the start research address in the next loop */
wlk_addr = wlk_prev_addr;
} else {
break;
}
}
if (((!prev_found) || (wlk_ate.id != id)) || (wlk_ate.len == 0U) || (cnt_his < cnt)) {
return -ENOENT;
}
if (wlk_ate.len <= ZMS_DATA_IN_ATE_SIZE) {
/* data is stored in the ATE */
if (data) {
memcpy(data, &wlk_ate.data, MIN(len, wlk_ate.len));
}
} else {
rd_addr &= ADDR_SECT_MASK;
rd_addr += wlk_ate.offset;
/* do not read or copy data if pointer is NULL */
if (data) {
rc = zms_flash_rd(fs, rd_addr, data, MIN(len, wlk_ate.len));
if (rc) {
goto err;
}
}
#ifdef CONFIG_ZMS_DATA_CRC
/* Do not compute CRC for partial reads as CRC won't match */
if (len >= wlk_ate.len) {
computed_data_crc = crc32_ieee(data, wlk_ate.len);
if (computed_data_crc != wlk_ate.data_crc) {
LOG_ERR("Invalid data CRC: ATE_CRC=0x%08X, "
"computed_data_crc=0x%08X",
wlk_ate.data_crc, computed_data_crc);
return -EIO;
}
}
#endif
}
return wlk_ate.len;
err:
return rc;
}
ssize_t zms_read(struct zms_fs *fs, uint32_t id, void *data, size_t len)
{
int rc;
rc = zms_read_hist(fs, id, data, len, 0);
if (rc < 0) {
return rc;
}
/* returns the minimum between ATE data length and requested length */
return MIN(rc, len);
}
ssize_t zms_get_data_length(struct zms_fs *fs, uint32_t id)
{
int rc;
rc = zms_read_hist(fs, id, NULL, 0, 0);
return rc;
}
ssize_t zms_calc_free_space(struct zms_fs *fs)
{
int rc;
int previous_sector_num = ZMS_INVALID_SECTOR_NUM;
int prev_found = 0;
int sec_closed;
struct zms_ate step_ate;
struct zms_ate wlk_ate;
struct zms_ate empty_ate;
struct zms_ate close_ate;
uint64_t step_addr;
uint64_t wlk_addr;
uint64_t step_prev_addr;
uint64_t wlk_prev_addr;
uint64_t data_wra = 0U;
uint8_t current_cycle;
ssize_t free_space = 0;
const uint32_t second_to_last_offset = (2 * fs->ate_size);
if (!fs->ready) {
LOG_ERR("zms not initialized");
return -EACCES;
}
/*
* There is always a closing ATE , an empty ATE, a GC_done ATE and a reserved ATE for
* deletion in each sector.
* And there is always one reserved Sector for garbage collection operations
*/
free_space = (fs->sector_count - 1) * (fs->sector_size - 4 * fs->ate_size);
step_addr = fs->ate_wra;
do {
step_prev_addr = step_addr;
rc = zms_prev_ate(fs, &step_addr, &step_ate);
if (rc) {
return rc;
}
/* When changing the sector let's get the new cycle counter */
rc = zms_get_cycle_on_sector_change(fs, step_prev_addr, previous_sector_num,
&current_cycle);
if (rc) {
return rc;
}
previous_sector_num = SECTOR_NUM(step_prev_addr);
/* Invalid and deleted ATEs are free spaces.
* Header ATEs are already retrieved from free space
*/
if (!zms_ate_valid_different_sector(fs, &step_ate, current_cycle) ||
(step_ate.id == ZMS_HEAD_ID) || (step_ate.len == 0)) {
continue;
}
wlk_addr = step_addr;
/* Try to find if there is a previous valid ATE with same ID */
prev_found = zms_find_ate_with_id(fs, step_ate.id, wlk_addr, step_addr, &wlk_ate,
&wlk_prev_addr);
if (prev_found < 0) {
return prev_found;
}
/* If no previous ATE is found, then this is a valid ATE that cannot be
* Garbage Collected
*/
if (!prev_found || (wlk_prev_addr == step_prev_addr)) {
if (step_ate.len > ZMS_DATA_IN_ATE_SIZE) {
free_space -= zms_al_size(fs, step_ate.len);
}
free_space -= fs->ate_size;
}
} while (step_addr != fs->ate_wra);
/* we must keep the sector_cycle before we start looking into special cases */
current_cycle = fs->sector_cycle;
/* Let's look now for special cases where some sectors have only ATEs with
* small data size.
*/
for (int i = 0; i < fs->sector_count; i++) {
step_addr = zms_close_ate_addr(fs, ((uint64_t)i << ADDR_SECT_SHIFT));
/* verify if the sector is closed */
sec_closed = zms_validate_closed_sector(fs, step_addr, &empty_ate, &close_ate);
if (sec_closed < 0) {
return sec_closed;
}
/* If the sector is closed and its offset is pointing to a position less than the
* 3rd to last ATE position in a sector, it means that we need to leave the second
* to last ATE empty.
*/
if ((sec_closed == 1) && (close_ate.offset <= second_to_last_offset)) {
free_space -= fs->ate_size;
} else if (!sec_closed) {
/* sector is open, let's recover the last ATE */
fs->sector_cycle = empty_ate.cycle_cnt;
rc = zms_recover_last_ate(fs, &step_addr, &data_wra);
if (rc) {
return rc;
}
if (SECTOR_OFFSET(step_addr) <= second_to_last_offset) {
free_space -= fs->ate_size;
}
}
}
/* restore sector cycle */
fs->sector_cycle = current_cycle;
return free_space;
}
size_t zms_active_sector_free_space(struct zms_fs *fs)
{
if (!fs->ready) {
LOG_ERR("ZMS not initialized");
return -EACCES;
}
return fs->ate_wra - fs->data_wra - fs->ate_size;
}
int zms_sector_use_next(struct zms_fs *fs)
{
int ret;
if (!fs->ready) {
LOG_ERR("ZMS not initialized");
return -EACCES;
}
k_mutex_lock(&fs->zms_lock, K_FOREVER);
ret = zms_sector_close(fs);
if (ret != 0) {
goto end;
}
ret = zms_gc(fs);
end:
k_mutex_unlock(&fs->zms_lock);
return ret;
}