Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions subsys/settings/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions subsys/settings/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
303 changes: 303 additions & 0 deletions subsys/settings/src/settings_retention.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <errno.h>
#include <stdbool.h>
#include <string.h>

#include <zephyr/logging/log.h>
#include <zephyr/retention/retention.h>
#include <zephyr/settings/settings.h>
#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;

Check notice on line 159 in subsys/settings/src/settings_retention.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/settings/src/settings_retention.c:159 - LOG_ERR("Invalid name length: %d, max supported: %d", - 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); + LOG_ERR("Invalid value length: %d, max supported: %d", lengths.length_name, + SETTINGS_MAX_VAL_LEN);
} 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);

Check notice on line 254 in subsys/settings/src/settings_retention.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

subsys/settings/src/settings_retention.c:254 - uint8_t empty_data[sizeof(lengths)] = { 0x00 }; + 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); + 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;
}
7 changes: 7 additions & 0 deletions tests/subsys/settings/retention/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
39 changes: 39 additions & 0 deletions tests/subsys/settings/retention/boards/nrf52840dk_nrf52840.overlay
Original file line number Diff line number Diff line change
@@ -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)>;
};
Loading
Loading