From e2abed1f43143b1e291d7b7d34468f802ebd6d0c Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Fri, 18 Oct 2024 08:28:28 +0200 Subject: [PATCH 1/4] Revert "[nrf noup] settings: zms: use dedicated lookup cache hash function" This reverts commit 98662fcd51860d23e15471744658f817e34a85ed. Signed-off-by: Riadh Ghaddab --- subsys/fs/zms/Kconfig | 9 --------- subsys/fs/zms/zms.c | 45 ------------------------------------------- 2 files changed, 54 deletions(-) diff --git a/subsys/fs/zms/Kconfig b/subsys/fs/zms/Kconfig index c9514608da8..bc1dae78279 100644 --- a/subsys/fs/zms/Kconfig +++ b/subsys/fs/zms/Kconfig @@ -51,15 +51,6 @@ config ZMS_MAX_BLOCK_SIZE help Changes the internal buffer size of ZMS -config ZMS_LOOKUP_CACHE_FOR_SETTINGS - bool "ZMS Storage lookup cache optimized for settings" - depends on ZMS_LOOKUP_CACHE - help - Use the lookup cache hash function that results in the least number of - collissions and, in turn, the best ZMS performance provided that the ZMS - is used as the settings backend only. This option should NOT be enabled - if the ZMS is also written to directly, outside the settings layer. - module = ZMS module-str = zms source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/fs/zms/zms.c b/subsys/fs/zms/zms.c index 32aafcfbe04..891af027916 100644 --- a/subsys/fs/zms/zms.c +++ b/subsys/fs/zms/zms.c @@ -11,10 +11,6 @@ #include #include #include "zms_priv.h" -#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS -#include -#include -#endif #include LOG_MODULE_REGISTER(fs_zms, CONFIG_ZMS_LOG_LEVEL); @@ -29,45 +25,6 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at #ifdef CONFIG_ZMS_LOOKUP_CACHE -#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS - -static inline size_t zms_lookup_cache_pos(uint32_t id) -{ - /* - * 1. The ZMS settings backend uses up to (ZMS_NAME_ID_OFFSET - 1) ZMS IDs to - store keys and equal number of ZMS IDs to store values. - * 2. For each key-value pair, the value is stored at ZMS ID greater by exactly - * ZMS_NAME_ID_OFFSET than ZMS ID that holds the key. - * 3. The backend tries to minimize the range of ZMS IDs used to store keys. - * That is, ZMS IDs are allocated sequentially, and freed ZMS IDs are reused - * before allocating new ones. - * - * Therefore, to assure the least number of collisions in the lookup cache, - * the least significant bit of the hash indicates whether the given ZMS ID - * represents a key or a value, and remaining bits of the hash are set to - * the ordinal number of the key-value pair. Consequently, the hash function - * provides the following mapping: - * - * 1st settings key => hash 0 - * 1st settings value => hash 1 - * 2nd settings key => hash 2 - * 2nd settings value => hash 3 - * ... - */ - BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_NAMECNT_ID), "ZMS_NAMECNT_ID is not power of 2"); - BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_NAME_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2"); - - uint32_t key_value_bit; - uint32_t key_value_ord; - - key_value_bit = (id >> LOG2(ZMS_NAME_ID_OFFSET)) & 1; - key_value_ord = id & (ZMS_NAME_ID_OFFSET - 1); - - return ((key_value_ord << 1) | key_value_bit) % CONFIG_ZMS_LOOKUP_CACHE_SIZE; -} - -#else /* CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS */ - static inline size_t zms_lookup_cache_pos(uint32_t id) { uint32_t hash; @@ -83,8 +40,6 @@ static inline size_t zms_lookup_cache_pos(uint32_t id) return hash % CONFIG_ZMS_LOOKUP_CACHE_SIZE; } -#endif /* CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS */ - static int zms_lookup_cache_rebuild(struct zms_fs *fs) { int rc, previous_sector_num = ZMS_INVALID_SECTOR_NUM; From 7b117a46d622a9e51260584d49dad4ff7ae55210 Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Fri, 18 Oct 2024 08:28:40 +0200 Subject: [PATCH 2/4] Revert "[nrf fromlist] fs: introduce ZMS a new Memory storage system" This reverts commit 6e31f3a53cf7540f066e3ec101d6f8dc40086c8d. Signed-off-by: Riadh Ghaddab --- include/zephyr/fs/zms.h | 212 ---- subsys/fs/CMakeLists.txt | 1 - subsys/fs/Kconfig | 1 - subsys/fs/zms/CMakeLists.txt | 3 - subsys/fs/zms/Kconfig | 58 -- subsys/fs/zms/zms.c | 1804 ---------------------------------- subsys/fs/zms/zms_priv.h | 76 -- 7 files changed, 2155 deletions(-) delete mode 100644 include/zephyr/fs/zms.h delete mode 100644 subsys/fs/zms/CMakeLists.txt delete mode 100644 subsys/fs/zms/Kconfig delete mode 100644 subsys/fs/zms/zms.c delete mode 100644 subsys/fs/zms/zms_priv.h diff --git a/include/zephyr/fs/zms.h b/include/zephyr/fs/zms.h deleted file mode 100644 index cfd1e79d6df..00000000000 --- a/include/zephyr/fs/zms.h +++ /dev/null @@ -1,212 +0,0 @@ -/* ZMS: Zephyr Memory Storage - * - * Copyright (c) 2024 BayLibre SAS - * - * SPDX-License-Identifier: Apache-2.0 - */ -#ifndef ZEPHYR_INCLUDE_FS_ZMS_H_ -#define ZEPHYR_INCLUDE_FS_ZMS_H_ - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Zephyr Memory Storage (ZMS) - * @defgroup zms Zephyr Memory Storage (ZMS) - * @ingroup file_system_storage - * @{ - * @} - */ - -/** - * @brief Zephyr Memory Storage Data Structures - * @defgroup zms_data_structures Zephyr Memory Storage Data Structures - * @ingroup zms - * @{ - */ - -/** - * @brief Zephyr Memory Storage File system structure - */ -struct zms_fs { - /** File system offset in flash **/ - off_t offset; - /** Allocation table entry write address. - * Addresses are stored as uint64_t: - * - high 4 bytes correspond to the sector - * - low 4 bytes are the offset in the sector - */ - uint64_t ate_wra; - /** Data write address */ - uint64_t data_wra; - /** Storage system is split into sectors, each sector size must be multiple of erase-blocks - * if the device has erase capabilities - */ - uint32_t sector_size; - /** Number of sectors in the file system */ - uint32_t sector_count; - /** Current cycle counter of the active sector (pointed by ate_wra)*/ - uint8_t sector_cycle; - /** Flag indicating if the file system is initialized */ - bool ready; - /** Mutex */ - struct k_mutex zms_lock; - /** Flash device runtime structure */ - const struct device *flash_device; - /** Flash memory parameters structure */ - const struct flash_parameters *flash_parameters; -#if CONFIG_ZMS_LOOKUP_CACHE - uint64_t lookup_cache[CONFIG_ZMS_LOOKUP_CACHE_SIZE]; -#endif -}; - -/** - * @} - */ - -/** - * @brief Zephyr Memory Storage APIs - * @defgroup zms_high_level_api Zephyr Memory Storage APIs - * @ingroup zms - * @{ - */ - -/** - * @brief Mount a ZMS file system onto the device specified in @p fs. - * - * @param fs Pointer to file system - * @retval 0 Success - * @retval -ERRNO errno code if error - */ -int zms_mount(struct zms_fs *fs); - -/** - * @brief Clear the ZMS file system from device. - * - * @param fs Pointer to file system - * @retval 0 Success - * @retval -ERRNO errno code if error - */ -int zms_clear(struct zms_fs *fs); - -/** - * @brief Write an entry to the file system. - * - * @note When @p len parameter is equal to @p 0 then entry is effectively removed (it is - * equivalent to calling of zms_delete). It is not possible to distinguish between a deleted - * entry and an entry with data of length 0. - * - * @param fs Pointer to file system - * @param id Id of the entry to be written - * @param data Pointer to the data to be written - * @param len Number of bytes to be written (maximum 64 KB) - * - * @return Number of bytes written. On success, it will be equal to the number of bytes requested - * to be written. When a rewrite of the same data already stored is attempted, nothing is written - * to flash, thus 0 is returned. On error, returns negative value of errno.h defined error codes. - */ -ssize_t zms_write(struct zms_fs *fs, uint32_t id, const void *data, size_t len); - -/** - * @brief Delete an entry from the file system - * - * @param fs Pointer to file system - * @param id Id of the entry to be deleted - * @retval 0 Success - * @retval -ERRNO errno code if error - */ -int zms_delete(struct zms_fs *fs, uint32_t id); - -/** - * @brief Read an entry from the file system. - * - * @param fs Pointer to file system - * @param id Id of the entry to be read - * @param data Pointer to data buffer - * @param len Number of bytes to be read (or size of the allocated read buffer) - * - * @return Number of bytes read. On success, it will be equal to the number of bytes requested - * to be read. When the return value is less than the number of bytes requested to read this - * indicates that ATE contain less data than requested. On error, returns negative value of - * errno.h defined error codes. - */ -ssize_t zms_read(struct zms_fs *fs, uint32_t id, void *data, size_t len); - -/** - * @brief Read a history entry from the file system. - * - * @param fs Pointer to file system - * @param id Id of the entry to be read - * @param data Pointer to data buffer - * @param len Number of bytes to be read - * @param cnt History counter: 0: latest entry, 1: one before latest ... - * - * @return Number of bytes read. On success, it will be equal to the number of bytes requested - * to be read. When the return value is larger than the number of bytes requested to read this - * indicates not all bytes were read, and more data is available. On error, returns negative - * value of errno.h defined error codes. - */ -ssize_t zms_read_hist(struct zms_fs *fs, uint32_t id, void *data, size_t len, uint32_t cnt); - -/** - * @brief Gets the data size that is stored in an entry with a given id - * - * @param fs Pointer to file system - * @param id Id of the entry that we want to get its data length - * - * @return Data length contained in the ATE. On success, it will be equal to the number of bytes - * in the ATE. On error, returns negative value of errno.h defined error codes. - */ -ssize_t zms_get_data_length(struct zms_fs *fs, uint32_t id); -/** - * @brief Calculate the available free space in the file system. - * - * @param fs Pointer to file system - * - * @return Number of bytes free. On success, it will be equal to the number of bytes that can - * still be written to the file system. - * Calculating the free space is a time consuming operation, especially on spi flash. - * On error, returns negative value of errno.h defined error codes. - */ -ssize_t zms_calc_free_space(struct zms_fs *fs); - -/** - * @brief Tell how many contiguous free space remains in the currently active ZMS sector. - * - * @param fs Pointer to the file system. - * - * @return Number of free bytes. - */ -size_t zms_sector_max_data_size(struct zms_fs *fs); - -/** - * @brief Close the currently active sector and switch to the next one. - * - * @note The garbage collector is called on the new sector. - * - * @warning This routine is made available for specific use cases. - * It collides with the ZMS goal of avoiding any unnecessary flash erase operations. - * Using this routine extensively can result in premature failure of the flash device. - * - * @param fs Pointer to the file system. - * - * @return 0 on success. On error, returns negative value of errno.h defined error codes. - */ -int zms_sector_use_next(struct zms_fs *fs); - -/** - * @} - */ - -#ifdef __cplusplus -} -#endif - -#endif /* ZEPHYR_INCLUDE_FS_ZMS_H_ */ diff --git a/subsys/fs/CMakeLists.txt b/subsys/fs/CMakeLists.txt index 825b8356381..fb774711543 100644 --- a/subsys/fs/CMakeLists.txt +++ b/subsys/fs/CMakeLists.txt @@ -25,7 +25,6 @@ endif() add_subdirectory_ifdef(CONFIG_FCB ./fcb) add_subdirectory_ifdef(CONFIG_NVS ./nvs) -add_subdirectory_ifdef(CONFIG_ZMS ./zms) if(CONFIG_FUSE_FS_ACCESS) zephyr_library_named(FS_FUSE) diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index 7029f84f5fd..0a20aea7ee8 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -99,6 +99,5 @@ endif # FILE_SYSTEM rsource "fcb/Kconfig" rsource "nvs/Kconfig" -rsource "zms/Kconfig" endmenu diff --git a/subsys/fs/zms/CMakeLists.txt b/subsys/fs/zms/CMakeLists.txt deleted file mode 100644 index b6db8a3f57f..00000000000 --- a/subsys/fs/zms/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -#SPDX-License-Identifier: Apache-2.0 - -zephyr_sources(zms.c) diff --git a/subsys/fs/zms/Kconfig b/subsys/fs/zms/Kconfig deleted file mode 100644 index bc1dae78279..00000000000 --- a/subsys/fs/zms/Kconfig +++ /dev/null @@ -1,58 +0,0 @@ -#Zephyr Memory Storage ZMS - -#Copyright (c) 2024 BayLibre SAS - -#SPDX-License-Identifier: Apache-2.0 - -config ZMS - bool "Zephyr Memory Storage" - select CRC - help - Enable support of Zephyr Memory Storage. - -if ZMS - -config ZMS_LOOKUP_CACHE - bool "ZMS lookup cache" - help - Enable ZMS cache to reduce the ZMS data lookup time. - Each cache entry holds an address of the most recent allocation - table entry (ATE) for all ZMS IDs that fall into that cache position. - -config ZMS_LOOKUP_CACHE_SIZE - int "ZMS Storage lookup cache size" - default 128 - range 1 65536 - depends on ZMS_LOOKUP_CACHE - help - Number of entries in ZMS lookup cache. - It is recommended that it should be a power of 2. - Every additional entry in cache will add 8 bytes in RAM - -config ZMS_DATA_CRC - bool "ZMS DATA CRC" - help - Enables DATA CRC - -config ZMS_CUSTOM_BLOCK_SIZE - bool "Custom buffer size used by ZMS for reads and writes" - default n - help - ZMS uses internal buffers to read/write and compare stored data. - Increasing the size of these buffers should be done carefully in order to not - overflow the stack. - Increasing this buffer means as well that ZMS could work with storage devices - that have larger write-block-size which decreases ZMS performance - -config ZMS_MAX_BLOCK_SIZE - int "ZMS internal buffer size" - default 32 - depends on ZMS_CUSTOM_BLOCK_SIZE - help - Changes the internal buffer size of ZMS - -module = ZMS -module-str = zms -source "subsys/logging/Kconfig.template.log_config" - -endif # ZMS diff --git a/subsys/fs/zms/zms.c b/subsys/fs/zms/zms.c deleted file mode 100644 index 891af027916..00000000000 --- a/subsys/fs/zms/zms.c +++ /dev/null @@ -1,1804 +0,0 @@ -/* ZMS: Zephyr Memory Storage - * - * Copyright (c) 2024 BayLibre SAS - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include "zms_priv.h" - -#include -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, previous_sector_num = ZMS_INVALID_SECTOR_NUM; - uint64_t addr, 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); -} - -/* 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 (len + (write_block_size - 1U)) & ~(write_block_size - 1U); -} - -/* Helper to get empty ATE address */ -static inline uint64_t zms_empty_ate_addr(struct zms_fs *fs, uint64_t addr) -{ - size_t ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - return (addr & ADDR_SECT_MASK) + fs->sector_size - ate_size; -} - -/* Helper to get close ATE address */ -static inline uint64_t zms_close_ate_addr(struct zms_fs *fs, uint64_t addr) -{ - size_t ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - return (addr & ADDR_SECT_MASK) + fs->sector_size - 2 * 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 = len & ~(fs->flash_parameters->write_block_size - 1U); - if (blen > 0) { - rc = flash_write(fs->flash_device, offset, data8, blen); - if (rc) { - /* flash write error */ - goto end; - } - len -= blen; - offset += blen; - data8 += blen; - } - if (len) { - memcpy(buf, data8, len); - (void)memset(buf + len, fs->flash_parameters->erase_value, - fs->flash_parameters->write_block_size - len); - - rc = flash_write(fs->flash_device, offset, buf, - fs->flash_parameters->write_block_size); - } - -end: - return rc; -} - -/* basic flash read from 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, block_size; - uint8_t buf[ZMS_BLOCK_SIZE]; - - block_size = ZMS_BLOCK_SIZE & ~(fs->flash_parameters->write_block_size - 1U); - - 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, block_size; - uint8_t cmp[ZMS_BLOCK_SIZE]; - - block_size = ZMS_BLOCK_SIZE & ~(fs->flash_parameters->write_block_size - 1U); - - (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, block_size; - uint8_t buf[ZMS_BLOCK_SIZE]; - - block_size = ZMS_BLOCK_SIZE & ~(fs->flash_parameters->write_block_size - 1U); - - 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: - * return 1 if crc8 and cycle_cnt valid, - * 0 otherwise - */ -static int zms_ate_valid(struct zms_fs *fs, const struct zms_ate *entry) -{ - size_t ate_size; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - if ((fs->sector_cycle != entry->cycle_cnt) || zms_ate_crc8_check(entry)) { - return 0; - } - - return 1; -} - -/* 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) -{ - size_t ate_size; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - 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 an sector close ate: a valid sector close ate: - * - valid ate - * - len = 0 and id = ZMS_HEAD_ID - * - 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) -{ - size_t ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - return (zms_ate_valid_different_sector(fs, entry, entry->cycle_cnt) && (!entry->len) && - (entry->id == ZMS_HEAD_ID) && !((fs->sector_size - entry->offset) % ate_size)); -} - -/* zms_empty_ate_valid validates an sector empty ate: a valid sector empty ate: - * - valid ate - * - 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, ate_end_addr; - struct zms_ate end_ate; - size_t ate_size; - int rc; - - LOG_DBG("Recovering last ate from sector %llu", SECTOR_NUM(*addr)); - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - /* skip close and empty ATE */ - *addr -= 2 * 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 -= 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, close_ate; - size_t ate_size; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - *addr += ate_size; - if ((SECTOR_OFFSET(*addr)) != (fs->sector_size - 2 * 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, garbage_ate; - size_t ate_size; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - close_ate.id = ZMS_HEAD_ID; - close_ate.len = 0U; - close_ate.offset = (uint32_t)SECTOR_OFFSET(fs->ate_wra + 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; - size_t ate_size; - uint8_t cycle_cnt; - int rc = 0; - uint64_t previous_ate_wra; - - addr &= ADDR_SECT_MASK; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - LOG_DBG("Adding empty ate at %llx", (uint64_t)(addr + fs->sector_size - 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; - size_t ate_size; - struct zms_ate empty_ate; - uint64_t empty_addr; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - 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; - size_t ate_size; - uint64_t close_addr; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - 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 + 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, 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, sec_closed; - struct zms_ate close_ate, gc_ate, wlk_ate, empty_ate; - uint64_t sec_addr, gc_addr, gc_prev_addr, wlk_addr, wlk_prev_addr, data_addr, stop_addr; - uint8_t previous_cycle = 0; - size_t ate_size; - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - 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 - ate_size; - /* skip header ATEs */ - stop_addr = gc_addr - 2 * 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; - - /* 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; - - /* Make it possible to detect that gc has finished by writing a - * gc done ate to the sector. In the field we might have zms systems - * that do not have sufficient space to add this ate, so for these - * situations avoid adding the gc done ate. - */ - - if (fs->ate_wra >= (fs->data_wra + ate_size)) { - rc = 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, sec_closed; - struct zms_ate last_ate, first_ate, close_ate, empty_ate; - size_t ate_size; - /* Initialize addr to 0 for the case fs->sector_count == 0. This - * should never happen as this is verified in zms_mount() but both - * Coverity and GCC believe the contrary. - */ - uint64_t addr = 0U, data_wra = 0U; - uint32_t i, closed_sectors = 0; - bool zms_magic_exist = false; - - k_mutex_lock(&fs->zms_lock, K_FOREVER); - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - /* 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 - 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 -= 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 + ate_size; - while (SECTOR_OFFSET(addr) < (fs->sector_size - 2 * 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 += 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 * 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 * 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; - size_t ate_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; - } - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - write_block_size = flash_get_write_block_size(fs->flash_device); - - /* 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 * ate_size) { - LOG_ERR("Invalid sector size, should be at least %u", ZMS_MIN_ATE_NUM * 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 ate_size, data_size; - struct zms_ate wlk_ate; - uint64_t wlk_addr, rd_addr; - uint32_t gc_count, required_space = 0U; /* no space, appropriate for delete ate */ - int prev_found = 0; - - if (!fs->ready) { - LOG_ERR("zms not initialized"); - return -EACCES; - } - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - 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 * 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 + ate_size; - } else { - required_space = 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 - 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, prev_found; - uint64_t wlk_addr, rd_addr, wlk_prev_addr; - uint32_t cnt_his; - struct zms_ate wlk_ate; - size_t ate_size; -#ifdef CONFIG_ZMS_DATA_CRC - uint32_t computed_data_crc; -#endif - - if (!fs->ready) { - LOG_ERR("zms not initialized"); - return -EACCES; - } - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - 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, previous_sector_num = ZMS_INVALID_SECTOR_NUM, prev_found = 0, sec_closed; - struct zms_ate step_ate, wlk_ate, empty_ate, close_ate; - uint64_t step_addr, wlk_addr, step_prev_addr, wlk_prev_addr, data_wra = 0U; - uint8_t current_cycle; - size_t ate_size; - ssize_t free_space = 0; - - if (!fs->ready) { - LOG_ERR("zms not initialized"); - return -EACCES; - } - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - /* - * 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 * 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 -= 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. - */ - const uint32_t second_to_last_offset = (2 * ate_size); - - for (uint32_t 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 -= 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 -= ate_size; - } - } - } - /* restore sector cycle */ - fs->sector_cycle = current_cycle; - - return free_space; -} - -size_t zms_sector_max_data_size(struct zms_fs *fs) -{ - size_t ate_size; - - if (!fs->ready) { - LOG_ERR("ZMS not initialized"); - return -EACCES; - } - - ate_size = zms_al_size(fs, sizeof(struct zms_ate)); - - return fs->ate_wra - fs->data_wra - 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; -} diff --git a/subsys/fs/zms/zms_priv.h b/subsys/fs/zms/zms_priv.h deleted file mode 100644 index 6594048ea0f..00000000000 --- a/subsys/fs/zms/zms_priv.h +++ /dev/null @@ -1,76 +0,0 @@ -/* ZMS: Zephyr Memory Storage - * - * Copyright (c) 2024 BayLibre SAS - * - * SPDX-License-Identifier: Apache-2.0 - */ -#ifndef __ZMS_PRIV_H_ -#define __ZMS_PRIV_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * MASKS AND SHIFT FOR ADDRESSES - * an address in zms is an uint64_t where: - * high 4 bytes represent the sector number - * low 4 bytes represent the offset in a sector - */ -#define ADDR_SECT_MASK GENMASK64(63, 32) -#define ADDR_SECT_SHIFT 32 -#define ADDR_OFFS_MASK GENMASK64(31, 0) -#define SECTOR_NUM(x) FIELD_GET(ADDR_SECT_MASK, x) -#define SECTOR_OFFSET(x) FIELD_GET(ADDR_OFFS_MASK, x) - -#if defined(CONFIG_ZMS_CUSTOM_BLOCK_SIZE) -#define ZMS_BLOCK_SIZE CONFIG_ZMS_MAX_BLOCK_SIZE -#else -#define ZMS_BLOCK_SIZE 32 -#endif - -#define ZMS_LOOKUP_CACHE_NO_ADDR GENMASK64(63, 0) -#define ZMS_HEAD_ID GENMASK(31, 0) - -#define ZMS_VERSION_MASK GENMASK(7, 0) -#define ZMS_GET_VERSION(x) FIELD_GET(ZMS_VERSION_MASK, x) -#define ZMS_DEFAULT_VERSION 1 -#define ZMS_MAGIC_NUMBER 0x42 /* murmur3a hash of "ZMS" (MSB) */ -#define ZMS_MAGIC_NUMBER_MASK GENMASK(15, 8) -#define ZMS_GET_MAGIC_NUMBER(x) FIELD_GET(ZMS_MAGIC_NUMBER_MASK, x) -#define ZMS_MIN_ATE_NUM 5 - -#define ZMS_INVALID_SECTOR_NUM -1 -#define ZMS_DATA_IN_ATE_SIZE 8 - -struct zms_ate { - uint8_t crc8; /* crc8 check of the entry */ - uint8_t cycle_cnt; /* cycle counter for non erasable devices */ - uint32_t id; /* data id */ - uint16_t len; /* data len within sector */ - union { - uint8_t data[8]; /* used to store small size data */ - struct { - uint32_t offset; /* data offset within sector */ - union { - uint32_t data_crc; /* - * crc for data: The data CRC is checked only - * when the whole data of the element is read. - * The data CRC is not checked for a partial - * read, as it is computed for the complete - * set of data. - */ - uint32_t metadata; /* - * Used to store metadata information - * such as storage version. - */ - }; - }; - }; -} __packed; - -#ifdef __cplusplus -} -#endif - -#endif /* __ZMS_PRIV_H_ */ From 413d9f04d2328b278f877426f61df0bb9aea8dfb Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Mon, 26 Feb 2024 12:18:58 +0100 Subject: [PATCH 3/4] [nrf fromlist] fs: introduce ZMS a new Memory storage system ZMS is the abreviation of Zephyr Memory Storage. It is a storage developed to target especially the non erasable devices. The new memory storage system inherit from the NVS storage multiple features and introduce new ones : * Inherited features : - light key-value based storage - cache for entries - Wear Leveling of flash memory - Resilience to power failures * New features : - cycle counter for non erasable devices (instead of erase emulation) - Keys up to 32-bit - Built-in support of CRC32 for data - Small size data (<= 8 bytes) integrated within entries Upstream PR: https://github.com/zephyrproject-rtos/zephyr/pull/77930 Signed-off-by: Riadh Ghaddab (cherry picked from commit 0feaf588f9628e4279001eb390ac07ae1e818f9e) --- include/zephyr/fs/zms.h | 215 +++++ subsys/fs/CMakeLists.txt | 1 + subsys/fs/Kconfig | 1 + subsys/fs/zms/CMakeLists.txt | 3 + subsys/fs/zms/Kconfig | 58 ++ subsys/fs/zms/zms.c | 1757 ++++++++++++++++++++++++++++++++++ subsys/fs/zms/zms_priv.h | 76 ++ 7 files changed, 2111 insertions(+) create mode 100644 include/zephyr/fs/zms.h create mode 100644 subsys/fs/zms/CMakeLists.txt create mode 100644 subsys/fs/zms/Kconfig create mode 100644 subsys/fs/zms/zms.c create mode 100644 subsys/fs/zms/zms_priv.h diff --git a/include/zephyr/fs/zms.h b/include/zephyr/fs/zms.h new file mode 100644 index 00000000000..1155319d792 --- /dev/null +++ b/include/zephyr/fs/zms.h @@ -0,0 +1,215 @@ +/* ZMS: Zephyr Memory Storage + * + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_FS_ZMS_H_ +#define ZEPHYR_INCLUDE_FS_ZMS_H_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Zephyr Memory Storage (ZMS) + * @defgroup zms Zephyr Memory Storage (ZMS) + * @ingroup file_system_storage + * @{ + * @} + */ + +/** + * @brief Zephyr Memory Storage Data Structures + * @defgroup zms_data_structures Zephyr Memory Storage Data Structures + * @ingroup zms + * @{ + */ + +/** + * @brief Zephyr Memory Storage File system structure + */ +struct zms_fs { + /** File system offset in flash **/ + off_t offset; + /** Allocation table entry write address. + * Addresses are stored as uint64_t: + * - high 4 bytes correspond to the sector + * - low 4 bytes are the offset in the sector + */ + uint64_t ate_wra; + /** Data write address */ + uint64_t data_wra; + /** Storage system is split into sectors, each sector size must be multiple of erase-blocks + * if the device has erase capabilities + */ + uint32_t sector_size; + /** Number of sectors in the file system */ + uint32_t sector_count; + /** Current cycle counter of the active sector (pointed by ate_wra)*/ + uint8_t sector_cycle; + /** Flag indicating if the file system is initialized */ + bool ready; + /** Mutex */ + struct k_mutex zms_lock; + /** Flash device runtime structure */ + const struct device *flash_device; + /** Flash memory parameters structure */ + const struct flash_parameters *flash_parameters; + /** Size of an Allocation Table Entry */ + size_t ate_size; +#if CONFIG_ZMS_LOOKUP_CACHE + /** Lookup table used to cache ATE address of a written ID */ + uint64_t lookup_cache[CONFIG_ZMS_LOOKUP_CACHE_SIZE]; +#endif +}; + +/** + * @} + */ + +/** + * @brief Zephyr Memory Storage APIs + * @defgroup zms_high_level_api Zephyr Memory Storage APIs + * @ingroup zms + * @{ + */ + +/** + * @brief Mount a ZMS file system onto the device specified in @p fs. + * + * @param fs Pointer to file system + * @retval 0 Success + * @retval -ERRNO errno code if error + */ +int zms_mount(struct zms_fs *fs); + +/** + * @brief Clear the ZMS file system from device. + * + * @param fs Pointer to file system + * @retval 0 Success + * @retval -ERRNO errno code if error + */ +int zms_clear(struct zms_fs *fs); + +/** + * @brief Write an entry to the file system. + * + * @note When @p len parameter is equal to @p 0 then entry is effectively removed (it is + * equivalent to calling of zms_delete). It is not possible to distinguish between a deleted + * entry and an entry with data of length 0. + * + * @param fs Pointer to file system + * @param id Id of the entry to be written + * @param data Pointer to the data to be written + * @param len Number of bytes to be written (maximum 64 KB) + * + * @return Number of bytes written. On success, it will be equal to the number of bytes requested + * to be written. When a rewrite of the same data already stored is attempted, nothing is written + * to flash, thus 0 is returned. On error, returns negative value of errno.h defined error codes. + */ +ssize_t zms_write(struct zms_fs *fs, uint32_t id, const void *data, size_t len); + +/** + * @brief Delete an entry from the file system + * + * @param fs Pointer to file system + * @param id Id of the entry to be deleted + * @retval 0 Success + * @retval -ERRNO errno code if error + */ +int zms_delete(struct zms_fs *fs, uint32_t id); + +/** + * @brief Read an entry from the file system. + * + * @param fs Pointer to file system + * @param id Id of the entry to be read + * @param data Pointer to data buffer + * @param len Number of bytes to be read (or size of the allocated read buffer) + * + * @return Number of bytes read. On success, it will be equal to the number of bytes requested + * to be read. When the return value is less than the number of bytes requested to read this + * indicates that ATE contain less data than requested. On error, returns negative value of + * errno.h defined error codes. + */ +ssize_t zms_read(struct zms_fs *fs, uint32_t id, void *data, size_t len); + +/** + * @brief Read a history entry from the file system. + * + * @param fs Pointer to file system + * @param id Id of the entry to be read + * @param data Pointer to data buffer + * @param len Number of bytes to be read + * @param cnt History counter: 0: latest entry, 1: one before latest ... + * + * @return Number of bytes read. On success, it will be equal to the number of bytes requested + * to be read. When the return value is larger than the number of bytes requested to read this + * indicates not all bytes were read, and more data is available. On error, returns negative + * value of errno.h defined error codes. + */ +ssize_t zms_read_hist(struct zms_fs *fs, uint32_t id, void *data, size_t len, uint32_t cnt); + +/** + * @brief Gets the data size that is stored in an entry with a given id + * + * @param fs Pointer to file system + * @param id Id of the entry that we want to get its data length + * + * @return Data length contained in the ATE. On success, it will be equal to the number of bytes + * in the ATE. On error, returns negative value of errno.h defined error codes. + */ +ssize_t zms_get_data_length(struct zms_fs *fs, uint32_t id); +/** + * @brief Calculate the available free space in the file system. + * + * @param fs Pointer to file system + * + * @return Number of bytes free. On success, it will be equal to the number of bytes that can + * still be written to the file system. + * Calculating the free space is a time consuming operation, especially on spi flash. + * On error, returns negative value of errno.h defined error codes. + */ +ssize_t zms_calc_free_space(struct zms_fs *fs); + +/** + * @brief Tell how many contiguous free space remains in the currently active ZMS sector. + * + * @param fs Pointer to the file system. + * + * @return Number of free bytes. + */ +size_t zms_sector_max_data_size(struct zms_fs *fs); + +/** + * @brief Close the currently active sector and switch to the next one. + * + * @note The garbage collector is called on the new sector. + * + * @warning This routine is made available for specific use cases. + * It collides with the ZMS goal of avoiding any unnecessary flash erase operations. + * Using this routine extensively can result in premature failure of the flash device. + * + * @param fs Pointer to the file system. + * + * @return 0 on success. On error, returns negative value of errno.h defined error codes. + */ +int zms_sector_use_next(struct zms_fs *fs); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_FS_ZMS_H_ */ diff --git a/subsys/fs/CMakeLists.txt b/subsys/fs/CMakeLists.txt index fb774711543..825b8356381 100644 --- a/subsys/fs/CMakeLists.txt +++ b/subsys/fs/CMakeLists.txt @@ -25,6 +25,7 @@ endif() add_subdirectory_ifdef(CONFIG_FCB ./fcb) add_subdirectory_ifdef(CONFIG_NVS ./nvs) +add_subdirectory_ifdef(CONFIG_ZMS ./zms) if(CONFIG_FUSE_FS_ACCESS) zephyr_library_named(FS_FUSE) diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index 0a20aea7ee8..7029f84f5fd 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -99,5 +99,6 @@ endif # FILE_SYSTEM rsource "fcb/Kconfig" rsource "nvs/Kconfig" +rsource "zms/Kconfig" endmenu diff --git a/subsys/fs/zms/CMakeLists.txt b/subsys/fs/zms/CMakeLists.txt new file mode 100644 index 00000000000..b6db8a3f57f --- /dev/null +++ b/subsys/fs/zms/CMakeLists.txt @@ -0,0 +1,3 @@ +#SPDX-License-Identifier: Apache-2.0 + +zephyr_sources(zms.c) diff --git a/subsys/fs/zms/Kconfig b/subsys/fs/zms/Kconfig new file mode 100644 index 00000000000..bc1dae78279 --- /dev/null +++ b/subsys/fs/zms/Kconfig @@ -0,0 +1,58 @@ +#Zephyr Memory Storage ZMS + +#Copyright (c) 2024 BayLibre SAS + +#SPDX-License-Identifier: Apache-2.0 + +config ZMS + bool "Zephyr Memory Storage" + select CRC + help + Enable support of Zephyr Memory Storage. + +if ZMS + +config ZMS_LOOKUP_CACHE + bool "ZMS lookup cache" + help + Enable ZMS cache to reduce the ZMS data lookup time. + Each cache entry holds an address of the most recent allocation + table entry (ATE) for all ZMS IDs that fall into that cache position. + +config ZMS_LOOKUP_CACHE_SIZE + int "ZMS Storage lookup cache size" + default 128 + range 1 65536 + depends on ZMS_LOOKUP_CACHE + help + Number of entries in ZMS lookup cache. + It is recommended that it should be a power of 2. + Every additional entry in cache will add 8 bytes in RAM + +config ZMS_DATA_CRC + bool "ZMS DATA CRC" + help + Enables DATA CRC + +config ZMS_CUSTOM_BLOCK_SIZE + bool "Custom buffer size used by ZMS for reads and writes" + default n + help + ZMS uses internal buffers to read/write and compare stored data. + Increasing the size of these buffers should be done carefully in order to not + overflow the stack. + Increasing this buffer means as well that ZMS could work with storage devices + that have larger write-block-size which decreases ZMS performance + +config ZMS_MAX_BLOCK_SIZE + int "ZMS internal buffer size" + default 32 + depends on ZMS_CUSTOM_BLOCK_SIZE + help + Changes the internal buffer size of ZMS + +module = ZMS +module-str = zms +source "subsys/logging/Kconfig.template.log_config" + +endif # ZMS diff --git a/subsys/fs/zms/zms.c b/subsys/fs/zms/zms.c new file mode 100644 index 00000000000..4a90ad0129b --- /dev/null +++ b/subsys/fs/zms/zms.c @@ -0,0 +1,1757 @@ +/* ZMS: Zephyr Memory Storage + * + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "zms_priv.h" + +#include +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, previous_sector_num = ZMS_INVALID_SECTOR_NUM; + uint64_t addr, 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); +} + +/* 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 (len + (write_block_size - 1U)) & ~(write_block_size - 1U); +} + +/* 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 = len & ~(fs->flash_parameters->write_block_size - 1U); + if (blen > 0) { + rc = flash_write(fs->flash_device, offset, data8, blen); + if (rc) { + /* flash write error */ + goto end; + } + len -= blen; + offset += blen; + data8 += blen; + } + if (len) { + memcpy(buf, data8, len); + (void)memset(buf + len, fs->flash_parameters->erase_value, + fs->flash_parameters->write_block_size - len); + + rc = flash_write(fs->flash_device, offset, buf, + fs->flash_parameters->write_block_size); + } + +end: + return rc; +} + +/* basic flash read from 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, block_size; + uint8_t buf[ZMS_BLOCK_SIZE]; + + block_size = ZMS_BLOCK_SIZE & ~(fs->flash_parameters->write_block_size - 1U); + + 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, block_size; + uint8_t cmp[ZMS_BLOCK_SIZE]; + + block_size = ZMS_BLOCK_SIZE & ~(fs->flash_parameters->write_block_size - 1U); + + (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, block_size; + uint8_t buf[ZMS_BLOCK_SIZE]; + + block_size = ZMS_BLOCK_SIZE & ~(fs->flash_parameters->write_block_size - 1U); + + 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: + * return 1 if crc8 and cycle_cnt valid, + * 0 otherwise + */ +static int zms_ate_valid(struct zms_fs *fs, const struct zms_ate *entry) +{ + if ((fs->sector_cycle != entry->cycle_cnt) || zms_ate_crc8_check(entry)) { + return 0; + } + + return 1; +} + +/* 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 an sector close ate: a valid sector close ate: + * - valid ate + * - len = 0 and id = ZMS_HEAD_ID + * - 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: + * - valid ate + * - 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, 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, 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, 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, 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, sec_closed; + struct zms_ate close_ate, gc_ate, wlk_ate, empty_ate; + uint64_t sec_addr, gc_addr, gc_prev_addr, wlk_addr, wlk_prev_addr, data_addr, 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; + + /* Make it possible to detect that gc has finished by writing a + * gc done ate to the sector. In the field we might have zms systems + * that do not have sufficient space to add this ate, so for these + * situations avoid adding the gc done ate. + */ + + if (fs->ate_wra >= (fs->data_wra + fs->ate_size)) { + 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, sec_closed; + struct zms_ate last_ate, first_ate, close_ate, empty_ate; + /* Initialize addr to 0 for the case fs->sector_count == 0. This + * should never happen as this is verified in zms_mount() but both + * Coverity and GCC believe the contrary. + */ + uint64_t addr = 0U, data_wra = 0U; + uint32_t i, 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 = flash_get_write_block_size(fs->flash_device); + + /* 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, rd_addr; + uint32_t gc_count, 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, prev_found = 0; + uint64_t wlk_addr, rd_addr = 0, 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, previous_sector_num = ZMS_INVALID_SECTOR_NUM, prev_found = 0, sec_closed; + struct zms_ate step_ate, wlk_ate, empty_ate, close_ate; + uint64_t step_addr, wlk_addr, step_prev_addr, wlk_prev_addr, data_wra = 0U; + uint8_t current_cycle; + ssize_t free_space = 0; + + 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. + */ + const uint32_t second_to_last_offset = (2 * fs->ate_size); + + for (uint32_t 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_sector_max_data_size(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; +} diff --git a/subsys/fs/zms/zms_priv.h b/subsys/fs/zms/zms_priv.h new file mode 100644 index 00000000000..6594048ea0f --- /dev/null +++ b/subsys/fs/zms/zms_priv.h @@ -0,0 +1,76 @@ +/* ZMS: Zephyr Memory Storage + * + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __ZMS_PRIV_H_ +#define __ZMS_PRIV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * MASKS AND SHIFT FOR ADDRESSES + * an address in zms is an uint64_t where: + * high 4 bytes represent the sector number + * low 4 bytes represent the offset in a sector + */ +#define ADDR_SECT_MASK GENMASK64(63, 32) +#define ADDR_SECT_SHIFT 32 +#define ADDR_OFFS_MASK GENMASK64(31, 0) +#define SECTOR_NUM(x) FIELD_GET(ADDR_SECT_MASK, x) +#define SECTOR_OFFSET(x) FIELD_GET(ADDR_OFFS_MASK, x) + +#if defined(CONFIG_ZMS_CUSTOM_BLOCK_SIZE) +#define ZMS_BLOCK_SIZE CONFIG_ZMS_MAX_BLOCK_SIZE +#else +#define ZMS_BLOCK_SIZE 32 +#endif + +#define ZMS_LOOKUP_CACHE_NO_ADDR GENMASK64(63, 0) +#define ZMS_HEAD_ID GENMASK(31, 0) + +#define ZMS_VERSION_MASK GENMASK(7, 0) +#define ZMS_GET_VERSION(x) FIELD_GET(ZMS_VERSION_MASK, x) +#define ZMS_DEFAULT_VERSION 1 +#define ZMS_MAGIC_NUMBER 0x42 /* murmur3a hash of "ZMS" (MSB) */ +#define ZMS_MAGIC_NUMBER_MASK GENMASK(15, 8) +#define ZMS_GET_MAGIC_NUMBER(x) FIELD_GET(ZMS_MAGIC_NUMBER_MASK, x) +#define ZMS_MIN_ATE_NUM 5 + +#define ZMS_INVALID_SECTOR_NUM -1 +#define ZMS_DATA_IN_ATE_SIZE 8 + +struct zms_ate { + uint8_t crc8; /* crc8 check of the entry */ + uint8_t cycle_cnt; /* cycle counter for non erasable devices */ + uint32_t id; /* data id */ + uint16_t len; /* data len within sector */ + union { + uint8_t data[8]; /* used to store small size data */ + struct { + uint32_t offset; /* data offset within sector */ + union { + uint32_t data_crc; /* + * crc for data: The data CRC is checked only + * when the whole data of the element is read. + * The data CRC is not checked for a partial + * read, as it is computed for the complete + * set of data. + */ + uint32_t metadata; /* + * Used to store metadata information + * such as storage version. + */ + }; + }; + }; +} __packed; + +#ifdef __cplusplus +} +#endif + +#endif /* __ZMS_PRIV_H_ */ From c0409a9025c0a188fc2c195ed657254f7bd8de53 Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Fri, 11 Oct 2024 10:06:38 +0200 Subject: [PATCH 4/4] [nrf noup] settings: zms: use dedicated lookup cache hash function Introduce ZMS_LOOKUP_CACHE_FOR_SETTINGS Kconfig option that enables a dedicated hash function for the ZMS lookup cache that takes advantage of the ZMS ID allocation scheme used by the ZMS settings backend. As such, this option should only be used if an application uses ZMS via the settings layer. Signed-off-by: Riadh Ghaddab --- subsys/fs/zms/Kconfig | 9 +++++++++ subsys/fs/zms/zms.c | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/subsys/fs/zms/Kconfig b/subsys/fs/zms/Kconfig index bc1dae78279..c9514608da8 100644 --- a/subsys/fs/zms/Kconfig +++ b/subsys/fs/zms/Kconfig @@ -51,6 +51,15 @@ config ZMS_MAX_BLOCK_SIZE help Changes the internal buffer size of ZMS +config ZMS_LOOKUP_CACHE_FOR_SETTINGS + bool "ZMS Storage lookup cache optimized for settings" + depends on ZMS_LOOKUP_CACHE + help + Use the lookup cache hash function that results in the least number of + collissions and, in turn, the best ZMS performance provided that the ZMS + is used as the settings backend only. This option should NOT be enabled + if the ZMS is also written to directly, outside the settings layer. + module = ZMS module-str = zms source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/fs/zms/zms.c b/subsys/fs/zms/zms.c index 4a90ad0129b..839c079f923 100644 --- a/subsys/fs/zms/zms.c +++ b/subsys/fs/zms/zms.c @@ -11,6 +11,10 @@ #include #include #include "zms_priv.h" +#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS +#include +#include +#endif #include LOG_MODULE_REGISTER(fs_zms, CONFIG_ZMS_LOG_LEVEL); @@ -25,6 +29,45 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at #ifdef CONFIG_ZMS_LOOKUP_CACHE +#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS + +static inline size_t zms_lookup_cache_pos(uint32_t id) +{ + /* + * 1. The ZMS settings backend uses up to (ZMS_NAME_ID_OFFSET - 1) ZMS IDs to + store keys and equal number of ZMS IDs to store values. + * 2. For each key-value pair, the value is stored at ZMS ID greater by exactly + * ZMS_NAME_ID_OFFSET than ZMS ID that holds the key. + * 3. The backend tries to minimize the range of ZMS IDs used to store keys. + * That is, ZMS IDs are allocated sequentially, and freed ZMS IDs are reused + * before allocating new ones. + * + * Therefore, to assure the least number of collisions in the lookup cache, + * the least significant bit of the hash indicates whether the given ZMS ID + * represents a key or a value, and remaining bits of the hash are set to + * the ordinal number of the key-value pair. Consequently, the hash function + * provides the following mapping: + * + * 1st settings key => hash 0 + * 1st settings value => hash 1 + * 2nd settings key => hash 2 + * 2nd settings value => hash 3 + * ... + */ + BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_NAMECNT_ID), "ZMS_NAMECNT_ID is not power of 2"); + BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_NAME_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2"); + + uint32_t key_value_bit; + uint32_t key_value_ord; + + key_value_bit = (id >> LOG2(ZMS_NAME_ID_OFFSET)) & 1; + key_value_ord = id & (ZMS_NAME_ID_OFFSET - 1); + + return ((key_value_ord << 1) | key_value_bit) % CONFIG_ZMS_LOOKUP_CACHE_SIZE; +} + +#else /* CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS */ + static inline size_t zms_lookup_cache_pos(uint32_t id) { uint32_t hash; @@ -40,6 +83,8 @@ static inline size_t zms_lookup_cache_pos(uint32_t id) return hash % CONFIG_ZMS_LOOKUP_CACHE_SIZE; } +#endif /* CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS */ + static int zms_lookup_cache_rebuild(struct zms_fs *fs) { int rc, previous_sector_num = ZMS_INVALID_SECTOR_NUM;