/* * 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; }