settings: adding new nvs backend

Added NVS backend to the Settings subsystem.

Signed-off-by: Kamil Piszczek <Kamil.Piszczek@nordicsemi.no>
diff --git a/subsys/settings/Kconfig b/subsys/settings/Kconfig
index 6978f04..5e98df9 100644
--- a/subsys/settings/Kconfig
+++ b/subsys/settings/Kconfig
@@ -65,6 +65,12 @@
 	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 @@
 	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 0000000..1c2ae2d
--- /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 <nvs/nvs.h>
+#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 ecae3f9..6b7142f 100644
--- a/subsys/settings/src/CMakeLists.txt
+++ b/subsys/settings/src/CMakeLists.txt
@@ -10,3 +10,4 @@
 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 601364d..e1d03c2 100644
--- a/subsys/settings/src/settings_init.c
+++ b/subsys/settings/src/settings_init.c
@@ -117,6 +117,58 @@
 
 	return rc;
 }
+
+#elif defined(CONFIG_SETTINGS_NVS)
+#include <device.h>
+#include <flash_map.h>
+#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 0000000..64dc68f
--- /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 <errno.h>
+#include <string.h>
+
+#include "settings/settings.h"
+#include "settings/settings_nvs.h"
+#include "settings_priv.h"
+
+#include <logging/log.h>
+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;
+}