From 415482fed5829b3aa92711fd23301906c429dfbd Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Tue, 28 Apr 2026 11:47:29 +0200 Subject: [PATCH 01/15] [nrf fromtree] kvss: zms: add mount flags to control mount behavior Add mount_flags to zms_fs structure to be able to control the mount operation. If ZMS_MOUNT_FLAG_NO_FORMAT flag is enabled, no header will be added to an erased memory and it will return -ENOTSUP error. Signed-off-by: Riadh Ghaddab (cherry picked from commit a5e7080de59769f20b1655f408e479dec873de3c) --- include/zephyr/kvss/zms.h | 18 ++++++++++++++++++ subsys/kvss/zms/zms.c | 7 ++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/zephyr/kvss/zms.h b/include/zephyr/kvss/zms.h index 45ab2f3424bb..e2544b63cd45 100644 --- a/include/zephyr/kvss/zms.h +++ b/include/zephyr/kvss/zms.h @@ -31,6 +31,17 @@ extern "C" { * @{ */ +/** Mount options for @ref zms_mount and @ref zms_mount_force. */ +enum zms_mount_flags { + /** + * Do not format erased media during mount. + * + * When this flag is set and the backing flash area is erased (no valid ZMS + * header), mount fails instead of creating a new ZMS header. + */ + ZMS_MOUNT_FLAG_NO_FORMAT = BIT(0), +}; + /** Zephyr Memory Storage file system structure */ struct zms_fs { /** File system offset in flash */ @@ -49,6 +60,8 @@ struct zms_fs { uint32_t sector_size; /** Number of sectors in the file system */ uint32_t sector_count; + /** Mount behavior flags from @ref zms_mount_flags */ + uint32_t mount_flags; /** Current cycle counter of the active sector (pointed to by `ate_wra`) */ uint8_t sector_cycle; /** Flag indicating if the file system is initialized */ @@ -91,6 +104,11 @@ typedef uint32_t zms_id_t; /** * @brief Mount a ZMS file system onto the device specified in `fs`. * + * @details If the flash area is erased and no valid ZMS header is found, + * mount will format the area and create a valid header by default. + * Set @ref ZMS_MOUNT_FLAG_NO_FORMAT in `fs->mount_flags` to disable this + * auto-format behavior and fail the mount instead. + * * @param fs Pointer to the file system. * * @retval 0 on success. diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index 04dd014777cb..d7a2f65d539f 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -1327,7 +1327,7 @@ static int zms_init(struct zms_fs *fs) goto end; } } - } else { + } else if (!(fs->mount_flags & ZMS_MOUNT_FLAG_NO_FORMAT)) { rc = zms_flash_erase_sector(fs, addr); if (rc) { goto end; @@ -1336,6 +1336,11 @@ static int zms_init(struct zms_fs *fs) if (rc) { goto end; } + } else { + /* No valid empty ATE in the last sector */ + LOG_ERR("No valid empty ATE found in the last sector"); + rc = -ENOTSUP; + goto end; } rc = zms_get_sector_cycle(fs, addr, &fs->sector_cycle); if (rc == -ENOENT) { From 27b988457a959b95a96cdcd3d2742e4f2dfafd8b Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Tue, 28 Apr 2026 11:50:26 +0200 Subject: [PATCH 02/15] [nrf fromtree] tests: kvss: zms: add tests for the mount_flags Add tests for the ZMS_MOUNT_FLAG_NO_FORMAT flag when mounting a storage Signed-off-by: Riadh Ghaddab (cherry picked from commit 0a9315120b31d03303d60b2ff7dbf73cca1b6fa3) --- tests/subsys/kvss/zms/src/main.c | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/subsys/kvss/zms/src/main.c b/tests/subsys/kvss/zms/src/main.c index cb861cca38c9..cd0ef7acb7f5 100644 --- a/tests/subsys/kvss/zms/src/main.c +++ b/tests/subsys/kvss/zms/src/main.c @@ -88,6 +88,7 @@ static void after(void *data) } fixture->fs.sector_count = TEST_SECTOR_COUNT; + fixture->fs.mount_flags = 0; } ZTEST_SUITE(zms, NULL, setup, before, after, NULL); @@ -123,6 +124,42 @@ static void execute_long_pattern_write(uint32_t id, struct zms_fs *fs) zassert_mem_equal(wr_buf, rd_buf, sizeof(rd_buf), "RD buff should be equal to the WR buff"); } +static void erase_test_partition(void) +{ + const struct flash_area *fa; + int err; + + err = flash_area_open(TEST_ZMS_AREA_ID, &fa); + zassert_true(err == 0, "flash_area_open() fail: %d", err); + + err = flash_area_erase(fa, 0, fa->fa_size); + zassert_true(err == 0, "flash_area_erase() fail: %d", err); + + flash_area_close(fa); +} + +ZTEST_F(zms, test_zms_mount_no_format_on_erased_flash) +{ + int err; + + erase_test_partition(); + + fixture->fs.mount_flags = ZMS_MOUNT_FLAG_NO_FORMAT; + err = zms_mount(&fixture->fs); + zassert_equal(err, -ENOTSUP, "zms_mount should fail without formatting: %d", err); +} + +ZTEST_F(zms, test_zms_mount_auto_format_on_erased_flash) +{ + int err; + + erase_test_partition(); + + fixture->fs.mount_flags = 0U; + err = zms_mount(&fixture->fs); + zassert_true(err == 0, "zms_mount call failure: %d", err); +} + ZTEST_F(zms, test_zms_write) { int err; From a0db4b7fbb140923585f828e757d1231d2ae78eb Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Tue, 28 Apr 2026 11:51:49 +0200 Subject: [PATCH 03/15] [nrf fromtree] doc: zms: update ZMS documentation about mount_flags Updtae the documentation and describe the usage of the mount_flags. Detail the ZMS_MOUNT_FLAG_NO_FORMAT optional flag. Signed-off-by: Riadh Ghaddab (cherry picked from commit 9f7802e38346c9de4cba197166e3a3b1d01794b3) --- doc/services/storage/zms/zms.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/services/storage/zms/zms.rst b/doc/services/storage/zms/zms.rst index 5adf018c481d..6b5845bf11ab 100644 --- a/doc/services/storage/zms/zms.rst +++ b/doc/services/storage/zms/zms.rst @@ -110,6 +110,18 @@ By default, :c:func:`zms_mount` returns an error if the partition cannot be moun For recovery-oriented use cases, :c:func:`zms_mount_force` can be used to automatically wipe and reinitialize the partition when the first mount attempt fails. +Mount flags +----------- + +ZMS mount behavior can be controlled with optional flags in ``zms_fs.mount_flags``. + +- Default behavior (no optional flags set, ``mount_flags = 0``): if the partition is erased and no valid + ZMS header is found, :c:func:`zms_mount` formats the partition by creating the + initial ZMS header. +- ``ZMS_MOUNT_FLAG_NO_FORMAT``: if the partition is erased and no valid ZMS + header is found, :c:func:`zms_mount` does not format the partition and returns + ``-ENOTSUP``. + To mount the filesystem the following elements in the :c:struct:`zms_fs` structure must be initialized: .. code-block:: c @@ -125,6 +137,9 @@ To mount the filesystem the following elements in the :c:struct:`zms_fs` structu /** Number of sectors in the file system */ uint32_t sector_count; + /** Optional mount behavior flags (enum zms_mount_flags) */ + uint32_t mount_flags; + /** Flash device runtime structure */ const struct device *flash_device; }; From edfb06d1b8d665c0663f54169d4d563f17eb2c4b Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Tue, 28 Apr 2026 16:48:01 +0200 Subject: [PATCH 04/15] [nrf fromtree] tests: kvss: zms: fix tests for native_sim/native/64 board target Fix number of writes to trigger a GC for native_sim/native/64 board as it has a different sector size. Fix the returned value for zms_free_space when ate_wra < data_wra. Skip test_zms_gc_3sectors for targets that have a sector size different than 1KB. Signed-off-by: Riadh Ghaddab (cherry picked from commit 64997e444703b37d639a18e678ba7c3ee7f516d3) --- subsys/kvss/zms/zms.c | 7 ++++--- tests/subsys/kvss/zms/src/main.c | 34 +++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index d7a2f65d539f..6099004662bf 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -1906,12 +1906,13 @@ static ssize_t zms_free_space(struct zms_fs *fs, uint32_t data_wra, uint32_t ate } /* initial value: available space for data at the top of the sector */ - free_space = ate_wra - data_wra - fs->ate_size; - - if (free_space < 0) { + if (ate_wra < (data_wra + fs->ate_size)) { /* not enough room for an ATE */ return 0; + } else { + free_space = ate_wra - data_wra - fs->ate_size; } + if (free_space < ZMS_DATA_IN_ATE_SIZE) { /* more data can be stored inside an ATE */ return ZMS_DATA_IN_ATE_SIZE; diff --git a/tests/subsys/kvss/zms/src/main.c b/tests/subsys/kvss/zms/src/main.c index cd0ef7acb7f5..fd5c94414bda 100644 --- a/tests/subsys/kvss/zms/src/main.c +++ b/tests/subsys/kvss/zms/src/main.c @@ -208,6 +208,13 @@ ZTEST_F(zms, test_zms_corrupted_write) err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount call failure: %d", err); + /* Get the address of current write calls and the maximum number of writes and reinitialize + * the current number of write calls to 0. + */ + stats_walk(fixture->sim_thresholds, flash_sim_max_write_calls_find, &flash_max_write_calls); + stats_walk(fixture->sim_stats, flash_sim_write_calls_find, &flash_write_stat); + *flash_write_stat = 0; + err = zms_read(&fixture->fs, TEST_DATA_ID, rd_buf, sizeof(rd_buf)); zassert_true(err == -ENOENT, "zms_read unexpected failure: %d", err); @@ -232,9 +239,6 @@ ZTEST_F(zms, test_zms_corrupted_write) /* Set the maximum number of writes that the flash simulator can * execute. */ - stats_walk(fixture->sim_thresholds, flash_sim_max_write_calls_find, &flash_max_write_calls); - stats_walk(fixture->sim_stats, flash_sim_write_calls_find, &flash_write_stat); - *flash_max_write_calls = *flash_write_stat - 1; *flash_write_stat = 0; @@ -261,6 +265,13 @@ ZTEST_F(zms, test_zms_corrupted_write) "write operation has failed"); } +static uint16_t get_max_writes_to_trigger_gc(struct zms_fs *fs, size_t data_size) +{ + const size_t aligned_size_headers = 3 * fs->ate_size; + + return (fs->sector_size - aligned_size_headers) / (data_size + fs->ate_size) + 1; +} + ZTEST_F(zms, test_zms_gc) { int err; @@ -268,14 +279,16 @@ ZTEST_F(zms, test_zms_gc) uint8_t buf[32]; uint8_t rd_buf[32]; const uint8_t max_id = 10; - /* 21st write will trigger GC. */ - const uint16_t max_writes = 21; + uint16_t max_writes; fixture->fs.sector_count = 2; err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount call failure: %d", err); + /* Calculate the number of writes to fill a sector and trigger GC. + */ + max_writes = get_max_writes_to_trigger_gc(&fixture->fs, sizeof(buf)); for (int i = 0; i < max_writes; i++) { uint8_t id = (i % max_id); uint8_t id_data = id + max_id * (i / max_id); @@ -365,6 +378,11 @@ ZTEST_F(zms, test_zms_gc_3sectors) /* 101st write will trigger 4th GC. */ const uint16_t max_writes_4 = 41 + 20 + 20 + 20; + if (fixture->fs.sector_size != 1024) { + /* this test is designed for 1KB sectors */ + ztest_test_skip(); + } + fixture->fs.sector_count = 3; err = zms_mount(&fixture->fs); @@ -443,8 +461,7 @@ ZTEST_F(zms, test_zms_corrupted_sector_close_operation) uint32_t *flash_max_write_calls; uint32_t *flash_max_len; const uint16_t max_id = 10; - /* 21st write will trigger GC. */ - const uint16_t max_writes = 21; + uint16_t max_writes; /* Get the address of simulator parameters. */ stats_walk(fixture->sim_thresholds, flash_sim_max_write_calls_find, &flash_max_write_calls); @@ -454,6 +471,9 @@ ZTEST_F(zms, test_zms_corrupted_sector_close_operation) err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount call failure: %d", err); + /* Calculate the number of writes to fill a sector and trigger GC. + */ + max_writes = get_max_writes_to_trigger_gc(&fixture->fs, sizeof(buf)); for (int i = 0; i < max_writes; i++) { uint8_t id = (i % max_id); uint8_t id_data = id + max_id * (i / max_id); From adf5ce49cfa93b3aaec4e1154d988a9b178a8810 Mon Sep 17 00:00:00 2001 From: Riadh Ghaddab Date: Thu, 30 Apr 2026 15:48:49 +0200 Subject: [PATCH 05/15] [nrf fromtree] tests: kvss: zms: fix the corrupt_close test When executing this test with flash simulator some flash_write are manually injected to simulate data corruption. Add a flash_erase before these writes to fix the test. Signed-off-by: Riadh Ghaddab (cherry picked from commit f331ee5ccd4877ade016ed62cd0cea4e6951de2d) --- tests/subsys/kvss/zms/src/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/subsys/kvss/zms/src/main.c b/tests/subsys/kvss/zms/src/main.c index fd5c94414bda..a15afd954b0a 100644 --- a/tests/subsys/kvss/zms/src/main.c +++ b/tests/subsys/kvss/zms/src/main.c @@ -662,6 +662,14 @@ ZTEST_F(zms, test_zms_gc_corrupt_close_ate) ate.crc8 = crc8_ccitt(0xff, (uint8_t *)&ate + SIZEOF_FIELD(struct zms_ate, crc8), sizeof(struct zms_ate) - SIZEOF_FIELD(struct zms_ate, crc8)); + /* Ensure sectors are erased before injecting handcrafted ATEs. + * Flash simulator writes are one-way bit transitions and cannot set bits back to 1. + */ + fixture->fs.sector_count = 3; + err = flash_erase(fixture->fs.flash_device, fixture->fs.offset, + fixture->fs.sector_count * fixture->fs.sector_size); + zassert_true(err == 0, "flash_erase sector 1 failed: %d", err); + /* Add empty ATE */ err = flash_write(fixture->fs.flash_device, fixture->fs.offset + fixture->fs.sector_size - sizeof(struct zms_ate), @@ -687,8 +695,6 @@ ZTEST_F(zms, test_zms_gc_corrupt_close_ate) &close_ate, sizeof(close_ate)); zassert_true(err == 0, "flash_write failed: %d", err); - fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount call failure: %d", err); From 57131f33c5d3b48c35d81b05123702045bcd2012 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Mon, 10 Mar 2025 15:28:35 +0100 Subject: [PATCH 06/15] [nrf fromtree] kvss: zms: add zms_get_num_cycles() ...which calculates the maximum number of times a single ZMS sector has been recycled. This is to enable estimation of an RRAM lifetime based on a set of tests. Signed-off-by: Damian Krolik Signed-off-by: Mircea Caprioru (cherry picked from commit 72032cbc8e70865747eb6acfb64672c5c1421d62) --- include/zephyr/kvss/zms.h | 1 + subsys/kvss/zms/zms.c | 59 +++++++++++++++++++++++++++++++++++++- subsys/kvss/zms/zms_priv.h | 16 ++++++++--- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/include/zephyr/kvss/zms.h b/include/zephyr/kvss/zms.h index e2544b63cd45..8a6bbb1bb441 100644 --- a/include/zephyr/kvss/zms.h +++ b/include/zephyr/kvss/zms.h @@ -290,6 +290,7 @@ ssize_t zms_active_sector_free_space(struct zms_fs *fs); */ int zms_sector_use_next(struct zms_fs *fs); +uint32_t zms_get_num_cycles(struct zms_fs *fs); /** * @} */ diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index 6099004662bf..4baac37f21f3 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -6,6 +6,7 @@ * ZMS: Zephyr Memory Storage */ +#include #include #include #include @@ -26,6 +27,7 @@ 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_add_empty_ate(struct zms_fs *fs, uint64_t addr); +static int zms_get_full_sector_cycle(struct zms_fs *fs, uint64_t addr, uint32_t *cycle_cnt); 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); @@ -876,6 +878,7 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr) { struct zms_ate empty_ate; uint8_t cycle_cnt; + uint32_t full_cycle_cnt; int rc = 0; uint64_t previous_ate_wra; @@ -891,7 +894,7 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr) FIELD_PREP(ZMS_MAGIC_NUMBER_MASK, ZMS_MAGIC_NUMBER) | FIELD_PREP(ZMS_ATE_FORMAT_MASK, ZMS_DEFAULT_ATE_FORMAT); - rc = zms_get_sector_cycle(fs, addr, &cycle_cnt); + rc = zms_get_full_sector_cycle(fs, addr, &full_cycle_cnt); if (rc == -ENOENT) { /* sector never used */ cycle_cnt = 0; @@ -901,11 +904,14 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr) } /* Increase cycle counter */ + empty_ate.full_cycle_cnt = full_cycle_cnt + 1; + cycle_cnt = (uint8_t)(empty_ate.full_cycle_cnt % BIT(8)); rc = zms_verify_and_increment_cycle_cnt(fs, addr, &cycle_cnt); if (rc < 0) { return rc; } empty_ate.cycle_cnt = cycle_cnt; + zms_ate_crc8_update(&empty_ate); /* Adding empty ate to this sector changes fs->ate_wra value @@ -946,6 +952,30 @@ static int zms_get_sector_cycle(struct zms_fs *fs, uint64_t addr, uint8_t *cycle return -ENOENT; } +static int zms_get_full_sector_cycle(struct zms_fs *fs, uint64_t addr, uint32_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.full_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) { @@ -2069,3 +2099,30 @@ int zms_sector_use_next(struct zms_fs *fs) k_mutex_unlock(&fs->zms_lock); return ret; } + +uint32_t zms_get_num_cycles(struct zms_fs *fs) +{ + uint32_t max_cycle_cnt = 0; + uint32_t cycle_cnt; + int rc; + + if (!fs || !fs->ready) { + return 0; + } + + k_mutex_lock(&fs->zms_lock, K_FOREVER); + + for (uint32_t i = 0; i < fs->sector_count; i++) { + rc = zms_get_full_sector_cycle(fs, (uint64_t)i << ADDR_SECT_SHIFT, &cycle_cnt); + + if (rc) { + continue; + } + + max_cycle_cnt = MAX(max_cycle_cnt, cycle_cnt); + } + + k_mutex_unlock(&fs->zms_lock); + + return max_cycle_cnt; +} diff --git a/subsys/kvss/zms/zms_priv.h b/subsys/kvss/zms/zms_priv.h index 44cfb2f3d280..c426d04b36de 100644 --- a/subsys/kvss/zms/zms_priv.h +++ b/subsys/kvss/zms/zms_priv.h @@ -73,8 +73,12 @@ struct zms_ate { /** data field used to store small sized data */ uint8_t data[8]; struct { - /** data offset within sector */ - uint32_t offset; + union { + /** data offset within sector */ + uint32_t offset; + /** full cycle count (for empty ATE) */ + uint32_t full_cycle_cnt; + }; union { /** * crc for data: The data CRC is checked only when the whole data @@ -97,8 +101,12 @@ struct zms_ate { union { /** data field used to store small sized data */ uint8_t data[4]; - /** data offset within sector */ - uint32_t offset; + union { + /** data offset within sector */ + uint32_t offset; + /** full cycle count (for empty ATE) */ + uint32_t full_cycle_cnt; + }; /** Used to store metadata information such as storage version. */ uint32_t metadata; }; From ea0ad2e654694a891afb258cef25a4def0513430 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Mon, 16 Mar 2026 19:13:24 +0200 Subject: [PATCH 07/15] [nrf fromtree] kvss: zms: fix full_cycle_cnt persistence across sector erase In zms_gc(), the sector erase at zms_flash_erase_sector() destroyed the empty ATE holding full_cycle_cnt before zms_add_empty_ate() could read it, causing zms_get_full_sector_cycle() to always return -ENOENT and resetting the count to 0 (stored as 1). The counter could never exceed 1. Fix by reading full_cycle_cnt before erasing and passing the saved value to zms_add_empty_ate() via a new prev_cycle_cnt parameter. Apply the same read-before-erase pattern in zms_init() recovery paths. Signed-off-by: Mircea Caprioru (cherry picked from commit 893f3dd9bf79540aa3f1d479cab0510a83eb9239) --- subsys/kvss/zms/zms.c | 48 +++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index 4baac37f21f3..1a35aedfd715 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -26,7 +26,7 @@ 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_add_empty_ate(struct zms_fs *fs, uint64_t addr); +static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr, uint32_t prev_cycle_cnt); static int zms_get_full_sector_cycle(struct zms_fs *fs, uint64_t addr, uint32_t *cycle_cnt); 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, @@ -652,7 +652,7 @@ static int zms_wipe_partition(struct zms_fs *fs) if (rc) { return rc; } - rc = zms_add_empty_ate(fs, addr); + rc = zms_add_empty_ate(fs, addr, 0); if (rc) { return rc; } @@ -874,7 +874,7 @@ static inline int zms_verify_and_increment_cycle_cnt(struct zms_fs *fs, uint64_t return 0; } -static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr) +static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr, uint32_t prev_cycle_cnt) { struct zms_ate empty_ate; uint8_t cycle_cnt; @@ -896,20 +896,22 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr) rc = zms_get_full_sector_cycle(fs, addr, &full_cycle_cnt); if (rc == -ENOENT) { - /* sector never used */ - cycle_cnt = 0; + /* Sector erased or never used — use caller-provided previous count */ + cycle_cnt = (uint8_t)(prev_cycle_cnt % BIT(8)); + full_cycle_cnt = prev_cycle_cnt; } else if (rc) { /* bad flash read */ return rc; + } else { + cycle_cnt = (uint8_t)(full_cycle_cnt % BIT(8)); } /* Increase cycle counter */ - empty_ate.full_cycle_cnt = full_cycle_cnt + 1; - cycle_cnt = (uint8_t)(empty_ate.full_cycle_cnt % BIT(8)); rc = zms_verify_and_increment_cycle_cnt(fs, addr, &cycle_cnt); if (rc < 0) { return rc; } + empty_ate.full_cycle_cnt = full_cycle_cnt + 1; empty_ate.cycle_cnt = cycle_cnt; zms_ate_crc8_update(&empty_ate); @@ -968,7 +970,11 @@ static int zms_get_full_sector_cycle(struct zms_fs *fs, uint64_t addr, uint32_t } if (zms_empty_ate_valid(fs, &empty_ate)) { - *cycle_cnt = empty_ate.full_cycle_cnt; + if (empty_ate.full_cycle_cnt == 0 && empty_ate.cycle_cnt > 0) { + *cycle_cnt = empty_ate.cycle_cnt; + } else { + *cycle_cnt = empty_ate.full_cycle_cnt; + } return 0; } @@ -1070,6 +1076,7 @@ static int zms_gc(struct zms_fs *fs) uint64_t gc_addr; uint64_t gc_prev_addr; uint64_t wlk_addr; + uint32_t saved_full_cycle_cnt = 0; uint64_t wlk_prev_addr; uint64_t data_addr; uint64_t stop_addr; @@ -1083,7 +1090,7 @@ static int zms_gc(struct zms_fs *fs) return rc; } /* sector never used */ - rc = zms_add_empty_ate(fs, fs->ate_wra); + rc = zms_add_empty_ate(fs, fs->ate_wra, 0); if (rc) { return rc; } @@ -1201,6 +1208,12 @@ static int zms_gc(struct zms_fs *fs) return rc; } + /* Read full_cycle_cnt BEFORE erasing so it can be preserved */ + rc = zms_get_full_sector_cycle(fs, sec_addr, &saved_full_cycle_cnt); + if (rc && rc != -ENOENT) { + return rc; + } + /* Erase the GC'ed sector when needed */ rc = zms_flash_erase_sector(fs, sec_addr); if (rc) { @@ -1210,7 +1223,7 @@ static int zms_gc(struct zms_fs *fs) #ifdef CONFIG_ZMS_LOOKUP_CACHE zms_lookup_cache_invalidate(fs, sec_addr >> ADDR_SECT_SHIFT); #endif - rc = zms_add_empty_ate(fs, sec_addr); + rc = zms_add_empty_ate(fs, sec_addr, saved_full_cycle_cnt); return rc; } @@ -1362,7 +1375,7 @@ static int zms_init(struct zms_fs *fs) if (rc) { goto end; } - rc = zms_add_empty_ate(fs, addr); + rc = zms_add_empty_ate(fs, addr, 0); if (rc) { goto end; } @@ -1470,6 +1483,7 @@ static int zms_init(struct zms_fs *fs) */ bool gc_done_marker = false; struct zms_ate gc_done_ate; + uint32_t saved_full_cycle_cnt = 0; fs->sector_cycle = empty_ate.cycle_cnt; addr = fs->ate_wra + fs->ate_size; @@ -1491,19 +1505,27 @@ static int zms_init(struct zms_fs *fs) LOG_INF("GC Done marker found"); addr = fs->ate_wra & ADDR_SECT_MASK; zms_sector_advance(fs, &addr); + rc = zms_get_full_sector_cycle(fs, addr, &saved_full_cycle_cnt); + if (rc && rc != -ENOENT) { + goto end; + } rc = zms_flash_erase_sector(fs, addr); if (rc < 0) { goto end; } - rc = zms_add_empty_ate(fs, addr); + rc = zms_add_empty_ate(fs, addr, saved_full_cycle_cnt); goto end; } LOG_INF("No GC Done marker found: restarting gc"); + rc = zms_get_full_sector_cycle(fs, fs->ate_wra, &saved_full_cycle_cnt); + if (rc && rc != -ENOENT) { + goto end; + } rc = zms_flash_erase_sector(fs, fs->ate_wra); if (rc) { goto end; } - rc = zms_add_empty_ate(fs, fs->ate_wra); + rc = zms_add_empty_ate(fs, fs->ate_wra, saved_full_cycle_cnt); if (rc) { goto end; } From 934dd54dbed1cfbd832d2ea0c457af405250aea9 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Mon, 16 Mar 2026 19:13:32 +0200 Subject: [PATCH 08/15] [nrf fromtree] kvss: zms: add zms_get_sector_num_cycles() and Doxygen Add zms_get_sector_num_cycles() to retrieve the 32-bit cycle count for a specific sector. Signed-off-by: Mircea Caprioru (cherry picked from commit 9196b588c03adbeb48381dc0b75c9bf717d97bf0) --- include/zephyr/kvss/zms.h | 28 ++++++++++++++++++++++++++++ subsys/kvss/zms/zms.c | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/include/zephyr/kvss/zms.h b/include/zephyr/kvss/zms.h index 8a6bbb1bb441..e973b5d9b92a 100644 --- a/include/zephyr/kvss/zms.h +++ b/include/zephyr/kvss/zms.h @@ -290,7 +290,35 @@ ssize_t zms_active_sector_free_space(struct zms_fs *fs); */ int zms_sector_use_next(struct zms_fs *fs); +/** + * @brief Return the maximum sector recycle count across all sectors. + * + * Iterates all sectors and returns the highest 32-bit cycle counter stored in + * each sector's empty ATE. This can be used to estimate write-cycle + * consumption during testing. + * + * @param fs Pointer to the file system. + * + * @return Maximum number of times any single sector has been recycled. + * @retval 0 if @p fs is NULL, the file system is not mounted, or no sector has + * been recycled yet. + */ uint32_t zms_get_num_cycles(struct zms_fs *fs); + +/** + * @brief Return the recycle count for a specific sector. + * + * @param fs Pointer to the file system. + * @param sector Sector index (0-based, must be less than @c fs->sector_count). + * @param cycles Pointer to store the 32-bit cycle count. + * + * @retval 0 on success. + * @retval -EINVAL if @p fs or @p cycles is NULL, or @p sector is out of range. + * @retval -EACCES if the file system is not mounted. + * @retval -ENOENT if the sector has no valid empty ATE. + */ +int zms_get_sector_num_cycles(struct zms_fs *fs, uint32_t sector, uint32_t *cycles); + /** * @} */ diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index 1a35aedfd715..d1e1be5359f8 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -2148,3 +2148,28 @@ uint32_t zms_get_num_cycles(struct zms_fs *fs) return max_cycle_cnt; } + +int zms_get_sector_num_cycles(struct zms_fs *fs, uint32_t sector, uint32_t *cycles) +{ + int rc; + + if (!fs || !cycles) { + return -EINVAL; + } + + if (!fs->ready) { + return -EACCES; + } + + if (sector >= fs->sector_count) { + return -EINVAL; + } + + k_mutex_lock(&fs->zms_lock, K_FOREVER); + + rc = zms_get_full_sector_cycle(fs, (uint64_t)sector << ADDR_SECT_SHIFT, cycles); + + k_mutex_unlock(&fs->zms_lock); + + return rc; +} From 1469af8b93d8e6cde34e39acfa0e702e432d746d Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Mon, 16 Mar 2026 19:13:40 +0200 Subject: [PATCH 09/15] [nrf fromtree] samples: kvss: zms: add cycle count verification sample Add a sample that exercises zms_get_num_cycles() by advancing sectors in a loop and verifying the counter increments correctly, including past the uint8_t wraparound boundary at 256. Signed-off-by: Mircea Caprioru (cherry picked from commit 134cc4321348b85da9ca67deb75ac5aacebc3600) --- samples/subsys/kvss/kvss.rst | 1 + .../kvss/zms/zms_cycle_count/CMakeLists.txt | 8 + .../kvss/zms/zms_cycle_count/README.rst | 90 ++++++++++ .../subsys/kvss/zms/zms_cycle_count/prj.conf | 6 + .../kvss/zms/zms_cycle_count/sample.yaml | 15 ++ .../kvss/zms/zms_cycle_count/src/main.c | 169 ++++++++++++++++++ 6 files changed, 289 insertions(+) create mode 100644 samples/subsys/kvss/zms/zms_cycle_count/CMakeLists.txt create mode 100644 samples/subsys/kvss/zms/zms_cycle_count/README.rst create mode 100644 samples/subsys/kvss/zms/zms_cycle_count/prj.conf create mode 100644 samples/subsys/kvss/zms/zms_cycle_count/sample.yaml create mode 100644 samples/subsys/kvss/zms/zms_cycle_count/src/main.c diff --git a/samples/subsys/kvss/kvss.rst b/samples/subsys/kvss/kvss.rst index 2780a5ae36cc..81baeb43f57f 100644 --- a/samples/subsys/kvss/kvss.rst +++ b/samples/subsys/kvss/kvss.rst @@ -1,5 +1,6 @@ .. zephyr:code-sample-category:: kvss :name: Key-Value Storage Systems :show-listing: + :glob: **/* Samples that demonstrate how to interact with Key-Value Storage Systems diff --git a/samples/subsys/kvss/zms/zms_cycle_count/CMakeLists.txt b/samples/subsys/kvss/zms/zms_cycle_count/CMakeLists.txt new file mode 100644 index 000000000000..7cc51fd8a327 --- /dev/null +++ b/samples/subsys/kvss/zms/zms_cycle_count/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(zms_cycle_count) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/kvss/zms/zms_cycle_count/README.rst b/samples/subsys/kvss/zms/zms_cycle_count/README.rst new file mode 100644 index 000000000000..d556c416f890 --- /dev/null +++ b/samples/subsys/kvss/zms/zms_cycle_count/README.rst @@ -0,0 +1,90 @@ +.. zephyr:code-sample:: zms-cycle-count + :name: ZMS Cycle Count Verification + :relevant-api: zms_high_level_api + + Verify that ZMS sector cycle counters are tracked correctly across garbage + collection cycles, including past the uint8_t wraparound boundary. + +Overview +******** + + This sample exercises and verifies the ZMS sector cycle counting APIs: + + * :c:func:`zms_get_num_cycles` + * :c:func:`zms_get_sector_num_cycles` + * :c:func:`zms_sector_use_next` + + It checks that the recycle count returned by ZMS matches the expected value + after many forced sector advances, and that the count keeps growing correctly + once it exceeds the original ``uint8_t`` cycle counter range (256), which is + where the legacy field used to wrap around. + + The sample runs in three phases: + + #. **Phase 1** -- Mounts the partition, clears it, and performs 30 sector + advances. The cycle count is checked against the expected value + (``base + PHASE1_ITERATIONS / SECTOR_COUNT``). + #. **Phase 2** -- Performs additional sector advances until the relative + cycle count exceeds 256, validating that the counter keeps incrementing + past the historical ``uint8_t`` wraparound point. + #. **Phase 3** -- Reads the per-sector cycle count via + :c:func:`zms_get_sector_num_cycles` for every sector, ensures none of them + are zero, and verifies that an out-of-range sector index returns + ``-EINVAL``. + + On success the sample prints ``PASS: cycle count verification succeeded``. + +Requirements +************ + +* A board with flash support or the ``native_sim`` target + +Building and Running +******************** + +The sample can be built for several platforms. It has been tested on +``native_sim``, ``qemu_x86`` and ``nrf7002dk/nrf5340/cpuapp``. + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/kvss/zms/zms_cycle_count + :goals: build + :compact: + +Sample Output +============= + +The exact ``base`` value depends on the existing state of the storage +partition (it grows by one full cycle each time the sample is re-run against +persistent flash). The example below shows a fresh-flash run where +``base=1``; subsequent runs simply start from a higher base and end at a +higher final value, but the relative growth and the ``PASS`` line are the +same. + +.. code-block:: console + + *** Booting Zephyr OS build v4.4.0-rc2 *** + [00:00:00.000,000] fs_zms: 3 Sectors of 4096 bytes + [00:00:00.000,000] fs_zms: alloc wra: 0, fc0 + [00:00:00.000,000] fs_zms: data wra: 0, 0 + [00:00:00.000,000] fs_zms: 3 Sectors of 4096 bytes + [00:00:00.000,000] fs_zms: alloc wra: 0, fc0 + [00:00:00.000,000] fs_zms: data wra: 0, 0 + After mount: num_cycles=1 (base=1) + --- Phase 1: 30 sector advances --- + After sector_use_next[1]: num_cycles=1 + After sector_use_next[2]: num_cycles=2 + After sector_use_next[3]: num_cycles=2 + ... + After sector_use_next[30]: num_cycles=11 + Phase 1 expected=11, got=11 + --- Phase 2: 741 sector advances to exceed 256 cycles above base --- + Phase 2 progress [3/741]: num_cycles=12 + Phase 2 progress [6/741]: num_cycles=13 + ... + Phase 2 progress [741/741]: num_cycles=258 + Final expected=258, got=258 (total_advances=771) + --- Phase 3: per-sector cycle count verification --- + Sector 0: cycles=258 + Sector 1: cycles=258 + Sector 2: cycles=257 + PASS: cycle count verification succeeded (num_cycles=258 > 255) diff --git a/samples/subsys/kvss/zms/zms_cycle_count/prj.conf b/samples/subsys/kvss/zms/zms_cycle_count/prj.conf new file mode 100644 index 000000000000..ca11ea7724aa --- /dev/null +++ b/samples/subsys/kvss/zms/zms_cycle_count/prj.conf @@ -0,0 +1,6 @@ +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_ZMS=y +CONFIG_LOG=y +CONFIG_LOG_BLOCK_IN_THREAD=y +CONFIG_MAIN_STACK_SIZE=4096 diff --git a/samples/subsys/kvss/zms/zms_cycle_count/sample.yaml b/samples/subsys/kvss/zms/zms_cycle_count/sample.yaml new file mode 100644 index 000000000000..3d97845297dc --- /dev/null +++ b/samples/subsys/kvss/zms/zms_cycle_count/sample.yaml @@ -0,0 +1,15 @@ +sample: + name: ZMS Cycle Count Verification + +tests: + sample.zms.cycle_count: + tags: zms + platform_allow: + - nrf7002dk/nrf5340/cpuapp + - qemu_x86 + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "PASS: cycle count verification succeeded" diff --git a/samples/subsys/kvss/zms/zms_cycle_count/src/main.c b/samples/subsys/kvss/zms/zms_cycle_count/src/main.c new file mode 100644 index 000000000000..d102c0e7f0a7 --- /dev/null +++ b/samples/subsys/kvss/zms/zms_cycle_count/src/main.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2026 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + * + * ZMS cycle count verification sample. + * Verifies that zms_get_num_cycles() correctly tracks sector recycle counts + * across multiple garbage collection cycles, including past the uint8_t + * wraparound boundary at 256. + */ + +#include +#include +#include +#include +#include + +#define ZMS_PARTITION storage_partition +#define ZMS_PARTITION_DEVICE PARTITION_DEVICE(ZMS_PARTITION) +#define ZMS_PARTITION_OFFSET PARTITION_OFFSET(ZMS_PARTITION) + +#define SECTOR_COUNT 3 + +/* Phase 1: basic test with 30 iterations (expect num_cycles = 11) */ +#define PHASE1_ITERATIONS 30 + +/* Phase 2: push past uint8_t wraparound (256). + * After phase 1 we have accumulated PHASE1_ITERATIONS / SECTOR_COUNT cycles + * above the base. We need 256 more cycles to exceed the uint8_t range. + * PHASE2_REL_TARGET is the total relative cycles needed (above base). + */ +#define PHASE2_REL_TARGET 257 +#define PHASE2_ITERATIONS ((PHASE2_REL_TARGET - (PHASE1_ITERATIONS / SECTOR_COUNT)) \ + * SECTOR_COUNT) + +static struct zms_fs fs; + +int main(void) +{ + int rc; + struct flash_pages_info info; + uint32_t num_cycles; + uint32_t expected; + uint32_t base_cycles; + int total_advances = 0; + + fs.flash_device = ZMS_PARTITION_DEVICE; + if (!device_is_ready(fs.flash_device)) { + printk("Flash device not ready\n"); + return 0; + } + + fs.offset = ZMS_PARTITION_OFFSET; + rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info); + if (rc) { + printk("Unable to get page info: %d\n", rc); + return 0; + } + fs.sector_size = info.size; + fs.sector_count = SECTOR_COUNT; + + /* Mount and clear to start from a known state */ + rc = zms_mount(&fs); + if (rc) { + printk("FAIL: initial mount failed: %d\n", rc); + return 0; + } + rc = zms_clear(&fs); + if (rc) { + printk("FAIL: clear failed: %d\n", rc); + return 0; + } + + /* Re-mount after clear */ + rc = zms_mount(&fs); + if (rc) { + printk("FAIL: re-mount failed: %d\n", rc); + return 0; + } + + num_cycles = zms_get_num_cycles(&fs); + base_cycles = num_cycles; + printk("After mount: num_cycles=%u (base=%u)\n", num_cycles, base_cycles); + + /* === Phase 1: basic cycle count test === */ + printk("--- Phase 1: %d sector advances ---\n", PHASE1_ITERATIONS); + for (int i = 1; i <= PHASE1_ITERATIONS; i++) { + rc = zms_sector_use_next(&fs); + if (rc) { + printk("FAIL: sector_use_next returned %d at iteration %d\n", rc, i); + return 0; + } + total_advances++; + num_cycles = zms_get_num_cycles(&fs); + printk("After sector_use_next[%d]: num_cycles=%u\n", i, num_cycles); + } + + expected = base_cycles + (PHASE1_ITERATIONS / SECTOR_COUNT); + printk("Phase 1 expected=%u, got=%u\n", expected, num_cycles); + if (num_cycles != expected) { + printk("FAIL: phase 1 cycle count mismatch\n"); + return 0; + } + + /* === Phase 2: push past uint8_t boundary (256) === */ + printk("--- Phase 2: %d sector advances to exceed 256 cycles above base ---\n", + PHASE2_ITERATIONS); + for (int i = 1; i <= PHASE2_ITERATIONS; i++) { + rc = zms_sector_use_next(&fs); + if (rc) { + printk("FAIL: sector_use_next returned %d at phase2 iteration %d\n", + rc, i); + return 0; + } + total_advances++; + /* Print progress every SECTOR_COUNT advances (each full cycle) */ + if ((i % SECTOR_COUNT) == 0) { + num_cycles = zms_get_num_cycles(&fs); + printk("Phase 2 progress [%d/%d]: num_cycles=%u\n", + i, PHASE2_ITERATIONS, num_cycles); + } + } + + num_cycles = zms_get_num_cycles(&fs); + expected = base_cycles + (total_advances / SECTOR_COUNT); + printk("Final expected=%u, got=%u (total_advances=%d)\n", + expected, num_cycles, total_advances); + + if (num_cycles != expected) { + printk("FAIL: full_cycle_cnt mismatch after wraparound\n"); + return 0; + } + + if (num_cycles - base_cycles < 256) { + printk("FAIL: num_cycles=%u did not exceed uint8_t range above base=%u\n", + num_cycles, base_cycles); + return 0; + } + + /* === Phase 3: verify per-sector cycle counts === */ + printk("--- Phase 3: per-sector cycle count verification ---\n"); + for (uint32_t s = 0; s < SECTOR_COUNT; s++) { + uint32_t sector_cycles; + + rc = zms_get_sector_num_cycles(&fs, s, §or_cycles); + if (rc) { + printk("FAIL: zms_get_sector_num_cycles(sector=%u) returned %d\n", s, rc); + return 0; + } + printk("Sector %u: cycles=%u\n", s, sector_cycles); + if (sector_cycles == 0) { + printk("FAIL: sector %u has 0 cycles\n", s); + return 0; + } + } + + /* Verify invalid sector index returns -EINVAL */ + uint32_t dummy; + + rc = zms_get_sector_num_cycles(&fs, SECTOR_COUNT, &dummy); + if (rc != -EINVAL) { + printk("FAIL: out-of-range sector returned %d instead of -EINVAL\n", rc); + return 0; + } + + printk("PASS: cycle count verification succeeded (num_cycles=%u > 255)\n", num_cycles); + + return 0; +} From 642968c75a07b63618d2023886cfd8ffa82b672d Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Wed, 25 Mar 2026 13:23:52 +0200 Subject: [PATCH 10/15] [nrf fromtree] kvss: zms: decouple cycle_cnt from full_cycle_cnt and fix wipe In zms_add_empty_ate(), use zms_get_sector_cycle() for the 8-bit cycle_cnt (data validity) independently from the 32-bit full_cycle_cnt (erase tracking). Previously cycle_cnt was derived from full_cycle_cnt which broke when zms_verify_and_increment_cycle_cnt double-incremented cycle_cnt. In zms_wipe_partition(), read each sector's full_cycle_cnt before erasing so the count is preserved. This is needed for RRAM/MRAM devices where zms_flash_erase_sector is a no-op. Signed-off-by: Mircea Caprioru (cherry picked from commit a3c7d231c3ac5b9737a384966c51c90dda4c4a07) --- subsys/kvss/zms/zms.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index d1e1be5359f8..a71d626fad71 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -645,14 +645,20 @@ static int zms_wipe_partition(struct zms_fs *fs) { int rc; uint64_t addr; + uint32_t prev_cycle_cnt; for (uint32_t i = 0; i < fs->sector_count; i++) { addr = (uint64_t)i << ADDR_SECT_SHIFT; + prev_cycle_cnt = 0; + rc = zms_get_full_sector_cycle(fs, addr, &prev_cycle_cnt); + if ((rc < 0) && (rc != -ENOENT)) { + return rc; + } rc = zms_flash_erase_sector(fs, addr); if (rc) { return rc; } - rc = zms_add_empty_ate(fs, addr, 0); + rc = zms_add_empty_ate(fs, addr, prev_cycle_cnt); if (rc) { return rc; } @@ -878,7 +884,6 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr, uint32_t prev_cyc { struct zms_ate empty_ate; uint8_t cycle_cnt; - uint32_t full_cycle_cnt; int rc = 0; uint64_t previous_ate_wra; @@ -894,24 +899,23 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr, uint32_t prev_cyc FIELD_PREP(ZMS_MAGIC_NUMBER_MASK, ZMS_MAGIC_NUMBER) | FIELD_PREP(ZMS_ATE_FORMAT_MASK, ZMS_DEFAULT_ATE_FORMAT); - rc = zms_get_full_sector_cycle(fs, addr, &full_cycle_cnt); + /* Get cycle_cnt independently for data validity purposes */ + rc = zms_get_sector_cycle(fs, addr, &cycle_cnt); if (rc == -ENOENT) { - /* Sector erased or never used — use caller-provided previous count */ - cycle_cnt = (uint8_t)(prev_cycle_cnt % BIT(8)); - full_cycle_cnt = prev_cycle_cnt; + /* sector erased or never used */ + cycle_cnt = 0; } else if (rc) { /* bad flash read */ return rc; - } else { - cycle_cnt = (uint8_t)(full_cycle_cnt % BIT(8)); } - /* Increase cycle counter */ + /* Increase cycle counter for data validity */ rc = zms_verify_and_increment_cycle_cnt(fs, addr, &cycle_cnt); if (rc < 0) { return rc; } - empty_ate.full_cycle_cnt = full_cycle_cnt + 1; + /* full_cycle_cnt tracks the total erase count independently */ + empty_ate.full_cycle_cnt = prev_cycle_cnt + 1; empty_ate.cycle_cnt = cycle_cnt; zms_ate_crc8_update(&empty_ate); From c63e79b93dd6660008982fc2d490a99396e0abeb Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Thu, 9 Apr 2026 09:44:20 +0300 Subject: [PATCH 11/15] [nrf fromtree] tests: fs: zms: add testsuite for ZMS cycle count List of added tests : - zms.test_zms_cycle_count_input_validation - zms.test_zms_cycle_count_increments - zms.test_zms_cycle_count_persistence Signed-off-by: Mircea Caprioru (cherry picked from commit cce771eb53fb926319278f3716f08d4f55f1a598) --- tests/subsys/kvss/zms/src/main.c | 106 ++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/tests/subsys/kvss/zms/src/main.c b/tests/subsys/kvss/zms/src/main.c index a15afd954b0a..3c3a42839b0f 100644 --- a/tests/subsys/kvss/zms/src/main.c +++ b/tests/subsys/kvss/zms/src/main.c @@ -327,6 +327,110 @@ ZTEST_F(zms, test_zms_gc) } } +ZTEST_F(zms, test_zms_cycle_count_input_validation) +{ + int err; + uint32_t cycles; + + /* zms_get_num_cycles must safely return 0 for NULL or unmounted fs. */ + zassert_equal(zms_get_num_cycles(NULL), 0, "expected 0 for NULL fs"); + zassert_equal(zms_get_num_cycles(&fixture->fs), 0, "expected 0 on unmounted fs"); + + /* Unmounted filesystem must be rejected. */ + err = zms_get_sector_num_cycles(&fixture->fs, 0, &cycles); + zassert_equal(err, -EACCES, "expected -EACCES on unmounted fs, got %d", err); + + err = zms_mount(&fixture->fs); + zassert_true(err == 0, "zms_mount call failure: %d", err); + + /* NULL fs / NULL out pointer. */ + err = zms_get_sector_num_cycles(NULL, 0, &cycles); + zassert_equal(err, -EINVAL, "expected -EINVAL for NULL fs, got %d", err); + + err = zms_get_sector_num_cycles(&fixture->fs, 0, NULL); + zassert_equal(err, -EINVAL, "expected -EINVAL for NULL cycles, got %d", err); + + /* Sector index out of range. */ + err = zms_get_sector_num_cycles(&fixture->fs, fixture->fs.sector_count, &cycles); + zassert_equal(err, -EINVAL, "expected -EINVAL for out-of-range sector, got %d", err); +} + +ZTEST_F(zms, test_zms_cycle_count_increments) +{ + int err; + uint32_t base_cycles; + uint32_t num_cycles; + uint32_t sector_cycles; + const uint32_t advances = 9; + + fixture->fs.sector_count = 3; + + err = zms_mount(&fixture->fs); + zassert_true(err == 0, "zms_mount call failure: %d", err); + + base_cycles = zms_get_num_cycles(&fixture->fs); + + /* Each call to zms_sector_use_next advances the active sector by one; + * after sector_count advances every sector has been recycled once and + * the maximum cycle count must have grown by exactly 1. + */ + for (uint32_t i = 0; i < advances; i++) { + err = zms_sector_use_next(&fixture->fs); + zassert_true(err == 0, "zms_sector_use_next failed at %u: %d", i, err); + } + + num_cycles = zms_get_num_cycles(&fixture->fs); + zassert_equal(num_cycles, base_cycles + (advances / fixture->fs.sector_count), + "num_cycles=%u expected=%u (base=%u)", num_cycles, + base_cycles + (advances / fixture->fs.sector_count), base_cycles); + + /* Each individual sector must report a non-zero, in-range cycle count. */ + for (uint32_t s = 0; s < fixture->fs.sector_count; s++) { + err = zms_get_sector_num_cycles(&fixture->fs, s, §or_cycles); + zassert_true(err == 0, "zms_get_sector_num_cycles(%u) failed: %d", s, err); + zassert_true(sector_cycles <= num_cycles, + "sector %u cycles=%u exceeds max=%u", s, sector_cycles, + num_cycles); + } +} + +ZTEST_F(zms, test_zms_cycle_count_persistence) +{ + int err; + uint32_t base_cycles; + uint32_t num_cycles_before; + uint32_t num_cycles_after; + const uint32_t advances = 6; + + fixture->fs.sector_count = 3; + + err = zms_mount(&fixture->fs); + zassert_true(err == 0, "zms_mount call failure: %d", err); + + base_cycles = zms_get_num_cycles(&fixture->fs); + + for (uint32_t i = 0; i < advances; i++) { + err = zms_sector_use_next(&fixture->fs); + zassert_true(err == 0, "zms_sector_use_next failed at %u: %d", i, err); + } + + num_cycles_before = zms_get_num_cycles(&fixture->fs); + zassert_true(num_cycles_before > base_cycles, + "cycle count did not advance: before=%u base=%u", num_cycles_before, + base_cycles); + + /* Re-mount and ensure the cycle count survives, exercising the + * full_cycle_cnt persistence path fixed in 6aa5edc5/6db9fd35. + */ + err = zms_mount(&fixture->fs); + zassert_true(err == 0, "zms_mount (remount) call failure: %d", err); + + num_cycles_after = zms_get_num_cycles(&fixture->fs); + zassert_equal(num_cycles_after, num_cycles_before, + "cycle count not persisted across remount: before=%u after=%u", + num_cycles_before, num_cycles_after); +} + static void write_content(uint32_t max_id, uint32_t begin, uint32_t end, struct zms_fs *fs) { uint8_t buf[32]; @@ -904,7 +1008,7 @@ ZTEST_F(zms, test_zms_cache_gc) */ num = num_matching_cache_entries(0ULL << ADDR_SECT_SHIFT, true, &fixture->fs); - zassert_equal(num, 0, "not invalidated cache entries aftetr gc"); + zassert_equal(num, 0, "not invalidated cache entries after gc"); num = num_matching_cache_entries(2ULL << ADDR_SECT_SHIFT, true, &fixture->fs); zassert_equal(num, 2, "invalid cache content after gc"); From bc4bea2f0f7f6fefbe899acb987401a6ffbe8093 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Mon, 27 Apr 2026 12:56:36 +0300 Subject: [PATCH 12/15] [nrf fromtree] kvss: zms: avoid full_cycle_cnt/metadata aliasing in 64-bit mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 64-bit ATE format only has a 4-byte payload union after the 8-byte id, so empty_ate.full_cycle_cnt and empty_ate.metadata alias the same 4 bytes. zms_add_empty_ate() wrote both — metadata first (needed for format/version detection on mount) then full_cycle_cnt — and the second write silently clobbered the first. As a result, every empty ATE in 64-bit ID mode lost its magic/version, zms_init() failed to recognise sectors on remount, and tests including test_zms_gc, test_zms_full_sector and test_zms_id_64bit failed silently. In 64-bit ID mode, skip the empty_ate.full_cycle_cnt write entirely so metadata stays intact. The 8-bit empty_ate.cycle_cnt (a separate field at byte offset 1, with no aliasing) is now seeded from prev_cycle_cnt on the just-erased path so it accumulates across recycles modulo 256 and doubles as the cumulative wear counter exposed by zms_get_num_cycles() / zms_get_sector_num_cycles(). The 32-bit ID codepath keeps the existing 32-bit full_cycle_cnt range and behaviour. Trade-off: in 64-bit ID mode the cycle counter wraps every 256 cycles instead of every ~4 billion. The 32-bit ID format is unchanged. Signed-off-by: Mircea Caprioru (cherry picked from commit cd38efaa07b492901408d09f1cfd3d6190d8b1a5) --- subsys/kvss/zms/zms.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index a71d626fad71..e90d15f541d4 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -903,7 +903,11 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr, uint32_t prev_cyc rc = zms_get_sector_cycle(fs, addr, &cycle_cnt); if (rc == -ENOENT) { /* sector erased or never used */ +#if !defined(CONFIG_ZMS_ID_64BIT) cycle_cnt = 0; +#else + cycle_cnt = (uint8_t)prev_cycle_cnt; +#endif } else if (rc) { /* bad flash read */ return rc; @@ -914,8 +918,9 @@ static int zms_add_empty_ate(struct zms_fs *fs, uint64_t addr, uint32_t prev_cyc if (rc < 0) { return rc; } - /* full_cycle_cnt tracks the total erase count independently */ +#if !defined(CONFIG_ZMS_ID_64BIT) empty_ate.full_cycle_cnt = prev_cycle_cnt + 1; +#endif empty_ate.cycle_cnt = cycle_cnt; zms_ate_crc8_update(&empty_ate); @@ -974,11 +979,15 @@ static int zms_get_full_sector_cycle(struct zms_fs *fs, uint64_t addr, uint32_t } if (zms_empty_ate_valid(fs, &empty_ate)) { +#if !defined(CONFIG_ZMS_ID_64BIT) if (empty_ate.full_cycle_cnt == 0 && empty_ate.cycle_cnt > 0) { *cycle_cnt = empty_ate.cycle_cnt; } else { *cycle_cnt = empty_ate.full_cycle_cnt; } +#else + *cycle_cnt = empty_ate.cycle_cnt; +#endif return 0; } From 0a48bf156ae2f6bf4bea0a4f7974bfa411a6862a Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Wed, 13 May 2026 12:36:06 +0300 Subject: [PATCH 13/15] [nrf fromtree] kvss: zms: drop unused full_cycle_cnt from 64-bit ATE The field is no longer written in 64-bit mode, so remove it from the struct. Signed-off-by: Mircea Caprioru (cherry picked from commit b2620bad47395fb3a4e4543c5348eb4b1a7f03dc) --- subsys/kvss/zms/zms_priv.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/subsys/kvss/zms/zms_priv.h b/subsys/kvss/zms/zms_priv.h index c426d04b36de..581e28f7b12d 100644 --- a/subsys/kvss/zms/zms_priv.h +++ b/subsys/kvss/zms/zms_priv.h @@ -101,12 +101,8 @@ struct zms_ate { union { /** data field used to store small sized data */ uint8_t data[4]; - union { - /** data offset within sector */ - uint32_t offset; - /** full cycle count (for empty ATE) */ - uint32_t full_cycle_cnt; - }; + /** data offset within sector */ + uint32_t offset; /** Used to store metadata information such as storage version. */ uint32_t metadata; }; From ac5ddbae10acf631c1d5d18b6674e3194ee01bf9 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Wed, 13 May 2026 12:36:21 +0300 Subject: [PATCH 14/15] [nrf fromtree] kvss: zms: return error code from zms_get_num_cycles() Match zms_get_sector_num_cycles(): return int and pass the value through an output pointer. Signed-off-by: Mircea Caprioru (cherry picked from commit 63568b735289ad750e171074d9cf6f16b3e33a1b) --- include/zephyr/kvss/zms.h | 15 ++++++----- .../kvss/zms/zms_cycle_count/src/main.c | 24 ++++++++++++++--- subsys/kvss/zms/zms.c | 13 ++++++--- tests/subsys/kvss/zms/src/main.c | 27 +++++++++++++------ 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/include/zephyr/kvss/zms.h b/include/zephyr/kvss/zms.h index e973b5d9b92a..27a6ed82a407 100644 --- a/include/zephyr/kvss/zms.h +++ b/include/zephyr/kvss/zms.h @@ -293,17 +293,18 @@ int zms_sector_use_next(struct zms_fs *fs); /** * @brief Return the maximum sector recycle count across all sectors. * - * Iterates all sectors and returns the highest 32-bit cycle counter stored in - * each sector's empty ATE. This can be used to estimate write-cycle - * consumption during testing. + * Iterates all sectors and stores the highest 32-bit cycle counter found in + * each sector's empty ATE in @p cycles. This can be used to estimate + * write-cycle consumption during testing. * * @param fs Pointer to the file system. + * @param cycles Pointer to store the maximum 32-bit cycle count across sectors. * - * @return Maximum number of times any single sector has been recycled. - * @retval 0 if @p fs is NULL, the file system is not mounted, or no sector has - * been recycled yet. + * @retval 0 on success. + * @retval -EINVAL if @p fs or @p cycles is NULL. + * @retval -EACCES if the file system is not mounted. */ -uint32_t zms_get_num_cycles(struct zms_fs *fs); +int zms_get_num_cycles(struct zms_fs *fs, uint32_t *cycles); /** * @brief Return the recycle count for a specific sector. diff --git a/samples/subsys/kvss/zms/zms_cycle_count/src/main.c b/samples/subsys/kvss/zms/zms_cycle_count/src/main.c index d102c0e7f0a7..5780db293f92 100644 --- a/samples/subsys/kvss/zms/zms_cycle_count/src/main.c +++ b/samples/subsys/kvss/zms/zms_cycle_count/src/main.c @@ -78,7 +78,11 @@ int main(void) return 0; } - num_cycles = zms_get_num_cycles(&fs); + rc = zms_get_num_cycles(&fs, &num_cycles); + if (rc) { + printk("FAIL: zms_get_num_cycles failed: %d\n", rc); + return 0; + } base_cycles = num_cycles; printk("After mount: num_cycles=%u (base=%u)\n", num_cycles, base_cycles); @@ -91,7 +95,11 @@ int main(void) return 0; } total_advances++; - num_cycles = zms_get_num_cycles(&fs); + rc = zms_get_num_cycles(&fs, &num_cycles); + if (rc) { + printk("FAIL: zms_get_num_cycles failed: %d\n", rc); + return 0; + } printk("After sector_use_next[%d]: num_cycles=%u\n", i, num_cycles); } @@ -115,13 +123,21 @@ int main(void) total_advances++; /* Print progress every SECTOR_COUNT advances (each full cycle) */ if ((i % SECTOR_COUNT) == 0) { - num_cycles = zms_get_num_cycles(&fs); + rc = zms_get_num_cycles(&fs, &num_cycles); + if (rc) { + printk("FAIL: zms_get_num_cycles failed: %d\n", rc); + return 0; + } printk("Phase 2 progress [%d/%d]: num_cycles=%u\n", i, PHASE2_ITERATIONS, num_cycles); } } - num_cycles = zms_get_num_cycles(&fs); + rc = zms_get_num_cycles(&fs, &num_cycles); + if (rc) { + printk("FAIL: zms_get_num_cycles failed: %d\n", rc); + return 0; + } expected = base_cycles + (total_advances / SECTOR_COUNT); printk("Final expected=%u, got=%u (total_advances=%d)\n", expected, num_cycles, total_advances); diff --git a/subsys/kvss/zms/zms.c b/subsys/kvss/zms/zms.c index e90d15f541d4..c11ea28f8e55 100644 --- a/subsys/kvss/zms/zms.c +++ b/subsys/kvss/zms/zms.c @@ -2135,14 +2135,18 @@ int zms_sector_use_next(struct zms_fs *fs) return ret; } -uint32_t zms_get_num_cycles(struct zms_fs *fs) +int zms_get_num_cycles(struct zms_fs *fs, uint32_t *cycles) { uint32_t max_cycle_cnt = 0; uint32_t cycle_cnt; int rc; - if (!fs || !fs->ready) { - return 0; + if (!fs || !cycles) { + return -EINVAL; + } + + if (!fs->ready) { + return -EACCES; } k_mutex_lock(&fs->zms_lock, K_FOREVER); @@ -2159,7 +2163,8 @@ uint32_t zms_get_num_cycles(struct zms_fs *fs) k_mutex_unlock(&fs->zms_lock); - return max_cycle_cnt; + *cycles = max_cycle_cnt; + return 0; } int zms_get_sector_num_cycles(struct zms_fs *fs, uint32_t sector, uint32_t *cycles) diff --git a/tests/subsys/kvss/zms/src/main.c b/tests/subsys/kvss/zms/src/main.c index 3c3a42839b0f..f7b0ac3fe056 100644 --- a/tests/subsys/kvss/zms/src/main.c +++ b/tests/subsys/kvss/zms/src/main.c @@ -332,11 +332,17 @@ ZTEST_F(zms, test_zms_cycle_count_input_validation) int err; uint32_t cycles; - /* zms_get_num_cycles must safely return 0 for NULL or unmounted fs. */ - zassert_equal(zms_get_num_cycles(NULL), 0, "expected 0 for NULL fs"); - zassert_equal(zms_get_num_cycles(&fixture->fs), 0, "expected 0 on unmounted fs"); + /* NULL fs / NULL out pointer must be rejected. */ + err = zms_get_num_cycles(NULL, &cycles); + zassert_equal(err, -EINVAL, "expected -EINVAL for NULL fs, got %d", err); + + err = zms_get_num_cycles(&fixture->fs, NULL); + zassert_equal(err, -EINVAL, "expected -EINVAL for NULL cycles, got %d", err); /* Unmounted filesystem must be rejected. */ + err = zms_get_num_cycles(&fixture->fs, &cycles); + zassert_equal(err, -EACCES, "expected -EACCES on unmounted fs, got %d", err); + err = zms_get_sector_num_cycles(&fixture->fs, 0, &cycles); zassert_equal(err, -EACCES, "expected -EACCES on unmounted fs, got %d", err); @@ -368,7 +374,8 @@ ZTEST_F(zms, test_zms_cycle_count_increments) err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount call failure: %d", err); - base_cycles = zms_get_num_cycles(&fixture->fs); + err = zms_get_num_cycles(&fixture->fs, &base_cycles); + zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); /* Each call to zms_sector_use_next advances the active sector by one; * after sector_count advances every sector has been recycled once and @@ -379,7 +386,8 @@ ZTEST_F(zms, test_zms_cycle_count_increments) zassert_true(err == 0, "zms_sector_use_next failed at %u: %d", i, err); } - num_cycles = zms_get_num_cycles(&fixture->fs); + err = zms_get_num_cycles(&fixture->fs, &num_cycles); + zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); zassert_equal(num_cycles, base_cycles + (advances / fixture->fs.sector_count), "num_cycles=%u expected=%u (base=%u)", num_cycles, base_cycles + (advances / fixture->fs.sector_count), base_cycles); @@ -407,14 +415,16 @@ ZTEST_F(zms, test_zms_cycle_count_persistence) err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount call failure: %d", err); - base_cycles = zms_get_num_cycles(&fixture->fs); + err = zms_get_num_cycles(&fixture->fs, &base_cycles); + zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); for (uint32_t i = 0; i < advances; i++) { err = zms_sector_use_next(&fixture->fs); zassert_true(err == 0, "zms_sector_use_next failed at %u: %d", i, err); } - num_cycles_before = zms_get_num_cycles(&fixture->fs); + err = zms_get_num_cycles(&fixture->fs, &num_cycles_before); + zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); zassert_true(num_cycles_before > base_cycles, "cycle count did not advance: before=%u base=%u", num_cycles_before, base_cycles); @@ -425,7 +435,8 @@ ZTEST_F(zms, test_zms_cycle_count_persistence) err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount (remount) call failure: %d", err); - num_cycles_after = zms_get_num_cycles(&fixture->fs); + err = zms_get_num_cycles(&fixture->fs, &num_cycles_after); + zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); zassert_equal(num_cycles_after, num_cycles_before, "cycle count not persisted across remount: before=%u after=%u", num_cycles_before, num_cycles_after); From 3517dd45a71dc3702c960d882a9fe7fe7eff4187 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Wed, 13 May 2026 12:36:32 +0300 Subject: [PATCH 15/15] [nrf fromtree] tests: fs: zms: verify cycle count survives zms_clear() Extend test_zms_cycle_count_persistence to call zms_clear() between the advances and the remount and check the counter is preserved. Signed-off-by: Mircea Caprioru (cherry picked from commit fa07d33870f3d7db4e2dbcca350f483d1a13832b) --- tests/subsys/kvss/zms/src/main.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/subsys/kvss/zms/src/main.c b/tests/subsys/kvss/zms/src/main.c index f7b0ac3fe056..9ac05a7c7a5c 100644 --- a/tests/subsys/kvss/zms/src/main.c +++ b/tests/subsys/kvss/zms/src/main.c @@ -407,7 +407,8 @@ ZTEST_F(zms, test_zms_cycle_count_persistence) int err; uint32_t base_cycles; uint32_t num_cycles_before; - uint32_t num_cycles_after; + uint32_t num_cycles_after_clear; + uint32_t num_cycles_after_remount; const uint32_t advances = 6; fixture->fs.sector_count = 3; @@ -429,17 +430,32 @@ ZTEST_F(zms, test_zms_cycle_count_persistence) "cycle count did not advance: before=%u base=%u", num_cycles_before, base_cycles); + /* zms_clear must not roll the cycle counter back: the per-sector + * full_cycle_cnt is preserved (and bumped) by zms_wipe_partition. + */ + err = zms_clear(&fixture->fs); + zassert_true(err == 0, "zms_clear failed: %d", err); + + err = zms_mount(&fixture->fs); + zassert_true(err == 0, "zms_mount (after clear) call failure: %d", err); + + err = zms_get_num_cycles(&fixture->fs, &num_cycles_after_clear); + zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); + zassert_true(num_cycles_after_clear >= num_cycles_before, + "cycle count regressed across zms_clear: before=%u after=%u", + num_cycles_before, num_cycles_after_clear); + /* Re-mount and ensure the cycle count survives, exercising the - * full_cycle_cnt persistence path fixed in 6aa5edc5/6db9fd35. + * full_cycle_cnt persistence path. */ err = zms_mount(&fixture->fs); zassert_true(err == 0, "zms_mount (remount) call failure: %d", err); - err = zms_get_num_cycles(&fixture->fs, &num_cycles_after); + err = zms_get_num_cycles(&fixture->fs, &num_cycles_after_remount); zassert_true(err == 0, "zms_get_num_cycles failed: %d", err); - zassert_equal(num_cycles_after, num_cycles_before, - "cycle count not persisted across remount: before=%u after=%u", - num_cycles_before, num_cycles_after); + zassert_equal(num_cycles_after_remount, num_cycles_after_clear, + "cycle count not persisted across remount: after_clear=%u after_remount=%u", + num_cycles_after_clear, num_cycles_after_remount); } static void write_content(uint32_t max_id, uint32_t begin, uint32_t end, struct zms_fs *fs)