diff --git a/subsys/settings/Kconfig b/subsys/settings/Kconfig index 0e2e4a29e96..74ea8121870 100644 --- a/subsys/settings/Kconfig +++ b/subsys/settings/Kconfig @@ -32,6 +32,7 @@ config SETTINGS_ENCODE_LEN choice SETTINGS_BACKEND prompt "Storage back-end" + default SETTINGS_ZMS if ZMS default SETTINGS_NVS if NVS default SETTINGS_FCB if FCB default SETTINGS_FILE if FILE_SYSTEM @@ -39,6 +40,30 @@ choice SETTINGS_BACKEND help Storage back-end to be used by the settings subsystem. +config SETTINGS_ZMS + bool "ZMS (Zephyr Memory Storage)" + depends on ZMS + help + Use ZMS as settings storage backend. + +if SETTINGS_ZMS + +config SETTINGS_ZMS_NAME_CACHE + bool "ZMS name lookup cache" + help + Enable ZMS name lookup cache, used to reduce the Settings name + lookup time. + +config SETTINGS_ZMS_NAME_CACHE_SIZE + int "ZMS name lookup cache size" + default 128 + range 1 $(UINT32_MAX) + depends on SETTINGS_ZMS_NAME_CACHE + help + Number of entries in Settings ZMS name cache. + +endif # SETTINGS_ZMS + config SETTINGS_FCB bool "FCB" depends on FCB @@ -161,6 +186,20 @@ config SETTINGS_NVS_SECTOR_COUNT help Number of sectors used for the NVS settings area +config SETTINGS_ZMS_SECTOR_SIZE_MULT + int "Sector size of the ZMS settings area" + default 1 + depends on SETTINGS_ZMS + help + The sector size to use for the ZMS settings area as a multiple of + FLASH_ERASE_BLOCK_SIZE. + +config SETTINGS_ZMS_SECTOR_COUNT + int "Sector count of the ZMS settings area" + default 8 + help + Number of sectors used for the ZMS settings area + config SETTINGS_SHELL bool "Settings shell" depends on SHELL diff --git a/subsys/settings/include/settings/settings_zms.h b/subsys/settings/include/settings/settings_zms.h new file mode 100644 index 00000000000..dd9fb3aba12 --- /dev/null +++ b/subsys/settings/include/settings/settings_zms.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __SETTINGS_ZMS_H_ +#define __SETTINGS_ZMS_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* In the ZMS backend, each setting is stored in two ZMS entries: + * 1. setting's name + * 2. setting's value + * + * The ZMS entry ID for the setting's value is determined implicitly based on + * the ID of the ZMS entry for the setting's name, once that is found. The + * difference between name and value ID is constant and equal to + * ZMS_NAME_ID_OFFSET. + * + * Setting's name entries start from ZMS_NAMECNT_ID + 1. + * The entry with ID == ZMS_NAMECNT_ID is used to store the largest name ID in use. + * + * Deleted records will not be found, only the last record will be read. + */ +#define ZMS_NAMECNT_ID 0x80000000 +#define ZMS_NAME_ID_OFFSET 0x40000000 + +struct settings_zms { + struct settings_store cf_store; + struct zms_fs cf_zms; + uint32_t last_name_id; + const struct device *flash_dev; +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + struct { + uint32_t name_hash; + uint32_t name_id; + } cache[CONFIG_SETTINGS_ZMS_NAME_CACHE_SIZE]; + + uint32_t cache_next; + uint32_t cache_total; + bool loaded; +#endif +}; + +/* register zms to be a source of settings */ +int settings_zms_src(struct settings_zms *cf); + +/* register zms to be the destination of settings */ +int settings_zms_dst(struct settings_zms *cf); + +/* Initialize a zms backend. */ +int settings_zms_backend_init(struct settings_zms *cf); + +#ifdef __cplusplus +} +#endif + +#endif /* __SETTINGS_ZMS_H_ */ diff --git a/subsys/settings/src/CMakeLists.txt b/subsys/settings/src/CMakeLists.txt index 897f4a7efb6..22f987cab90 100644 --- a/subsys/settings/src/CMakeLists.txt +++ b/subsys/settings/src/CMakeLists.txt @@ -14,3 +14,4 @@ zephyr_sources_ifdef(CONFIG_SETTINGS_FCB settings_fcb.c) zephyr_sources_ifdef(CONFIG_SETTINGS_NVS settings_nvs.c) zephyr_sources_ifdef(CONFIG_SETTINGS_NONE settings_none.c) zephyr_sources_ifdef(CONFIG_SETTINGS_SHELL settings_shell.c) +zephyr_sources_ifdef(CONFIG_SETTINGS_ZMS settings_zms.c) diff --git a/subsys/settings/src/settings_zms.c b/subsys/settings/src/settings_zms.c new file mode 100644 index 00000000000..1abfd6fd98e --- /dev/null +++ b/subsys/settings/src/settings_zms.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "settings/settings_zms.h" +#include "settings_priv.h" + +#include +#include +#include +#include +LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL); + +#if DT_HAS_CHOSEN(zephyr_settings_partition) +#define SETTINGS_PARTITION DT_FIXED_PARTITION_ID(DT_CHOSEN(zephyr_settings_partition)) +#else +#define SETTINGS_PARTITION FIXED_PARTITION_ID(storage_partition) +#endif + +struct settings_zms_read_fn_arg { + struct zms_fs *fs; + uint32_t id; +}; + +static int settings_zms_load(struct settings_store *cs, const struct settings_load_arg *arg); +static int settings_zms_save(struct settings_store *cs, const char *name, const char *value, + size_t val_len); +static void *settings_zms_storage_get(struct settings_store *cs); + +static struct settings_store_itf settings_zms_itf = {.csi_load = settings_zms_load, + .csi_save = settings_zms_save, + .csi_storage_get = settings_zms_storage_get}; + +static ssize_t settings_zms_read_fn(void *back_end, void *data, size_t len) +{ + struct settings_zms_read_fn_arg *rd_fn_arg; + ssize_t rc; + + rd_fn_arg = (struct settings_zms_read_fn_arg *)back_end; + + rc = zms_read(rd_fn_arg->fs, rd_fn_arg->id, data, len); + if (rc > (ssize_t)len) { + /* zms_read signals that not all bytes were read + * align read len to what was requested + */ + rc = len; + } + return rc; +} + +int settings_zms_src(struct settings_zms *cf) +{ + cf->cf_store.cs_itf = &settings_zms_itf; + settings_src_register(&cf->cf_store); + + return 0; +} + +int settings_zms_dst(struct settings_zms *cf) +{ + cf->cf_store.cs_itf = &settings_zms_itf; + settings_dst_register(&cf->cf_store); + + return 0; +} + +#if CONFIG_SETTINGS_ZMS_NAME_CACHE +#define SETTINGS_ZMS_CACHE_OVFL(cf) ((cf)->cache_total > ARRAY_SIZE((cf)->cache)) + +static void settings_zms_cache_add(struct settings_zms *cf, const char *name, uint32_t name_id) +{ + uint32_t name_hash = sys_hash32(name, strlen(name)); + + cf->cache[cf->cache_next].name_hash = name_hash; + cf->cache[cf->cache_next++].name_id = name_id; + + cf->cache_next %= CONFIG_SETTINGS_ZMS_NAME_CACHE_SIZE; +} + +static uint32_t settings_zms_cache_match(struct settings_zms *cf, const char *name, char *rdname, + size_t len) +{ + uint32_t name_hash = sys_hash32(name, strlen(name)); + int rc; + + for (int i = 0; i < CONFIG_SETTINGS_ZMS_NAME_CACHE_SIZE; i++) { + if (cf->cache[i].name_hash != name_hash) { + continue; + } + + if (cf->cache[i].name_id <= ZMS_NAMECNT_ID) { + continue; + } + + rc = zms_read(&cf->cf_zms, cf->cache[i].name_id, rdname, len); + if (rc < 0) { + continue; + } + + rdname[rc] = '\0'; + + if (strcmp(name, rdname)) { + continue; + } + + return cf->cache[i].name_id; + } + + return ZMS_NAMECNT_ID; +} +#endif /* CONFIG_SETTINGS_ZMS_NAME_CACHE */ + +static int settings_zms_load(struct settings_store *cs, const struct settings_load_arg *arg) +{ + int ret = 0; + struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store); + struct settings_zms_read_fn_arg read_fn_arg; + char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; + char buf; + ssize_t rc1, rc2; + uint32_t name_id = ZMS_NAMECNT_ID; + +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + uint32_t cached = 0; + + cf->loaded = false; +#endif + + name_id = cf->last_name_id + 1; + + while (1) { + + name_id--; + if (name_id == ZMS_NAMECNT_ID) { +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + cf->loaded = true; + cf->cache_total = cached; +#endif + break; + } + + /* In the ZMS backend, each setting item is stored in two ZMS + * entries one for the setting's name and one with the + * setting's value. + */ + rc1 = zms_read(&cf->cf_zms, name_id, &name, sizeof(name)); + rc2 = zms_read(&cf->cf_zms, name_id + ZMS_NAME_ID_OFFSET, &buf, sizeof(buf)); + + if ((rc1 <= 0) && (rc2 <= 0)) { + /* Settings largest ID in use is invalid due to + * reset, power failure or partition overflow. + * Decrement it and check the next ID in subsequent + * iteration. + */ + if (name_id == cf->last_name_id) { + cf->last_name_id--; + zms_write(&cf->cf_zms, ZMS_NAMECNT_ID, &cf->last_name_id, + sizeof(uint32_t)); + } + + continue; + } + + if ((rc1 <= 0) || (rc2 <= 0)) { + /* Settings item is not stored correctly in the ZMS. + * ZMS entry for its name or value is either missing + * or deleted. Clean dirty entries to make space for + * future settings item. + */ + zms_delete(&cf->cf_zms, name_id); + zms_delete(&cf->cf_zms, name_id + ZMS_NAME_ID_OFFSET); + + if (name_id == cf->last_name_id) { + cf->last_name_id--; + zms_write(&cf->cf_zms, ZMS_NAMECNT_ID, &cf->last_name_id, + sizeof(uint32_t)); + } + + continue; + } + + /* Found a name, this might not include a trailing \0 */ + name[rc1] = '\0'; + read_fn_arg.fs = &cf->cf_zms; + read_fn_arg.id = name_id + ZMS_NAME_ID_OFFSET; + +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + settings_zms_cache_add(cf, name, name_id); + cached++; +#endif + + ret = settings_call_set_handler(name, SETTINGS_MAX_VAL_LEN, settings_zms_read_fn, &read_fn_arg, + (void *)arg); + if (ret) { + break; + } + } + return ret; +} + +static int settings_zms_save(struct settings_store *cs, const char *name, const char *value, + size_t val_len) +{ + struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store); + char rdname[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; + uint32_t name_id, write_name_id; + bool delete, write_name; + int rc = 0; + + if (!name) { + return -EINVAL; + } + + /* Find out if we are doing a delete */ + delete = ((value == NULL) || (val_len == 0)); + +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + bool name_in_cache = false; + + name_id = settings_zms_cache_match(cf, name, rdname, sizeof(rdname)); + if (name_id != ZMS_NAMECNT_ID) { + write_name_id = name_id; + write_name = false; + name_in_cache = true; + goto found; + } +#endif + + /* No entry with "name" is in cache, let's find if it exists in the storage */ + name_id = cf->last_name_id + 1; + write_name_id = cf->last_name_id + 1; + write_name = true; + +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + /* We can skip reading ZMS if we know that the cache wasn't overflowed. */ + if (cf->loaded && !SETTINGS_ZMS_CACHE_OVFL(cf)) { + goto found; + } +#endif + + /* Let's find if we already have an ID within storage */ + while (1) { + name_id--; + if (name_id == ZMS_NAMECNT_ID) { + break; + } + + rc = zms_read(&cf->cf_zms, name_id, &rdname, sizeof(rdname)); + + if (rc < 0) { + /* Error or entry not found */ + if (rc == -ENOENT) { + /* This is a free ID let's keep it */ + write_name_id = name_id; + } + continue; + } + + rdname[rc] = '\0'; + + if (strcmp(name, rdname)) { + /* ID exists but the name is different, that's not the ID + * we are looking for. + */ + continue; + } + + /* At this step we found the ID that corresponds to name */ + if (!delete) { + write_name_id = name_id; + write_name = false; + } + + goto found; + } + +found: + if (delete) { + if (name_id == ZMS_NAMECNT_ID) { + return 0; + } + + rc = zms_delete(&cf->cf_zms, name_id); + if (rc >= 0) { + rc = zms_delete(&cf->cf_zms, name_id + ZMS_NAME_ID_OFFSET); + } + + if (rc < 0) { + return rc; + } + + if (name_id == cf->last_name_id) { + cf->last_name_id--; + rc = zms_write(&cf->cf_zms, ZMS_NAMECNT_ID, &cf->last_name_id, + sizeof(uint32_t)); + if (rc < 0) { + /* Error: can't to store + * the largest name ID in use. + */ + return rc; + } + } + + return 0; + } + + /* No free IDs left. */ + if (write_name_id == ZMS_NAMECNT_ID + ZMS_NAME_ID_OFFSET - 1) { + return -ENOMEM; + } + + /* update the last_name_id and write to flash if required*/ + if (write_name_id > cf->last_name_id) { + cf->last_name_id = write_name_id; + rc = zms_write(&cf->cf_zms, ZMS_NAMECNT_ID, &cf->last_name_id, sizeof(uint32_t)); + if (rc < 0) { + return rc; + } + } + + /* write the value */ + rc = zms_write(&cf->cf_zms, write_name_id + ZMS_NAME_ID_OFFSET, value, val_len); + if (rc < 0) { + return rc; + } + + /* write the name if required */ + if (write_name) { + rc = zms_write(&cf->cf_zms, write_name_id, name, strlen(name)); + if (rc < 0) { + return rc; + } + } + +#if CONFIG_SETTINGS_ZMS_NAME_CACHE + if (!name_in_cache) { + settings_zms_cache_add(cf, name, write_name_id); + if (cf->loaded && !SETTINGS_ZMS_CACHE_OVFL(cf)) { + cf->cache_total++; + } + } +#endif + + return 0; +} + +/* Initialize the zms backend. */ +int settings_zms_backend_init(struct settings_zms *cf) +{ + int rc; + uint32_t last_name_id; + + cf->cf_zms.flash_device = cf->flash_dev; + if (cf->cf_zms.flash_device == NULL) { + return -ENODEV; + } + + rc = zms_mount(&cf->cf_zms); + if (rc) { + return rc; + } + + rc = zms_read(&cf->cf_zms, ZMS_NAMECNT_ID, &last_name_id, sizeof(last_name_id)); + if (rc < 0) { + cf->last_name_id = ZMS_NAMECNT_ID; + } else { + cf->last_name_id = last_name_id; + } + + LOG_DBG("Initialized"); + return 0; +} + +int settings_backend_init(void) +{ + static struct settings_zms default_settings_zms; + int rc; + uint32_t cnt = 0; + size_t zms_sector_size, zms_size = 0; + const struct flash_area *fa; + struct flash_sector hw_flash_sector; + uint32_t sector_cnt = 1; + + rc = flash_area_open(SETTINGS_PARTITION, &fa); + if (rc) { + return rc; + } + + rc = flash_area_get_sectors(SETTINGS_PARTITION, §or_cnt, &hw_flash_sector); + if (rc != 0 && rc != -ENOMEM) { + return rc; + } + + zms_sector_size = CONFIG_SETTINGS_ZMS_SECTOR_SIZE_MULT * hw_flash_sector.fs_size; + + if (zms_sector_size > UINT32_MAX) { + return -EDOM; + } + + while (cnt < CONFIG_SETTINGS_ZMS_SECTOR_COUNT) { + zms_size += zms_sector_size; + if (zms_size > fa->fa_size) { + break; + } + cnt++; + } + + /* define the zms file system using the page_info */ + default_settings_zms.cf_zms.sector_size = zms_sector_size; + default_settings_zms.cf_zms.sector_count = cnt; + default_settings_zms.cf_zms.offset = fa->fa_off; + default_settings_zms.flash_dev = fa->fa_dev; + + rc = settings_zms_backend_init(&default_settings_zms); + if (rc) { + return rc; + } + + rc = settings_zms_src(&default_settings_zms); + + if (rc) { + return rc; + } + + rc = settings_zms_dst(&default_settings_zms); + + return rc; +} + +static void *settings_zms_storage_get(struct settings_store *cs) +{ + struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store); + + return &cf->cf_zms; +} diff --git a/tests/subsys/settings/zms/CMakeLists.txt b/tests/subsys/settings/zms/CMakeLists.txt new file mode 100644 index 00000000000..218451c840e --- /dev/null +++ b/tests/subsys/settings/zms/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_settings_zms_raw) + +add_subdirectory(./src zms_test_bindir) diff --git a/tests/subsys/settings/zms/prj.conf b/tests/subsys/settings/zms/prj.conf new file mode 100644 index 00000000000..0029ceafbc8 --- /dev/null +++ b/tests/subsys/settings/zms/prj.conf @@ -0,0 +1,10 @@ +CONFIG_ZTEST=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_ZMS=y + +CONFIG_SETTINGS=y +CONFIG_SETTINGS_RUNTIME=y +CONFIG_SETTINGS_ZMS=y + +CONFIG_SYS_HASH_FUNC32=y diff --git a/tests/subsys/settings/zms/src/CMakeLists.txt b/tests/subsys/settings/zms/src/CMakeLists.txt new file mode 100644 index 00000000000..220a42849a8 --- /dev/null +++ b/tests/subsys/settings/zms/src/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 BayLibre SAS + +zephyr_include_directories( + ${ZEPHYR_BASE}/subsys/settings/include + ${ZEPHYR_BASE}/subsys/settings/src + ${ZEPHYR_BASE}/tests/subsys/settings/zms/src + ) + +target_sources(app PRIVATE settings_test_zms.c) + +add_subdirectory(../../src settings_test_bindir) diff --git a/tests/subsys/settings/zms/src/settings_test.h b/tests/subsys/settings/zms/src/settings_test.h new file mode 100644 index 00000000000..472ff71f59b --- /dev/null +++ b/tests/subsys/settings/zms/src/settings_test.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _SETTINGS_TEST_ZMS_H +#define _SETTINGS_TEST_ZMS_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint8_t val8; +extern uint8_t val8_un; +extern uint32_t val32; +extern uint64_t val64; + +extern int test_get_called; +extern int test_set_called; +extern int test_commit_called; +extern int test_export_block; + +extern struct settings_handler c_test_handlers[]; + +void ctest_clear_call_state(void); +int ctest_get_call_state(void); + +void config_wipe_srcs(void); +void *settings_config_setup(void); +void settings_config_teardown(void *fixture); + +#ifdef __cplusplus +} +#endif +#endif /* _SETTINGS_TEST_ZMS_H */ diff --git a/tests/subsys/settings/zms/src/settings_test_zms.c b/tests/subsys/settings/zms/src/settings_test_zms.c new file mode 100644 index 00000000000..17a5ccd56fe --- /dev/null +++ b/tests/subsys/settings/zms/src/settings_test_zms.c @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "settings_priv.h" +#include "settings_test.h" + +uint8_t val8; +uint8_t val8_un; +uint32_t val32; +uint64_t val64; + +int test_get_called; +int test_set_called; +int test_commit_called; +int test_export_block; + +int c1_handle_get(const char *name, char *val, int val_len_max); +int c1_handle_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg); +int c1_handle_commit(void); +int c1_handle_export(int (*cb)(const char *name, const void *value, size_t val_len)); + +struct settings_handler c_test_handlers[] = { + {.name = "myfoo", + .h_get = c1_handle_get, + .h_set = c1_handle_set, + .h_commit = c1_handle_commit, + .h_export = c1_handle_export}, +}; + +int c1_handle_get(const char *name, char *val, int val_len_max) +{ + const char *next; + + test_get_called = 1; + + if (settings_name_steq(name, "mybar", &next) && !next) { + val_len_max = MIN(val_len_max, sizeof(val8)); + memcpy(val, &val8, MIN(val_len_max, sizeof(val8))); + return val_len_max; + } + + if (settings_name_steq(name, "mybar64", &next) && !next) { + val_len_max = MIN(val_len_max, sizeof(val64)); + memcpy(val, &val64, MIN(val_len_max, sizeof(val64))); + return val_len_max; + } + + return -ENOENT; +} + +int c1_handle_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) +{ + size_t val_len; + int rc; + const char *next; + + test_set_called = 1; + if (settings_name_steq(name, "mybar", &next) && !next) { + rc = read_cb(cb_arg, &val8, sizeof(val8)); + zassert_true(rc >= 0, "SETTINGS_VALUE_SET callback"); + return 0; + } + + if (settings_name_steq(name, "mybar64", &next) && !next) { + rc = read_cb(cb_arg, &val64, sizeof(val64)); + zassert_true(rc >= 0, "SETTINGS_VALUE_SET callback"); + return 0; + } + + if (settings_name_steq(name, "unaligned", &next) && !next) { + val_len = len; + zassert_equal(val_len, sizeof(val8_un), "value length: %d, ought equal 1", val_len); + rc = read_cb(cb_arg, &val8_un, sizeof(val8_un)); + zassert_true(rc >= 0, "SETTINGS_VALUE_SET callback"); + return 0; + } + + return -ENOENT; +} + +int c1_handle_commit(void) +{ + test_commit_called = 1; + return 0; +} + +int c1_handle_export(int (*cb)(const char *name, const void *value, size_t val_len)) +{ + if (test_export_block) { + return 0; + } + + (void)cb("myfoo/mybar", &val8, sizeof(val8)); + + (void)cb("myfoo/mybar64", &val64, sizeof(val64)); + + (void)cb("myfoo/unaligned", &val8_un, sizeof(val8_un)); + + return 0; +} + +void ctest_clear_call_state(void) +{ + test_get_called = 0; + test_set_called = 0; + test_commit_called = 0; +} + +int ctest_get_call_state(void) +{ + return test_get_called + test_set_called + test_commit_called; +} + +void config_wipe_srcs(void) +{ + sys_slist_init(&settings_load_srcs); + settings_save_dst = NULL; +} + +ZTEST_SUITE(settings_config, NULL, settings_config_setup, NULL, NULL, settings_config_teardown); diff --git a/tests/subsys/settings/zms/testcase.yaml b/tests/subsys/settings/zms/testcase.yaml new file mode 100644 index 00000000000..2234fd4cd78 --- /dev/null +++ b/tests/subsys/settings/zms/testcase.yaml @@ -0,0 +1,7 @@ +tests: + settings.zms: + depends_on: zms + min_ram: 32 + tags: + - settings + - zms