This resolves some addressed comments in this PR https://github.com/zephyrproject-rtos/zephyr/pull/77930 It adds as well a section in the documentation about some recommendations to increase ZMS performance. Signed-off-by: Riadh Ghaddab <rghaddab@baylibre.com>
1803 lines
45 KiB
C
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, ¤t_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,
|
|
¤t_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 %u",
|
|
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;
|
|
struct zms_ate wlk_ate;
|
|
uint64_t wlk_addr;
|
|
uint64_t rd_addr;
|
|
uint32_t gc_count;
|
|
uint32_t required_space = 0U; /* no space, appropriate for delete ate */
|
|
int prev_found = 0;
|
|
|
|
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;
|
|
|
|
/* 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, &rd_addr);
|
|
if (prev_found < 0) {
|
|
return prev_found;
|
|
}
|
|
|
|
#ifdef CONFIG_ZMS_LOOKUP_CACHE
|
|
no_cached_entry:
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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,
|
|
¤t_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;
|
|
}
|