diff --git a/include/zephyr/fs/zms.h b/include/zephyr/fs/zms.h index 17f9b5163d11e..b78523c79dfd4 100644 --- a/include/zephyr/fs/zms.h +++ b/include/zephyr/fs/zms.h @@ -92,6 +92,7 @@ typedef uint32_t zms_id_t; * @brief Mount a ZMS file system onto the device specified in `fs`. * * @param fs Pointer to the file system. + * @param wipe_on_failure If true, the partition will be wiped if mounting fails the first time. * * @retval 0 on success. * @retval -ENOTSUP if the detected file system is not ZMS. @@ -100,7 +101,7 @@ typedef uint32_t zms_id_t; * @retval -ENXIO if there is a device error. * @retval -EIO if there is a memory read/write error. */ -int zms_mount(struct zms_fs *fs); +int zms_mount(struct zms_fs *fs, bool wipe_on_failure); /** * @brief Clear the ZMS file system from device. The ZMS file system must be re-mounted after this diff --git a/subsys/fs/zms/zms.c b/subsys/fs/zms/zms.c index 93d140663f5c5..822a8ee6f1ad9 100644 --- a/subsys/fs/zms/zms.c +++ b/subsys/fs/zms/zms.c @@ -21,6 +21,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_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); @@ -598,6 +599,26 @@ static int zms_flash_write_entry(struct zms_fs *fs, zms_id_t id, const void *dat return 0; } +/* zms_wipe_partition erases the whole partition used by ZMS */ +static int zms_wipe_partition(struct zms_fs *fs) +{ + int rc; + uint64_t addr; + + 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) { + return rc; + } + rc = zms_add_empty_ate(fs, addr); + 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 @@ -1128,7 +1149,6 @@ static int zms_gc(struct zms_fs *fs) int zms_clear(struct zms_fs *fs) { int rc; - uint64_t addr; if (!fs) { LOG_ERR("Invalid fs"); @@ -1141,25 +1161,15 @@ int zms_clear(struct zms_fs *fs) } 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; - } - } + + rc = zms_wipe_partition(fs); /* zms needs to be reinitialized after clearing */ fs->ready = false; -end: k_mutex_unlock(&fs->zms_lock); - return 0; + return rc; } static int zms_init(struct zms_fs *fs) @@ -1459,7 +1469,7 @@ static int zms_init(struct zms_fs *fs) return rc; } -int zms_mount(struct zms_fs *fs) +int zms_mount(struct zms_fs *fs, bool wipe_on_failure) { int rc; struct flash_pages_info info; @@ -1519,6 +1529,13 @@ int zms_mount(struct zms_fs *fs) rc = zms_init(fs); + if (rc && wipe_on_failure) { + /* wipe partition and try once more */ + LOG_WRN("ZMS init failed (%d), wiping partition", rc); + zms_wipe_partition(fs); + rc = zms_init(fs); + } + if (rc) { return rc; } diff --git a/subsys/settings/Kconfig b/subsys/settings/Kconfig index 95e995d8d24b4..f4d8d8e575229 100644 --- a/subsys/settings/Kconfig +++ b/subsys/settings/Kconfig @@ -92,6 +92,13 @@ config SETTINGS_ZMS_LL_CACHE_SIZE help Number of entries in Settings ZMS linked list cache. +config SETTINGS_ZMS_WIPE_ON_MOUNT_FAILURE + bool "ZMS wipe on mount failure" + default n + help + Enable wiping of ZMS storage on mount failure to recover from + corrupted data. + endif # SETTINGS_ZMS config SETTINGS_FCB diff --git a/subsys/settings/src/settings_zms.c b/subsys/settings/src/settings_zms.c index 485c2a68b0866..5181a9cf22412 100644 --- a/subsys/settings/src/settings_zms.c +++ b/subsys/settings/src/settings_zms.c @@ -680,13 +680,14 @@ static int settings_zms_get_last_hash_ids(struct settings_zms *cf) static int settings_zms_backend_init(struct settings_zms *cf) { int rc; + bool wipe_on_failure = IS_ENABLED(CONFIG_SETTINGS_ZMS_WIPE_ON_MOUNT_FAILURE); cf->cf_zms.flash_device = cf->flash_dev; if (cf->cf_zms.flash_device == NULL) { return -ENODEV; } - rc = zms_mount(&cf->cf_zms); + rc = zms_mount(&cf->cf_zms, wipe_on_failure); if (rc) { return rc; } diff --git a/tests/subsys/fs/zms/src/main.c b/tests/subsys/fs/zms/src/main.c index 05f4c25153c23..66ceeae44b6a8 100644 --- a/tests/subsys/fs/zms/src/main.c +++ b/tests/subsys/fs/zms/src/main.c @@ -96,7 +96,7 @@ ZTEST_F(zms, test_zms_mount) { int err; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); } @@ -127,7 +127,7 @@ ZTEST_F(zms, test_zms_write) { int err; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); execute_long_pattern_write(TEST_DATA_ID, &fixture->fs); @@ -168,7 +168,7 @@ ZTEST_F(zms, test_zms_corrupted_write) uint32_t *flash_write_stat; uint32_t *flash_max_write_calls; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); err = zms_read(&fixture->fs, TEST_DATA_ID, rd_buf, sizeof(rd_buf)); @@ -211,7 +211,7 @@ ZTEST_F(zms, test_zms_corrupted_write) /* Reinitialize the ZMS. */ memset(&fixture->fs, 0, sizeof(fixture->fs)); (void)setup(); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); len = zms_read(&fixture->fs, TEST_DATA_ID, rd_buf, sizeof(rd_buf)); @@ -236,7 +236,7 @@ ZTEST_F(zms, test_zms_gc) fixture->fs.sector_count = 2; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); for (int i = 0; i < max_writes; i++) { @@ -261,7 +261,7 @@ ZTEST_F(zms, test_zms_gc) "RD buff should be equal to the WR buff"); } - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); for (int id = 0; id < max_id; id++) { @@ -330,7 +330,7 @@ ZTEST_F(zms, test_zms_gc_3sectors) fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 0, "unexpected write sector"); @@ -341,7 +341,7 @@ ZTEST_F(zms, test_zms_gc_3sectors) zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2, "unexpected write sector"); check_content(max_id, &fixture->fs); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2, "unexpected write sector"); @@ -354,7 +354,7 @@ ZTEST_F(zms, test_zms_gc_3sectors) zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 0, "unexpected write sector"); check_content(max_id, &fixture->fs); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 0, "unexpected write sector"); @@ -367,7 +367,7 @@ ZTEST_F(zms, test_zms_gc_3sectors) zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 1, "unexpected write sector"); check_content(max_id, &fixture->fs); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 1, "unexpected write sector"); @@ -380,7 +380,7 @@ ZTEST_F(zms, test_zms_gc_3sectors) zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2, "unexpected write sector"); check_content(max_id, &fixture->fs); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2, "unexpected write sector"); @@ -414,7 +414,7 @@ ZTEST_F(zms, test_zms_corrupted_sector_close_operation) stats_walk(fixture->sim_thresholds, flash_sim_max_len_find, &flash_max_len); stats_walk(fixture->sim_stats, flash_sim_write_calls_find, &flash_write_stat); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); for (int i = 0; i < max_writes; i++) { @@ -442,7 +442,7 @@ ZTEST_F(zms, test_zms_corrupted_sector_close_operation) *flash_max_write_calls = 0; *flash_max_len = 0; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); check_content(max_id, &fixture->fs); @@ -464,7 +464,7 @@ ZTEST_F(zms, test_zms_full_sector) fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); while (1) { @@ -481,7 +481,7 @@ ZTEST_F(zms, test_zms_full_sector) zassert_true(err == 0, "zms_delete call failure: %d", err); /* the last sector is full now, test re-initialization */ - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); len = zms_write(&fixture->fs, filling_id, &filling_id, sizeof(filling_id)); @@ -513,7 +513,7 @@ ZTEST_F(zms, test_delete) fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); for (filling_id = 0; filling_id < 10; filling_id++) { @@ -632,7 +632,7 @@ ZTEST_F(zms, test_zms_gc_corrupt_close_ate) fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); data = 0; @@ -684,7 +684,7 @@ ZTEST_F(zms, test_zms_gc_corrupt_ate) fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); } @@ -725,7 +725,7 @@ ZTEST_F(zms, test_zms_cache_init) /* Test cache initialization when the store is empty */ fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); num = num_occupied_cache_entries(&fixture->fs); @@ -746,7 +746,7 @@ ZTEST_F(zms, test_zms_cache_init) /* Test cache initialization when the store is non-empty */ memset(fixture->fs.lookup_cache, 0xAA, sizeof(fixture->fs.lookup_cache)); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); num = num_occupied_cache_entries(&fixture->fs); @@ -770,7 +770,7 @@ ZTEST_F(zms, test_zms_cache_collision) uint16_t data; fixture->fs.sector_count = 4; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); for (int id = 0; id < CONFIG_ZMS_LOOKUP_CACHE_SIZE + 1; id++) { @@ -810,7 +810,7 @@ ZTEST_F(zms, test_zms_cache_gc) uint16_t data = 0; fixture->fs.sector_count = 3; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); /* Fill the first sector with writes of ID 1 */ @@ -862,7 +862,7 @@ ZTEST_F(zms, test_zms_cache_hash_quality) uint32_t id; uint16_t data; - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); /* Write ZMS IDs from 0 to CONFIG_ZMS_LOOKUP_CACHE_SIZE - 1 */ @@ -885,7 +885,7 @@ ZTEST_F(zms, test_zms_cache_hash_quality) err = zms_clear(&fixture->fs); zassert_true(err == 0, "zms_clear call failure: %d", err); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); /* Write CONFIG_ZMS_LOOKUP_CACHE_SIZE ZMS IDs that form the following series: 0, 4, 8... */ @@ -913,7 +913,7 @@ ZTEST_F(zms, test_zms_input_validation) { int err; - err = zms_mount(NULL); + err = zms_mount(NULL, false); zassert_true(err == -EINVAL, "zms_mount call with NULL fs failure: %d", err); err = zms_clear(NULL); @@ -980,7 +980,7 @@ ZTEST_F(zms, test_zms_id_64bit) Z_TEST_SKIP_IFNDEF(CONFIG_ZMS_ID_64BIT); - err = zms_mount(&fixture->fs); + err = zms_mount(&fixture->fs, false); zassert_true(err == 0, "zms_mount call failure: %d", err); /* Fill the first sector with writes of different IDs */ @@ -1009,3 +1009,76 @@ ZTEST_F(zms, test_zms_id_64bit) zassert_true(len == -ENOENT, "zms_read_hist unexpected failure: %d", len); } } + +/* + * Test wipe_on_failure parameter of zms_mount(). + * Verifies that corrupted ZMS partition triggers mount failure when wipe_on_failure=false, + * but auto-recovery when wipe_on_failure=true. + */ +ZTEST_F(zms, test_zms_mount_wipe_on_failure) +{ + int err; + struct zms_ate close_ate; + struct zms_ate empty_ate; + uint32_t test_data = 0xDEADBEEF; + ssize_t len; + + /* Construct valid ZMS ATEs with UNSUPPORTED VERSION (version=2). + * This will cause zms_init to detect ZMS magic but fail with -EPROTONOSUPPORT. + */ + + /* Create close_ate with version=2, magic=0x42, format=0 */ + memset(&close_ate, 0, sizeof(close_ate)); + close_ate.id = ZMS_HEAD_ID; + close_ate.len = 0; + close_ate.cycle_cnt = 1; + close_ate.offset = 2 * sizeof(struct zms_ate); /* Points to close ATE location */ + close_ate.metadata = FIELD_PREP(ZMS_VERSION_MASK, 2) | /* Unsupported version */ + FIELD_PREP(ZMS_MAGIC_NUMBER_MASK, ZMS_MAGIC_NUMBER) | + FIELD_PREP(ZMS_ATE_FORMAT_MASK, ZMS_DEFAULT_ATE_FORMAT); + close_ate.crc8 = crc8_ccitt(0xff, (uint8_t *)&close_ate + SIZEOF_FIELD(struct zms_ate, crc8), + sizeof(struct zms_ate) - SIZEOF_FIELD(struct zms_ate, crc8)); + + /* Create empty_ate with version=2 */ + memset(&empty_ate, 0, sizeof(empty_ate)); + empty_ate.id = ZMS_HEAD_ID; + empty_ate.len = 0xffff; /* Empty ATE marker */ + empty_ate.cycle_cnt = 1; + empty_ate.metadata = FIELD_PREP(ZMS_VERSION_MASK, 2) | /* Unsupported version */ + FIELD_PREP(ZMS_MAGIC_NUMBER_MASK, ZMS_MAGIC_NUMBER) | + FIELD_PREP(ZMS_ATE_FORMAT_MASK, ZMS_DEFAULT_ATE_FORMAT); + empty_ate.crc8 = crc8_ccitt(0xff, (uint8_t *)&empty_ate + SIZEOF_FIELD(struct zms_ate, crc8), + sizeof(struct zms_ate) - SIZEOF_FIELD(struct zms_ate, crc8)); + + /* Erase sector 0 before writing corrupted ATEs */ + err = flash_erase(fixture->fs.flash_device, fixture->fs.offset, fixture->fs.sector_size); + zassert_true(err == 0, "flash_erase failed: %d", err); + + /* Write corrupted ATEs to sector 0 */ + err = flash_write(fixture->fs.flash_device, + fixture->fs.offset + fixture->fs.sector_size - sizeof(struct zms_ate), + &empty_ate, sizeof(empty_ate)); + zassert_true(err == 0, "flash_write empty_ate failed: %d", err); + + err = flash_write(fixture->fs.flash_device, + fixture->fs.offset + fixture->fs.sector_size - 2 * sizeof(struct zms_ate), + &close_ate, sizeof(close_ate)); + zassert_true(err == 0, "flash_write close_ate failed: %d", err); + + /* Test 1: Mount with wipe_on_failure=false should FAIL with -EPROTONOSUPPORT */ + err = zms_mount(&fixture->fs, false); + zassert_equal(err, -EPROTONOSUPPORT, + "zms_mount should fail with -EPROTONOSUPPORT, got: %d", err); + + /* Test 2: Mount with wipe_on_failure=true should SUCCEED (auto-wipe and recover) */ + err = zms_mount(&fixture->fs, false); + zassert_true(err == 0, "zms_mount with wipe_on_failure should succeed: %d", err); + + /* Verify that the filesystem is functional after auto-wipe */ + len = zms_write(&fixture->fs, 1, &test_data, sizeof(test_data)); + zassert_equal(len, sizeof(test_data), "zms_write after recovery failed: %d", len); + + len = zms_read(&fixture->fs, 1, &test_data, sizeof(test_data)); + zassert_equal(len, sizeof(test_data), "zms_read after recovery failed: %d", len); + zassert_equal(test_data, 0xDEADBEEF, "read data mismatch after recovery"); +}