From 88bb7597b13faecae3d44b96a3763cc2edbcf034 Mon Sep 17 00:00:00 2001 From: Kamil Piszczek Date: Mon, 24 Jun 2019 11:10:18 +0200 Subject: [PATCH] settings: adding new nvs backend Added NVS backend to the Settings subsystem. Signed-off-by: Kamil Piszczek --- subsys/settings/Kconfig | 21 ++ .../settings/include/settings/settings_nvs.h | 57 +++++ subsys/settings/src/CMakeLists.txt | 1 + subsys/settings/src/settings_init.c | 52 ++++ subsys/settings/src/settings_nvs.c | 238 ++++++++++++++++++ 5 files changed, 369 insertions(+) create mode 100644 subsys/settings/include/settings/settings_nvs.h create mode 100644 subsys/settings/src/settings_nvs.c diff --git a/subsys/settings/Kconfig b/subsys/settings/Kconfig index 6978f0491c2..5e98df93b97 100644 --- a/subsys/settings/Kconfig +++ b/subsys/settings/Kconfig @@ -65,6 +65,12 @@ config SETTINGS_FS help Use a file system as a settings storage back-end. +config SETTINGS_NVS + bool "NVS non-volatile storage support" + depends on SETTINGS + help + Enables NVS storage support + config SETTINGS_CUSTOM bool "CUSTOM" help @@ -111,3 +117,18 @@ config SETTINGS_FS_MAX_LINES depends on SETTINGS && SETTINGS_FS help Limit how many items stored in a file before compressing + +config SETTINGS_NVS_SECTOR_SIZE_MULT + int "Sector size of the NVS settings area" + default 1 + depends on SETTINGS && SETTINGS_NVS + help + The sector size to use for the NVS settings area as a multiple of + FLASH_ERASE_BLOCK_SIZE. + +config SETTINGS_NVS_SECTOR_COUNT + int "Sector count of the NVS settings area" + default 8 + depends on SETTINGS && SETTINGS_NVS + help + Number of sectors used for the NVS settings area diff --git a/subsys/settings/include/settings/settings_nvs.h b/subsys/settings/include/settings/settings_nvs.h new file mode 100644 index 00000000000..1c2ae2d7094 --- /dev/null +++ b/subsys/settings/include/settings/settings_nvs.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Laczen + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __SETTINGS_NVS_H_ +#define __SETTINGS_NVS_H_ + +#include +#include "settings/settings.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* In the NVS backend, each setting is stored in two NVS entries: + * 1. setting's name + * 2. setting's value + * + * The NVS entry ID for the setting's value is determined implicitly based on + * the ID of the NVS entry for the setting's name, once that is found. The + * difference between name and value ID is constant and equal to + * NVS_NAME_ID_OFFSET. + * + * Setting's name entries start from NVS_NAMECNT_ID + 1. The entry at + * NVS_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 NVS_NAMECNT_ID 0x8000 +#define NVS_NAME_ID_OFFSET 0x4000 + +struct settings_nvs { + struct settings_store cf_store; + struct nvs_fs cf_nvs; + u16_t last_name_id; + const char *flash_dev_name; +}; + +/* register nvs to be a source of settings */ +int settings_nvs_src(struct settings_nvs *cf); + +/* register nvs to be the destination of settings */ +int settings_nvs_dst(struct settings_nvs *cf); + +/* Initialize a nvs backend. */ +int settings_nvs_backend_init(struct settings_nvs *cf); + + +#ifdef __cplusplus +} +#endif + +#endif /* __SETTINGS_NVS_H_ */ diff --git a/subsys/settings/src/CMakeLists.txt b/subsys/settings/src/CMakeLists.txt index ecae3f965c5..6b7142f9753 100644 --- a/subsys/settings/src/CMakeLists.txt +++ b/subsys/settings/src/CMakeLists.txt @@ -10,3 +10,4 @@ zephyr_sources( zephyr_sources_ifdef(CONFIG_SETTINGS_RUNTIME settings_runtime.c) zephyr_sources_ifdef(CONFIG_SETTINGS_FS settings_file.c) zephyr_sources_ifdef(CONFIG_SETTINGS_FCB settings_fcb.c) +zephyr_sources_ifdef(CONFIG_SETTINGS_NVS settings_nvs.c) diff --git a/subsys/settings/src/settings_init.c b/subsys/settings/src/settings_init.c index 601364d2c50..e1d03c286fb 100644 --- a/subsys/settings/src/settings_init.c +++ b/subsys/settings/src/settings_init.c @@ -117,6 +117,58 @@ int settings_backend_init(void) return rc; } + +#elif defined(CONFIG_SETTINGS_NVS) +#include +#include +#include "settings/settings_nvs.h" + +static struct settings_nvs default_settings_nvs; + +int settings_backend_init(void) +{ + int rc; + u16_t cnt = 0; + size_t nvs_sector_size, nvs_size = 0; + const struct flash_area *fa; + + rc = flash_area_open(DT_FLASH_AREA_STORAGE_ID, &fa); + if (rc) { + return rc; + } + + nvs_sector_size = CONFIG_SETTINGS_NVS_SECTOR_SIZE_MULT * + DT_FLASH_ERASE_BLOCK_SIZE; + while (cnt < CONFIG_SETTINGS_NVS_SECTOR_COUNT) { + nvs_size += nvs_sector_size; + if (nvs_size > fa->fa_size) { + break; + } + cnt++; + } + + /* define the nvs file system using the page_info */ + default_settings_nvs.cf_nvs.sector_size = nvs_sector_size; + default_settings_nvs.cf_nvs.sector_count = cnt; + default_settings_nvs.cf_nvs.offset = fa->fa_off; + default_settings_nvs.flash_dev_name = fa->fa_dev_name; + + rc = settings_nvs_backend_init(&default_settings_nvs); + if (rc) { + return rc; + } + + rc = settings_nvs_src(&default_settings_nvs); + + if (rc) { + return rc; + } + + rc = settings_nvs_dst(&default_settings_nvs); + + return rc; +} + #elif defined(CONFIG_SETTINGS_NONE) int settings_backend_init(void) { diff --git a/subsys/settings/src/settings_nvs.c b/subsys/settings/src/settings_nvs.c new file mode 100644 index 00000000000..64dc68fbbbc --- /dev/null +++ b/subsys/settings/src/settings_nvs.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 Laczen + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "settings/settings.h" +#include "settings/settings_nvs.h" +#include "settings_priv.h" + +#include +LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL); + +struct settings_nvs_read_fn_arg { + struct nvs_fs *fs; + u16_t id; +}; + +static int settings_nvs_load(struct settings_store *cs, const char *subtree); +static int settings_nvs_save(struct settings_store *cs, const char *name, + const char *value, size_t val_len); + +static struct settings_store_itf settings_nvs_itf = { + .csi_load = settings_nvs_load, + .csi_save = settings_nvs_save, +}; + +static ssize_t settings_nvs_read_fn(void *back_end, void *data, size_t len) +{ + struct settings_nvs_read_fn_arg *rd_fn_arg; + + rd_fn_arg = (struct settings_nvs_read_fn_arg *)back_end; + + return nvs_read(rd_fn_arg->fs, rd_fn_arg->id, data, len); +} + +int settings_nvs_src(struct settings_nvs *cf) +{ + cf->cf_store.cs_itf = &settings_nvs_itf; + settings_src_register(&cf->cf_store); + + return 0; +} + +int settings_nvs_dst(struct settings_nvs *cf) +{ + cf->cf_store.cs_itf = &settings_nvs_itf; + settings_dst_register(&cf->cf_store); + + return 0; +} + +static int settings_nvs_load(struct settings_store *cs, const char *subtree) +{ + struct settings_nvs *cf = (struct settings_nvs *)cs; + struct settings_nvs_read_fn_arg read_fn_arg; + struct settings_handler *ch; + char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; + char buf; + const char *name_argv; + ssize_t rc1, rc2; + u16_t name_id = NVS_NAMECNT_ID; + + name_id = cf->last_name_id + 1; + + while (1) { + + name_id--; + if (name_id == NVS_NAMECNT_ID) { + break; + } + + /* In the NVS backend, each setting item is stored in two NVS + * entries one for the setting's name and one with the + * setting's value. + */ + rc1 = nvs_read(&cf->cf_nvs, name_id, &name, sizeof(name)); + rc2 = nvs_read(&cf->cf_nvs, name_id + NVS_NAME_ID_OFFSET, + &buf, sizeof(buf)); + + if ((rc1 <= 0) && (rc2 <= 0)) { + continue; + } + + if ((rc1 <= 0) || (rc2 <= 0)) { + /* Settings item is not stored correctly in the NVS. + * NVS entry for its name or value is either missing + * or deleted. Clean dirty entries to make space for + * future settings item. + */ + if (name_id == cf->last_name_id) { + cf->last_name_id--; + nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, + &cf->last_name_id, sizeof(u16_t)); + } + nvs_delete(&cf->cf_nvs, name_id); + nvs_delete(&cf->cf_nvs, name_id + NVS_NAME_ID_OFFSET); + continue; + } + + /* Found a name, this might not include a trailing \0 */ + name[rc1] = '\0'; + + if (subtree && !settings_name_steq(name, subtree, NULL)) { + continue; + } + + ch = settings_parse_and_lookup(name, &name_argv); + if (!ch) { + continue; + } + + read_fn_arg.fs = &cf->cf_nvs; + read_fn_arg.id = name_id + NVS_NAME_ID_OFFSET; + ch->h_set(name_argv, rc2, settings_nvs_read_fn, + (void *) &read_fn_arg); + } + return 0; +} + +static int settings_nvs_save(struct settings_store *cs, const char *name, + const char *value, size_t val_len) +{ + struct settings_nvs *cf = (struct settings_nvs *)cs; + char rdname[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; + u16_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)); + + name_id = cf->last_name_id + 1; + write_name_id = cf->last_name_id + 1; + write_name = true; + + while (1) { + name_id--; + if (name_id == NVS_NAMECNT_ID) { + break; + } + + rc = nvs_read(&cf->cf_nvs, name_id, &rdname, sizeof(rdname)); + + if (rc < 0) { + /* Error or entry not found */ + if (rc == -ENOENT) { + write_name_id = name_id; + } + continue; + } + + rdname[rc] = '\0'; + + if (strcmp(name, rdname)) { + continue; + } + + if ((delete) && (name_id == cf->last_name_id)) { + cf->last_name_id--; + rc = nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, + &cf->last_name_id, sizeof(u16_t)); + } + + if (delete) { + rc = nvs_delete(&cf->cf_nvs, name_id); + rc = nvs_delete(&cf->cf_nvs, name_id + + NVS_NAME_ID_OFFSET); + + return 0; + } + write_name_id = name_id; + write_name = false; + break; + } + + if (delete) { + return -ENOENT; + } + + /* No free IDs left. */ + if (write_name_id == NVS_NAMECNT_ID + NVS_NAME_ID_OFFSET) { + return -ENOMEM; + } + + /* write the value */ + rc = nvs_write(&cf->cf_nvs, write_name_id + NVS_NAME_ID_OFFSET, + value, val_len); + + /* write the name if required */ + if (write_name) { + rc = nvs_write(&cf->cf_nvs, write_name_id, name, strlen(name)); + } + + /* 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 = nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, &cf->last_name_id, + sizeof(u16_t)); + } + + if (rc < 0) { + return rc; + } + + return 0; +} + +/* Initialize the nvs backend. */ +int settings_nvs_backend_init(struct settings_nvs *cf) +{ + int rc; + u16_t last_name_id; + + rc = nvs_init(&cf->cf_nvs, cf->flash_dev_name); + if (rc) { + return rc; + } + + rc = nvs_read(&cf->cf_nvs, NVS_NAMECNT_ID, &last_name_id, + sizeof(last_name_id)); + if (rc < 0) { + cf->last_name_id = NVS_NAMECNT_ID; + } else { + cf->last_name_id = last_name_id; + } + + LOG_DBG("Initialized"); + return 0; +}