From 090fdbf6786e5d5d341da633544c86d8faeb73cc Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Tue, 15 Jul 2025 14:16:10 +0100 Subject: [PATCH 1/2] [nrf fromlist] settings: Add retention backend Adds a retention backend for settings Upstream PR #: 93151 Signed-off-by: Jamie McCrae --- subsys/settings/Kconfig | 14 ++ subsys/settings/src/CMakeLists.txt | 1 + subsys/settings/src/settings_retention.c | 303 +++++++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 subsys/settings/src/settings_retention.c diff --git a/subsys/settings/Kconfig b/subsys/settings/Kconfig index fdb3c4bb3ce..5b6a76052b2 100644 --- a/subsys/settings/Kconfig +++ b/subsys/settings/Kconfig @@ -30,12 +30,20 @@ config SETTINGS_DYNAMIC_HANDLERS config SETTINGS_ENCODE_LEN bool +DT_CHOSEN_ZEPHYR_SETTINGS_PARTITION := zephyr,settings-partition +DT_ZEPHYR_RETENTION := zephyr,retention + +config SETTINGS_SUPPORTED_RETENTION + bool + default y if RETENTION && $(dt_chosen_has_compat,$(DT_CHOSEN_ZEPHYR_SETTINGS_PARTITION),$(DT_ZEPHYR_RETENTION)) + 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 + default SETTINGS_RETENTION if SETTINGS_SUPPORTED_RETENTION default SETTINGS_NONE help Storage back-end to be used by the settings subsystem. @@ -104,6 +112,12 @@ config SETTINGS_NVS_NAME_CACHE_SIZE endif # SETTINGS_NVS +config SETTINGS_RETENTION + bool "Retention storage support" + depends on SETTINGS_SUPPORTED_RETENTION + help + Enables retention storage support (bulk load/save supported only). + config SETTINGS_CUSTOM bool "CUSTOM" help diff --git a/subsys/settings/src/CMakeLists.txt b/subsys/settings/src/CMakeLists.txt index 4aa797fbb35..705fd3dc77d 100644 --- a/subsys/settings/src/CMakeLists.txt +++ b/subsys/settings/src/CMakeLists.txt @@ -14,3 +14,4 @@ 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) +zephyr_sources_ifdef(CONFIG_SETTINGS_RETENTION settings_retention.c) diff --git a/subsys/settings/src/settings_retention.c b/subsys/settings/src/settings_retention.c new file mode 100644 index 00000000000..931bf02cb9f --- /dev/null +++ b/subsys/settings/src/settings_retention.c @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include "settings_priv.h" + +LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL); + +#if !DT_HAS_CHOSEN(zephyr_settings_partition) +#error "Missing zephyr,settings-partition chosen node" +#elif !DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_settings_partition), zephyr_retention) +#error "zephyr,settings-partition must be a zephyr,retention node" +#endif + +/* + * Retention storage stores each setting in the following format: + * uint16_t length_name + * uint16_t length_value + * uint8_t name[...] + * uint8_t value[...] + * + * Each setting is placed sequentially into the retention memory area, it is assumed that the + * checksum feature is used to ensure data validity upon loading settings from the retained + * memory though this is optional. + * + * Upon saving settings, the whole retention area is cleared first, then settings are written + * one-by-one, it is only supported to save/load all settings in one go. + */ + +/** Retention settings context object */ +struct settings_retention { + /** Settings storage */ + struct settings_store cf_store; + + /** Retention device */ + const struct device *cf_retention; + + /** Last write position when setting was saved */ + uint32_t last_write_pos; +}; + +/** Length of name and value object, used when reading/saving settings */ +struct settings_retention_lengths { + /** Length of name */ + uint16_t length_name; + + /** Length of value */ + uint16_t length_value; + + /* Name and value byte arrays follow past this point... */ +}; + +BUILD_ASSERT(sizeof(struct settings_retention_lengths) == sizeof(uint16_t) + sizeof(uint16_t)); + +/** Used with read callback */ +struct settings_retention_read_arg { + /** Retention device */ + const struct device *cf_retention; + + /** Offset to read from */ + uint32_t offset; +}; + +static int settings_retention_load(struct settings_store *cs, const struct settings_load_arg *arg); +static int settings_retention_save(struct settings_store *cs, const char *name, const char *value, + size_t val_len); +static void *settings_retention_storage_get(struct settings_store *cs); +static int settings_retention_save_start(struct settings_store *cs); + +static const struct device *storage_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_settings_partition)); + +static const struct settings_store_itf settings_retention_itf = { + .csi_load = settings_retention_load, + .csi_save = settings_retention_save, + .csi_storage_get = settings_retention_storage_get, + .csi_save_start = settings_retention_save_start, +}; + +static int settings_retention_src(struct settings_retention *cf) +{ + if (!retention_is_valid(cf->cf_retention)) { + return -EIO; + } + + cf->cf_store.cs_itf = &settings_retention_itf; + settings_src_register(&cf->cf_store); + + return 0; +} + +static int settings_retention_dst(struct settings_retention *cf) +{ + cf->cf_store.cs_itf = &settings_retention_itf; + settings_dst_register(&cf->cf_store); + + return 0; +} + +static int settings_retention_read_value(void *cb_arg, void *data, size_t len) +{ + int rc; + struct settings_retention_read_arg *ctx = cb_arg; + + rc = retention_read(ctx->cf_retention, ctx->offset, data, len); + + if (rc != 0) { + return rc; + } + + return len; +} + +static int settings_retention_load(struct settings_store *cs, const struct settings_load_arg *arg) +{ + int rc; + uint32_t pos = 0; + struct settings_retention *cf = CONTAINER_OF(cs, struct settings_retention, cf_store); + uint32_t max_pos = retention_size(cf->cf_retention); + struct settings_retention_read_arg read_arg = { + .cf_retention = cf->cf_retention, + }; + + while (pos < max_pos) { + struct settings_retention_lengths lengths; + char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; + + if ((pos + sizeof(lengths)) >= max_pos) { + return -EIO; + } + + /* Read lengths and check validity */ + rc = retention_read(cf->cf_retention, pos, (uint8_t *)&lengths, sizeof(lengths)); + + if (rc != 0) { + return rc; + } + + if ((lengths.length_name == 0 && lengths.length_value == 0) || + (lengths.length_name == USHRT_MAX && lengths.length_value == USHRT_MAX)) { + /* Empty data, finished loading */ + LOG_DBG("Finished loading retentions settings, size: 0x%x", pos); + break; + } else if (lengths.length_name > SETTINGS_MAX_NAME_LEN) { + LOG_ERR("Invalid name length: %d, max supported: %d", + lengths.length_name, SETTINGS_MAX_NAME_LEN); + return -EIO; + } else if (lengths.length_value > SETTINGS_MAX_VAL_LEN) { + LOG_ERR("Invalid value length: %d, max supported: %d", + lengths.length_name, SETTINGS_MAX_VAL_LEN); + return -EIO; + } else if ((lengths.length_name + lengths.length_value + pos) > max_pos) { + LOG_ERR("Data length goes beyond retention area: 0x%x, max size: 0x%x", + (lengths.length_name + lengths.length_value + pos), max_pos); + return -EIO; + } + + /* Read values */ + pos += sizeof(lengths); + rc = retention_read(cf->cf_retention, pos, name, lengths.length_name); + + if (rc != 0) { + return rc; + } + + name[lengths.length_name] = '\0'; + pos += lengths.length_name; + read_arg.offset = pos; + + rc = settings_call_set_handler(name, lengths.length_value, + &settings_retention_read_value, &read_arg, arg); + + if (rc != 0) { + return rc; + } + + pos += lengths.length_value; + } + + return 0; +} + +static int settings_retention_save(struct settings_store *cs, const char *name, const char *value, + size_t val_len) +{ + struct settings_retention *cf = CONTAINER_OF(cs, struct settings_retention, cf_store); + struct settings_retention_lengths lengths; + uint32_t off = cf->last_write_pos; + int rc = -EINVAL; + + if (name == NULL || (value == NULL && val_len > 0)) { + return -EINVAL; + } + + lengths.length_name = (uint16_t)strlen(name); + lengths.length_value = (uint16_t)val_len; + + if (lengths.length_name == 0) { + return -EINVAL; + } else if ((cf->last_write_pos + sizeof(lengths) + lengths.length_name + val_len) > + retention_size(cf->cf_retention)) { + return -E2BIG; + } + + /* Write data before writing length header to ensure that if something happens before one + * is written then the data is not wrongly seen as valid upon reading, as would be the + * case if it was partially written + */ + off += sizeof(lengths); + rc = retention_write(cf->cf_retention, off, name, lengths.length_name); + + if (rc != 0) { + return rc; + } + + off += lengths.length_name; + rc = retention_write(cf->cf_retention, off, value, val_len); + + if (rc != 0) { + goto tidy; + } + + rc = retention_write(cf->cf_retention, cf->last_write_pos, (uint8_t *)&lengths, + sizeof(lengths)); + + if (rc != 0) { + goto tidy; + } + + off += val_len; + cf->last_write_pos = off; + +tidy: + if (rc != 0) { + /* Attempt to clear data header that was partially written */ + uint8_t empty_data[sizeof(lengths)] = { 0x00 }; + uint8_t l = sizeof(lengths) + lengths.length_name + val_len; + uint8_t i = 0; + + while (i < l) { + uint8_t write_len = (i + sizeof(empty_data)) > l ? (l - i) : + sizeof(empty_data); + + rc = retention_write(cf->cf_retention, (cf->last_write_pos + i), + empty_data, write_len); + + if (rc != 0) { + break; + } + + i += write_len; + } + } + + return rc; +} + +static int settings_retention_save_start(struct settings_store *cs) +{ + struct settings_retention *cf = CONTAINER_OF(cs, struct settings_retention, cf_store); + + cf->last_write_pos = 0; + + return retention_clear(cf->cf_retention); +} + +int settings_backend_init(void) +{ + int rc; + static struct settings_retention config_init_settings_retention; + + if (!device_is_ready(storage_dev)) { + return -ENOENT; + } + + config_init_settings_retention.cf_retention = storage_dev; + config_init_settings_retention.last_write_pos = 0; + + rc = settings_retention_src(&config_init_settings_retention); + + if (rc != 0 && rc != -EIO) { + return rc; + } + + rc = settings_retention_dst(&config_init_settings_retention); + + return rc; +} + +static void *settings_retention_storage_get(struct settings_store *cs) +{ + struct settings_retention *cf = CONTAINER_OF(cs, struct settings_retention, cf_store); + + return &cf->cf_retention; +} From ba22e5179f979cb309fb2ec4640c2f23e67fd791 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Tue, 15 Jul 2025 14:17:04 +0100 Subject: [PATCH 2/2] [nrf fromlist] tests: settings: Add retention test A clone of the zms test, using the retention backend, with support for nrf52840dk and qemu_cortex_m3 Upstream PR #: 93151 Signed-off-by: Jamie McCrae --- .../subsys/settings/retention/CMakeLists.txt | 7 + .../boards/nrf52840dk_nrf52840.overlay | 39 ++++++ .../retention/boards/qemu_cortex_m3.overlay | 35 +++++ tests/subsys/settings/retention/prj.conf | 7 + .../settings/retention/src/CMakeLists.txt | 12 ++ tests/subsys/settings/retention/src/main.c | 130 ++++++++++++++++++ .../settings/retention/src/settings_test.h | 42 ++++++ tests/subsys/settings/retention/testcase.yaml | 9 ++ 8 files changed, 281 insertions(+) create mode 100644 tests/subsys/settings/retention/CMakeLists.txt create mode 100644 tests/subsys/settings/retention/boards/nrf52840dk_nrf52840.overlay create mode 100644 tests/subsys/settings/retention/boards/qemu_cortex_m3.overlay create mode 100644 tests/subsys/settings/retention/prj.conf create mode 100644 tests/subsys/settings/retention/src/CMakeLists.txt create mode 100644 tests/subsys/settings/retention/src/main.c create mode 100644 tests/subsys/settings/retention/src/settings_test.h create mode 100644 tests/subsys/settings/retention/testcase.yaml diff --git a/tests/subsys/settings/retention/CMakeLists.txt b/tests/subsys/settings/retention/CMakeLists.txt new file mode 100644 index 00000000000..315cbacf594 --- /dev/null +++ b/tests/subsys/settings/retention/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(settings_retention) + +add_subdirectory(./src zms_test_bindir) diff --git a/tests/subsys/settings/retention/boards/nrf52840dk_nrf52840.overlay b/tests/subsys/settings/retention/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 00000000000..e7806a54b85 --- /dev/null +++ b/tests/subsys/settings/retention/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + /delete-property/ zephyr,boot-mode; + }; + + sram@2003F000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2003F000 DT_SIZE_K(4)>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + settings_partition0: settings_partition@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x1000>; + }; + }; + }; + + chosen { + zephyr,settings-partition = &settings_partition0; + }; +}; + +&sram0 { + reg = <0x20000000 DT_SIZE_K(252)>; +}; diff --git a/tests/subsys/settings/retention/boards/qemu_cortex_m3.overlay b/tests/subsys/settings/retention/boards/qemu_cortex_m3.overlay new file mode 100644 index 00000000000..fe9bab6b727 --- /dev/null +++ b/tests/subsys/settings/retention/boards/qemu_cortex_m3.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + sram@2000F000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2000F000 0x1000>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + settings_partition0: settings_partition@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x1000>; + }; + }; + }; + + chosen { + zephyr,settings-partition = &settings_partition0; + }; +}; + +&sram0 { + reg = <0x20000000 0xf000>; +}; diff --git a/tests/subsys/settings/retention/prj.conf b/tests/subsys/settings/retention/prj.conf new file mode 100644 index 00000000000..ae8afe82d74 --- /dev/null +++ b/tests/subsys/settings/retention/prj.conf @@ -0,0 +1,7 @@ +CONFIG_ZTEST=y +CONFIG_RETAINED_MEM=y +CONFIG_RETENTION=y + +CONFIG_SETTINGS=y +CONFIG_SETTINGS_RUNTIME=y +CONFIG_SETTINGS_RETENTION=y diff --git a/tests/subsys/settings/retention/src/CMakeLists.txt b/tests/subsys/settings/retention/src/CMakeLists.txt new file mode 100644 index 00000000000..b2897bddcf9 --- /dev/null +++ b/tests/subsys/settings/retention/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 main.c) + +add_subdirectory(../../src settings_test_bindir) diff --git a/tests/subsys/settings/retention/src/main.c b/tests/subsys/settings/retention/src/main.c new file mode 100644 index 00000000000..3673e0b9810 --- /dev/null +++ b/tests/subsys/settings/retention/src/main.c @@ -0,0 +1,130 @@ +/* + * 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; + + if (val_len_max < 0) { + return -EINVAL; + } + + 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: %zd, 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/retention/src/settings_test.h b/tests/subsys/settings/retention/src/settings_test.h new file mode 100644 index 00000000000..6a53260e1d4 --- /dev/null +++ b/tests/subsys/settings/retention/src/settings_test.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _SETTINGS_TEST_H +#define _SETTINGS_TEST_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_H */ diff --git a/tests/subsys/settings/retention/testcase.yaml b/tests/subsys/settings/retention/testcase.yaml new file mode 100644 index 00000000000..adbd61e4701 --- /dev/null +++ b/tests/subsys/settings/retention/testcase.yaml @@ -0,0 +1,9 @@ +tests: + settings.retention: + platform_allow: + - nrf52840dk/nrf52840 + - qemu_cortex_m3 + min_ram: 8 + tags: + - settings + - retention