Repository: zephyrproject-rtos/zephyr
Pull request #87792: Series of optimizations for the ZMS backend for Settings
Author: login: rghaddab
Labels: name: area: File System
name: area: Storage
description: Storage subsystem
name: area: Settings
description: Settings subsystem
Created: 2025-03-28T06:48:51Z | Merged: 2025-05-15T15:53:26Z

These are a list of commits that optimizes reads and writes performance for the ZMS backend for Settings.


Diff:
diff --git a/doc/services/storage/settings/index.rst b/doc/services/storage/settings/index.rst
index 4f8831e3a11ee..d5a244b04fb88 100644
--- a/doc/services/storage/settings/index.rst
+++ b/doc/services/storage/settings/index.rst
@@ -78,6 +78,14 @@ backend.
     This gets called when loading values from persistent storage using
     :c:func:`settings_load()`.
 
+**csi_load_one**
+    This gets called when loading only one item from persistent storage using
+    :c:func:`settings_load_one()`.
+
+**csi_get_val_len**
+    This gets called when getting a value's length from persistent storage using
+    :c:func:`settings_get_val_len()`.
+
 **csi_save**
     This gets called when saving a single setting to persistent storage using
     :c:func:`settings_save_one()`.
@@ -148,6 +156,14 @@ After all data is loaded, the ``h_commit`` handler is issued,
 signalling the application that the settings were successfully
 retrieved.
 
+Alternatively, a call to :c:func:`settings_load_one()` will load only one
+Settings entry and store it in the provided buffer.
+
+Optionally, to get only the value's length associated with the Settings entry,
+a call to :c:func:`settings_get_val_len()` can be performed.
+This is used for example by applications that allocates dynamically the data
+buffer and needs to get the data size before reading it by settings_load_one().
+
 Technically FCB and file backends may store some history of the entities.
 This means that the newest data entity is stored after any
 older existing data entities.
diff --git a/include/zephyr/settings/settings.h b/include/zephyr/settings/settings.h
index f22f1aba1189f..4a99cc0d1ac23 100644
--- a/include/zephyr/settings/settings.h
+++ b/include/zephyr/settings/settings.h
@@ -45,6 +45,9 @@ extern "C" {
  */
 #define SETTINGS_EXTRA_LEN ((SETTINGS_MAX_DIR_DEPTH - 1) + 2)
 
+/* Maximum Settings name length including separators */
+#define SETTINGS_FULL_NAME_LEN ((SETTINGS_MAX_NAME_LEN) + (SETTINGS_EXTRA_LEN) + 1)
+
 /**
  * Function used to read the data from the settings storage in
  * h_set handler implementations.
@@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
+ *             value on failure.
+ */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len);
+
+/**
+ * Get the data length of the value relative to the key
+ *
+ * @param[in] key Name/key of the settings item.
+ * @return length of value if item exists, 0 if not and negative value on failure.
+ */
+ssize_t settings_get_val_len(const char *key);
+
 /**
  * Callback function used for direct loading.
  * Used by @ref settings_load_subtree_direct function.
@@ -447,8 +469,8 @@ struct settings_store_itf {
 	/**< Loads values from storage limited to subtree defined by subtree.
 	 *
 	 * Parameters:
-	 *  - cs - Corresponding backend handler node,
-	 *  - arg - Structure that holds additional data for data loading.
+	 *  - cs[in] - Corresponding backend handler node,
+	 *  - arg[in] - Structure that holds additional data for data loading.
 	 *
 	 * @note
 	 * Backend is expected not to provide duplicates of the entities.
@@ -457,11 +479,31 @@ struct settings_store_itf {
 	 * load callback only on the final entity.
 	 */
 
+	ssize_t (*csi_load_one)(struct settings_store *cs, const char *name,
+				char *buf, size_t buf_len);
+	/**< Loads one value from storage that corresponds to the key defined by name.
+	 *
+	 * Parameters:
+	 *  - cs[in] - Corresponding backend handler node.
+	 *  - name[in] - Key in string format.
+	 *  - buf[in] - Buffer where data should be copied.
+	 *  - buf_len[in] - Length of buf.
+	 */
+
+	ssize_t (*csi_get_val_len)(struct settings_store *cs, const char *name);
+	/**< Gets the value's length associated to the Key defined by name.
+	 * It returns 0 if the Key/Value doesn't exist.
+	 *
+	 * Parameters:
+	 *  - cs[in] - Corresponding backend handler node.
+	 *  - name[in] - Key in string format.
+	 */
+
 	int (*csi_save_start)(struct settings_store *cs);
 	/**< Handler called before an export operation.
 	 *
 	 * Parameters:
-	 *  - cs - Corresponding backend handler node
+	 *  - cs[in] - Corresponding backend handler node
 	 */
 
 	int (*csi_save)(struct settings_store *cs, const char *name,
@@ -469,23 +511,23 @@ struct settings_store_itf {
 	/**< Save a single key-value pair to storage.
 	 *
 	 * Parameters:
-	 *  - cs - Corresponding backend handler node
-	 *  - name - Key in string format
-	 *  - value - Binary value
-	 *  - val_len - Length of value in bytes.
+	 *  - cs[in] - Corresponding backend handler node
+	 *  - name[in] - Key in string format
+	 *  - value[in] - Binary value
+	 *  - val_len[in] - Length of value in bytes.
 	 */
 
 	int (*csi_save_end)(struct settings_store *cs);
 	/**< Handler called after an export operation.
 	 *
 	 * Parameters:
-	 *  - cs - Corresponding backend handler node
+	 *  - cs[in] - Corresponding backend handler node
 	 */
 
 	/**< Get pointer to the storage instance used by the backend.
 	 *
 	 * Parameters:
-	 *  - cs - Corresponding backend handler node
+	 *  - cs[in] - Corresponding backend handler node
 	 */
 	void *(*csi_storage_get)(struct settings_store *cs);
 };
diff --git a/subsys/fs/zms/Kconfig b/subsys/fs/zms/Kconfig
index aed3a398f2d31..f371d75647f1f 100644
--- a/subsys/fs/zms/Kconfig
+++ b/subsys/fs/zms/Kconfig
@@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	bool "ZMS Storage lookup cache optimized for settings"
+	depends on ZMS_LOOKUP_CACHE && SETTINGS_ZMS
+	help
+	  Enable usage of lookup cache based on hashes to get, the best ZMS performance,
+	  provided that the ZMS is used only for the purpose of providing the settings
+	  backend. This option should NOT be enabled if the ZMS is also written to
+	  directly, outside the settings layer.
+
 config ZMS_NO_DOUBLE_WRITE
 	bool "Avoid writing the same data again in the storage"
 	help
diff --git a/subsys/fs/zms/zms.c b/subsys/fs/zms/zms.c
index 95cfc788b0908..80b86a41cdcbd 100644
--- a/subsys/fs/zms/zms.c
+++ b/subsys/fs/zms/zms.c
@@ -12,6 +12,9 @@
 #include <zephyr/fs/zms.h>
 #include <zephyr/sys/crc.h>
 #include "zms_priv.h"
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+#include <settings/settings_zms.h>
+#endif
 
 #include <zephyr/logging/log.h>
 LOG_MODULE_REGISTER(fs_zms, CONFIG_ZMS_LOG_LEVEL);
@@ -28,15 +31,41 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at
 
 static inline size_t zms_lookup_cache_pos(uint32_t id)
 {
-	uint32_t hash;
+	uint32_t hash = id;
+
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	/*
+	 * 1. Settings subsystem is storing the name ID and the linked list node ID
+	 *    with only one bit difference at BIT(0).
+	 * 2. Settings subsystem is also storing the name ID and the data ID in two
+	 *    different ZMS entries at an exact offset of ZMS_DATA_ID_OFFSET.
+	 *
+	 * Therefore, to assure the least number of collisions in the lookup cache,
+	 * the BIT(0) of the hash indicates whether the given ZMS ID represents a
+	 * linked list entry or not, the BIT(1) indicates whether the ZMS ID is a name
+	 * or data and the remaining bits of the hash are set to a truncated part of the
+	 * original hash generated by Settings.
+	 */
+
+	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_DATA_ID_OFFSET is not power of 2");
 
+	uint32_t key_value_bit;
+	uint32_t key_value_hash;
+	uint32_t key_value_ll;
+
+	key_value_bit = (id >> LOG2(ZMS_DATA_ID_OFFSET)) & 1;
+	key_value_hash = (id & ZMS_HASH_MASK) >> (CONFIG_SETTINGS_ZMS_MAX_COLLISIONS_BITS + 1);
+	key_value_ll = id & BIT(0);
+
+	hash = (key_value_hash << 2) | (key_value_bit << 1) | key_value_ll;
+#else
 	/* 32-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
-	hash = id;
 	hash ^= hash >> 16;
 	hash *= 0x7feb352dU;
 	hash ^= hash >> 15;
 	hash *= 0x846ca68bU;
 	hash ^= hash >> 16;
+#endif /* CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS */
 
 	return hash % CONFIG_ZMS_LOOKUP_CACHE_SIZE;
 }
@@ -1433,8 +1462,6 @@ ssize_t zms_write(struct zms_fs *fs, uint32_t id, const void *data, size_t len)
 {
 	int rc;
 	size_t data_size;
-	uint64_t wlk_addr;
-	uint64_t rd_addr;
 	uint32_t gc_count;
 	uint32_t required_space = 0U; /* no space, appropriate for delete ate */
 
@@ -1455,19 +1482,19 @@ ssize_t zms_write(struct zms_fs *fs, uint32_t id, const void *data, size_t len)
 		return -EINVAL;
 	}
 
+#ifdef CONFIG_ZMS_NO_DOUBLE_WRITE
 	/* find latest entry with same id */
 #ifdef CONFIG_ZMS_LOOKUP_CACHE
-	wlk_addr = fs->lookup_cache[zms_lookup_cache_pos(id)];
+	uint64_t wlk_addr = fs->lookup_cache[zms_lookup_cache_pos(id)];
 
 	if (wlk_addr == ZMS_LOOKUP_CACHE_NO_ADDR) {
 		goto no_cached_entry;
 	}
 #else
 	wlk_addr = fs->ate_wra;
-#endif
-	rd_addr = wlk_addr;
+#endif /* CONFIG_ZMS_LOOKUP_CACHE */
+	uint64_t rd_addr = wlk_addr;
 
-#ifdef CONFIG_ZMS_NO_DOUBLE_WRITE
 	/* Search for a previous valid ATE with the same ID */
 	struct zms_ate wlk_ate;
 	int prev_found = zms_find_ate_with_id(fs, id, wlk_addr, fs->ate_wra, &wlk_ate, &rd_addr);
@@ -1511,11 +1538,11 @@ ssize_t zms_write(struct zms_fs *fs, uint32_t id, const void *data, size_t len)
 			return 0;
 		}
 	}
-#endif
-
 #ifdef CONFIG_ZMS_LOOKUP_CACHE
 no_cached_entry:
-#endif
+#endif /* CONFIG_ZMS_LOOKUP_CACHE */
+#endif /* CONFIG_ZMS_NO_DOUBLE_WRITE */
+
 	/* calculate required space if the entry contains data */
 	if (data_size) {
 		/* Leave space for delete ate */
diff --git a/subsys/settings/Kconfig b/subsys/settings/Kconfig
index a59b618759e04..fdb3c4bb3ce20 100644
--- a/subsys/settings/Kconfig
+++ b/subsys/settings/Kconfig
@@ -47,6 +47,25 @@ config SETTINGS_ZMS
 	help
 	  Use ZMS as settings storage backend.
 
+if SETTINGS_ZMS
+
+config SETTINGS_ZMS_LL_CACHE
+	bool "ZMS linked list lookup cache"
+	help
+	  Enable ZMS lookup cache for linked list, used to reduce the
+	  Settings load time by having most linked list elements already
+	  in cache.
+
+config SETTINGS_ZMS_LL_CACHE_SIZE
+	int "ZMS linked list lookup cache size"
+	default 128
+	range 1 $(UINT32_MAX)
+	depends on SETTINGS_ZMS_LL_CACHE
+	help
+	  Number of entries in Settings ZMS linked list cache.
+
+endif # SETTINGS_ZMS
+
 config SETTINGS_FCB
 	bool "FCB"
 	depends on FCB
@@ -171,6 +190,26 @@ config SETTINGS_ZMS_MAX_COLLISIONS_BITS
 	  The maximum number of hash collisions needs to be well sized depending
 	  on the data that is going to be stored in ZMS and its hash values
 
+config SETTINGS_ZMS_NO_LL_DELETE
+	bool "Disable deletion of Linked list hashes"
+	help
+	  For some applications, the Settings delete operation is too long for
+	  ZMS because of the linked list update.
+	  As a tradeoff for performance the linked list is not updated. As a
+	  result, some nodes will be unused and will occupy some space in the
+	  storage.
+	  These nodes will be used again when the same Settings element that has
+	  been deleted is created again.
+
+config SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	bool "Load only subtree path if provided"
+	help
+	  Loads first the key defined by the subtree path.
+	  If the callback handler returns a zero value it will
+	  continue to look for all the keys under that subtree path.
+	  If the callback handler returns a non negative value, it
+	  returns immeditaley.
+
 config SETTINGS_SHELL
 	bool "Settings shell"
 	depends on SHELL
diff --git a/subsys/settings/include/settings/settings_zms.h b/subsys/settings/include/settings/settings_zms.h
index 9b9939815c225..2d318aaf3197e 100644
--- a/subsys/settings/include/settings/settings_zms.h
+++ b/subsys/settings/include/settings/settings_zms.h
@@ -20,7 +20,7 @@ extern "C" {
  * The ZMS entry ID for the setting's value is determined implicitly based on
  * the ID of the ZMS entry for the setting's name, once that is found. The
  * difference between name and value ID is constant and equal to
- * ZMS_NAME_ID_OFFSET.
+ * ZMS_DATA_ID_OFFSET.
  *
  * Setting's name is hashed into 29 bits minus hash_collisions_bits.
  * The 2 MSB_bits have always the same value 10, the LL_bit for the name's hash is 0
@@ -63,24 +63,34 @@ extern "C" {
 
 /* some useful macros */
 #define ZMS_NAME_ID_FROM_LL_NODE(x) (x & ~BIT(0))
+#define ZMS_LL_NODE_FROM_NAME_ID(x) (x | BIT(0))
 #define ZMS_UPDATE_COLLISION_NUM(x, y)                                                             \
 	((x & ~ZMS_COLLISIONS_MASK) | ((y << 1) & ZMS_COLLISIONS_MASK))
-#define ZMS_COLLISION_NUM(x) ((x & ZMS_COLLISIONS_MASK) >> 1)
+#define ZMS_COLLISION_NUM(x)     ((x & ZMS_COLLISIONS_MASK) >> 1)
+#define ZMS_NAME_ID_FROM_HASH(x) ((x & ZMS_HASH_TOTAL_MASK) | BIT(31))
+#define ZMS_DATA_ID_FROM_HASH(x) (ZMS_NAME_ID_FROM_HASH(x) + ZMS_DATA_ID_OFFSET)
+#define ZMS_DATA_ID_FROM_NAME(x) (x + ZMS_DATA_ID_OFFSET)
+#define ZMS_DATA_ID_FROM_LL_NODE(x) (ZMS_NAME_ID_FROM_LL_NODE(x) + ZMS_DATA_ID_OFFSET)
+
+struct settings_hash_linked_list {
+	uint32_t previous_hash;
+	uint32_t next_hash;
+};
 
 struct settings_zms {
 	struct settings_store cf_store;
 	struct zms_fs cf_zms;
 	const struct device *flash_dev;
+#if CONFIG_SETTINGS_ZMS_LL_CACHE
+	struct settings_hash_linked_list ll_cache[CONFIG_SETTINGS_ZMS_LL_CACHE_SIZE];
+	uint32_t ll_cache_next;
+	bool ll_has_changed;
+#endif /* CONFIG_SETTINGS_ZMS_LL_CACHE */
 	uint32_t last_hash_id;
 	uint32_t second_to_last_hash_id;
 	uint8_t hash_collision_num;
 };
 
-struct settings_hash_linked_list {
-	uint32_t previous_hash;
-	uint32_t next_hash;
-};
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/subsys/settings/src/settings_store.c b/subsys/settings/src/settings_store.c
index b697f993d9416..49775a7fc8056 100644
--- a/subsys/settings/src/settings_store.c
+++ b/subsys/settings/src/settings_store.c
@@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name.
+ * Returns the value's length in the provided `param` pointer
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
+	}
+
+	return 0;
+}
+
+/* Gets the value's size if the Key defined by name is in the persistent storage,
+ * Returns 0 if the Key doesn't exist.
+ */
+ssize_t settings_get_val_len(const char *name)
+{
+	struct settings_store *cs;
+	int rc = 0;
+	size_t val_len = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * get the value's length.
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_get_val_len) {
+			val_len = cs->cs_itf->csi_get_val_len(cs, name);
+		} else {
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_get_val_len_default_cb,
+				.param = &val_len
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
+	}
+	k_mutex_unlock(&settings_lock);
+
+	if (rc >= 0) {
+		return val_len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * For every config store that defines csi_load_one() function use it.
+	 * Otherwise, use the csi_load() function to load the key/value pair
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
+	}
+	k_mutex_unlock(&settings_lock);
+
+	if (rc >= 0) {
+		return val_len;
+	}
+	return rc;
+}
+
 /*
  * Append a single value to persisted config. Don't store duplicate value.
  */
diff --git a/subsys/settings/src/settings_zms.c b/subsys/settings/src/settings_zms.c
index 9d588bc588d91..025d943dc7a25 100644
--- a/subsys/settings/src/settings_zms.c
+++ b/subsys/settings/src/settings_zms.c
@@ -3,6 +3,9 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L /* for strnlen() */
+
 #include <errno.h>
 #include <string.h>
 
@@ -27,14 +30,19 @@ struct settings_zms_read_fn_arg {
 };
 
 static int settings_zms_load(struct settings_store *cs, const struct settings_load_arg *arg);
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len);
 static int settings_zms_save(struct settings_store *cs, const char *name, const char *value,
 			     size_t val_len);
 static void *settings_zms_storage_get(struct settings_store *cs);
 static int settings_zms_get_last_hash_ids(struct settings_zms *cf);
+static ssize_t settings_zms_get_val_len(struct settings_store *cs, const char *name);
 
 static struct settings_store_itf settings_zms_itf = {.csi_load = settings_zms_load,
+						     .csi_load_one = settings_zms_load_one,
 						     .csi_save = settings_zms_save,
-						     .csi_storage_get = settings_zms_storage_get};
+						     .csi_storage_get = settings_zms_storage_get,
+						     .csi_get_val_len = settings_zms_get_val_len};
 
 static ssize_t settings_zms_read_fn(void *back_end, void *data, size_t len)
 {
@@ -61,6 +69,7 @@ static int settings_zms_dst(struct settings_zms *cf)
 	return 0;
 }
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
 static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
@@ -68,11 +77,39 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 	struct settings_hash_linked_list settings_update_element;
 
 	/* let's update the linked list */
-	rc = zms_read(&cf->cf_zms, name_hash | 1, &settings_element,
+	rc = zms_read(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
 		      sizeof(struct settings_hash_linked_list));
 	if (rc < 0) {
 		return rc;
 	}
+
+	/* update the previous element */
+	if (settings_element.previous_hash) {
+		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
+			      sizeof(struct settings_hash_linked_list));
+		if (rc < 0) {
+			return rc;
+		}
+		if (!settings_element.next_hash) {
+			/* we are deleting the last element of the linked list,
+			 * let's update the second_to_last_hash_id
+			 */
+			cf->second_to_last_hash_id = settings_update_element.previous_hash;
+		}
+		settings_update_element.next_hash = settings_element.next_hash;
+		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
+			       &settings_update_element, sizeof(struct settings_hash_linked_list));
+		if (rc < 0) {
+			return rc;
+		}
+	}
+
+	/* Now delete the current linked list element */
+	rc = zms_delete(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash));
+	if (rc < 0) {
+		return rc;
+	}
+
 	/* update the next element */
 	if (settings_element.next_hash) {
 		rc = zms_read(&cf->cf_zms, settings_element.next_hash, &settings_update_element,
@@ -96,29 +133,10 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
@@ -126,24 +144,168 @@ static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+	size_t name_len = strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN);
+
+	name_hash = sys_hash32(arg->subtree, name_len) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+	size_t name_len = strnlen(name, SETTINGS_FULL_NAME_LEN);
+
+	name_hash = sys_hash32(name, name_len) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name with current collision number doesn't exist, but there might be
+			 * one with a higher collision number
+			 */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this step names are equal, we found the corresponding hash */
+		return name_hash;
 	}
 
-	return rc;
+	return 0;
+}
+
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	uint32_t name_hash = 0;
+	ssize_t rc = 0;
+	uint32_t value_id;
+
+	/* verify that name is not NULL */
+	if (!name || !buf) {
+		return -EINVAL;
+	}
+
+	name_hash = settings_zms_find_hash_from_name(cf, name);
+	if (name_hash) {
+		/* we found a name_hash corresponding to name */
+		value_id = ZMS_DATA_ID_FROM_HASH(name_hash);
+		rc = zms_read(&cf->cf_zms, value_id, buf, buf_len);
+
+		return (rc == buf_len) ? zms_get_data_length(&cf->cf_zms, value_id) : rc;
+	}
+
+	return 0;
+}
+
+/* Gets the next linked list node either from cache (if enabled) or from persistent
+ * storage if cache is full or cache is not enabled.
+ * It updates as well the next cache index and the next linked list node ID.
+ */
+static int settings_zms_get_next_ll(struct settings_zms *cf, uint32_t *ll_hash_id,
+				    [[maybe_unused]] uint32_t *ll_cache_index)
+{
+	struct settings_hash_linked_list settings_element;
+	int ret = 0;
+
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	if (*ll_cache_index < cf->ll_cache_next) {
+		settings_element = cf->ll_cache[*ll_cache_index];
+		*ll_cache_index = *ll_cache_index + 1;
+	} else if (*ll_hash_id == cf->second_to_last_hash_id) {
+		/* The last ll node is not stored in the cache as it is already
+		 * in the cf->last_hash_id.
+		 */
+		settings_element.next_hash = cf->last_hash_id;
+	} else {
+#endif
+		ret = zms_read(&cf->cf_zms, *ll_hash_id, &settings_element,
+			       sizeof(struct settings_hash_linked_list));
+		if (ret < 0) {
+			return ret;
+		}
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	}
+#endif
+	/* update next ll_hash_id */
+	*ll_hash_id = settings_element.next_hash;
+
+	return 0;
 }
 
 static int settings_zms_load(struct settings_store *cs, const struct settings_load_arg *arg)
@@ -151,21 +313,41 @@ static int settings_zms_load(struct settings_store *cs, const struct settings_lo
 	int ret = 0;
 	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
 	struct settings_zms_read_fn_arg read_fn_arg;
-	struct settings_hash_linked_list settings_element;
-	char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
+	char name[SETTINGS_FULL_NAME_LEN];
 	ssize_t rc1;
 	ssize_t rc2;
 	uint32_t ll_hash_id;
+	uint32_t prev_ll_hash_id;
+	uint32_t ll_cache_index = 0;
 
-	ret = zms_read(&cf->cf_zms, ZMS_LL_HEAD_HASH_ID, &settings_element,
-		       sizeof(struct settings_hash_linked_list));
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	/* If arg->subtree is not null we must first load settings in that subtree */
+	if (arg->subtree != NULL) {
+		ret = settings_zms_load_subtree(cs, arg);
+		if (ret) {
+			return ret;
+		}
+	}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	if (cf->ll_has_changed) {
+		/* reload the linked list in cache */
+		ret = settings_zms_get_last_hash_ids(cf);
+		if (ret < 0) {
+			return ret;
+		}
+	}
+#endif
+
+	/* Load all found Settings */
+	ll_hash_id = ZMS_LL_HEAD_HASH_ID;
+	ret = settings_zms_get_next_ll(cf, &ll_hash_id, &ll_cache_index);
 	if (ret < 0) {
 		return ret;
 	}
-	ll_hash_id = settings_element.next_hash;
 
 	while (ll_hash_id) {
-
 		/* In the ZMS backend, each setting item is stored in two ZMS
 		 * entries one for the setting's name and one with the
 		 * setting's value.
@@ -173,40 +355,43 @@ static int settings_zms_load(struct settings_store *cs, const struct settings_lo
 		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_LL_NODE(ll_hash_id), &name,
 			       sizeof(name) - 1);
 		/* get the length of data and verify that it exists */
-		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_NAME_ID_FROM_LL_NODE(ll_hash_id) +
-							       ZMS_DATA_ID_OFFSET);
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_LL_NODE(ll_hash_id));
+
+		/* updated the next linked list node in case the called handler will
+		 * delete this settings entry.
+		 */
+		prev_ll_hash_id = ll_hash_id;
+		ret = settings_zms_get_next_ll(cf, &ll_hash_id, &ll_cache_index);
+		if (ret < 0) {
+			return ret;
+		}
 
 		if ((rc1 <= 0) || (rc2 <= 0)) {
-			/* Settings item is not stored correctly in the ZMS.
-			 * ZMS entry for its name or value is either missing
-			 * or deleted. Clean dirty entries to make space for
-			 * future settings item.
+			/* In case we are not updating the linked list, this is an empty mode
+			 * Just continue
+			 */
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+			/* Otherwise, Settings item is not stored correctly in the ZMS.
+			 * ZMS entry's name or value is either missing or deleted.
+			 * Clean dirty entries to make space for future settings items.
 			 */
-			ret = settings_zms_delete(cf, ZMS_NAME_ID_FROM_LL_NODE(ll_hash_id));
+			ret = settings_zms_delete(cf, ZMS_NAME_ID_FROM_LL_NODE(prev_ll_hash_id));
 			if (ret < 0) {
 				return ret;
 			}
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 			continue;
 		}
 
 		/* Found a name, this might not include a trailing \0 */
 		name[rc1] = '\0';
 		read_fn_arg.fs = &cf->cf_zms;
-		read_fn_arg.id = ZMS_NAME_ID_FROM_LL_NODE(ll_hash_id) + ZMS_DATA_ID_OFFSET;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_LL_NODE(prev_ll_hash_id);
 
-		ret = settings_call_set_handler(name, rc2, settings_zms_read_fn, &read_fn_arg,
-						(void *)arg);
+		ret = settings_call_set_handler(name, rc2, settings_zms_read_fn, &read_fn_arg, arg);
 		if (ret) {
-			break;
-		}
-
-		/* update next ll_hash_id */
-		ret = zms_read(&cf->cf_zms, ll_hash_id, &settings_element,
-			       sizeof(struct settings_hash_linked_list));
-		if (ret < 0) {
 			return ret;
 		}
-		ll_hash_id = settings_element.next_hash;
 	}
 
 	return ret;
@@ -217,7 +402,7 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 {
 	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
 	struct settings_hash_linked_list settings_element;
-	char rdname[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
+	char rdname[SETTINGS_FULL_NAME_LEN];
 	uint32_t name_hash;
 	uint32_t collision_num = 0;
 	bool delete;
@@ -225,15 +410,19 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 	bool hash_collision;
 	int rc = 0;
 	int first_available_hash_index = -1;
+	size_t name_len;
 
 	if (!name) {
 		return -EINVAL;
 	}
 
+	/* get the name length */
+	name_len = strnlen(name, SETTINGS_FULL_NAME_LEN);
+
 	/* Find out if we are doing a delete */
 	delete = ((value == NULL) || (val_len == 0));
 
-	name_hash = sys_hash32(name, strlen(name)) & ZMS_HASH_MASK;
+	name_hash = sys_hash32(name, name_len) & ZMS_HASH_MASK;
 	/* MSB is always 1 */
 	name_hash |= BIT(31);
 
@@ -257,7 +446,7 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 		 * name
 		 */
 		rdname[rc] = '\0';
-		if (!strcmp(name, rdname)) {
+		if ((rc == name_len) && !memcmp(name, rdname, rc)) {
 			/* Hash exist and the names are equal, we should
 			 * not write the names again.
 			 */
@@ -302,17 +491,25 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 	}
 
 	/* write the value */
-	rc = zms_write(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET, value, val_len);
+	rc = zms_write(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash), value, val_len);
 	if (rc < 0) {
 		return rc;
 	}
 
 	/* write the name if required */
 	if (write_name) {
-		rc = zms_write(&cf->cf_zms, name_hash, name, strlen(name));
-		if (rc < 0) {
+		/* First let's update the linked list */
+#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+		/* verify that the ll_node doesn't exist otherwise do not update it */
+		rc = zms_read(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
+			      sizeof(struct settings_hash_linked_list));
+		if (rc >= 0) {
+			goto no_ll_update;
+		} else if (rc != -ENOENT) {
 			return rc;
 		}
+		/* else the LL node doesn't exist let's update it */
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 		/* write linked list structure element */
 		settings_element.next_hash = 0;
 		/* Verify first that the linked list last element is not broken.
@@ -326,13 +523,14 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 			}
 		}
 		settings_element.previous_hash = cf->last_hash_id;
-		rc = zms_write(&cf->cf_zms, name_hash | 1, &settings_element,
+		rc = zms_write(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
 			       sizeof(struct settings_hash_linked_list));
 		if (rc < 0) {
 			return rc;
 		}
+
 		/* Now update the previous linked list element */
-		settings_element.next_hash = name_hash | 1;
+		settings_element.next_hash = ZMS_LL_NODE_FROM_NAME_ID(name_hash);
 		settings_element.previous_hash = cf->second_to_last_hash_id;
 		rc = zms_write(&cf->cf_zms, cf->last_hash_id, &settings_element,
 			       sizeof(struct settings_hash_linked_list));
@@ -340,7 +538,71 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 			return rc;
 		}
 		cf->second_to_last_hash_id = cf->last_hash_id;
-		cf->last_hash_id = name_hash | 1;
+		cf->last_hash_id = ZMS_LL_NODE_FROM_NAME_ID(name_hash);
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+		if (cf->ll_cache_next < CONFIG_SETTINGS_ZMS_LL_CACHE_SIZE) {
+			cf->ll_cache[cf->ll_cache_next] = settings_element;
+			cf->ll_cache_next = cf->ll_cache_next + 1;
+		}
+#endif
+#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+no_ll_update:
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+		/* Now let's write the name */
+		rc = zms_write(&cf->cf_zms, name_hash, name, name_len);
+		if (rc < 0) {
+			return rc;
+		}
+	}
+	return 0;
+}
+
+static ssize_t settings_zms_get_val_len(struct settings_store *cs, const char *name)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	uint32_t name_hash = 0;
+
+	/* verify that name is not NULL */
+	if (!name) {
+		return -EINVAL;
+	}
+
+	name_hash = settings_zms_find_hash_from_name(cf, name);
+	if (name_hash) {
+		return zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+	}
+
+	return 0;
+}
+
+/* This function inits the linked list head if it doesn't exist or recover it
+ * if the ll_last_hash_id is different than the head hash ID
+ */
+static int settings_zms_init_or_recover_ll(struct settings_zms *cf, uint32_t ll_last_hash_id)
+{
+	struct settings_hash_linked_list settings_element;
+	int rc = 0;
+
+	if (ll_last_hash_id == ZMS_LL_HEAD_HASH_ID) {
+		/* header doesn't exist */
+		settings_element.previous_hash = 0;
+		settings_element.next_hash = 0;
+		rc = zms_write(&cf->cf_zms, ZMS_LL_HEAD_HASH_ID, &settings_element,
+			       sizeof(struct settings_hash_linked_list));
+		if (rc < 0) {
+			return rc;
+		}
+		cf->last_hash_id = ZMS_LL_HEAD_HASH_ID;
+		cf->second_to_last_hash_id = 0;
+	} else {
+		/* let's recover it by keeping all nodes until the last one */
+		settings_element.previous_hash = cf->second_to_last_hash_id;
+		settings_element.next_hash = 0;
+		rc = zms_write(&cf->cf_zms, cf->last_hash_id, &settings_element,
+			       sizeof(struct settings_hash_linked_list));
+		if (rc < 0) {
+			return rc;
+		}
 	}
 
 	return 0;
@@ -350,28 +612,53 @@ static int settings_zms_get_last_hash_ids(struct settings_zms *cf)
 {
 	struct settings_hash_linked_list settings_element;
 	uint32_t ll_last_hash_id = ZMS_LL_HEAD_HASH_ID;
+	uint32_t previous_ll_hash_id = 0;
 	int rc = 0;
 
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_cache_next = 0;
+#endif
 	cf->hash_collision_num = 0;
 	do {
 		rc = zms_read(&cf->cf_zms, ll_last_hash_id, &settings_element,
 			      sizeof(settings_element));
 		if (rc == -ENOENT) {
-			/* header doesn't exist or linked list broken, reinitialize the header */
-			const struct settings_hash_linked_list settings_element = {
-				.previous_hash = 0, .next_hash = 0};
-			rc = zms_write(&cf->cf_zms, ZMS_LL_HEAD_HASH_ID, &settings_element,
-				       sizeof(struct settings_hash_linked_list));
+			/* header doesn't exist or linked list broken, reinitialize the header
+			 * if it doesn't exist and recover it if it is broken
+			 */
+			return settings_zms_init_or_recover_ll(cf, ll_last_hash_id);
+		} else if (rc < 0) {
+			return rc;
+		}
+
+		if (settings_element.previous_hash != previous_ll_hash_id) {
+			/* This is a special case that can happen when a power down occurred
+			 * when deleting a linked list node.
+			 * If the power down occurred after updating the previous linked list node,
+			 * then we would end up with a state where the previous_hash of the linked
+			 * list is broken. Let's recover from this
+			 */
+			rc = zms_delete(&cf->cf_zms, settings_element.previous_hash);
+			if (rc < 0) {
+				return rc;
+			}
+			/* Now recover the linked list */
+			settings_element.previous_hash = previous_ll_hash_id;
+			zms_write(&cf->cf_zms, ll_last_hash_id, &settings_element,
+				  sizeof(struct settings_hash_linked_list));
 			if (rc < 0) {
 				return rc;
 			}
-			cf->last_hash_id = ZMS_LL_HEAD_HASH_ID;
-			cf->second_to_last_hash_id = 0;
-			return 0;
-		} else if (rc < 0) {
-			return rc;
 		}
+		previous_ll_hash_id = ll_last_hash_id;
 
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+		if ((cf->ll_cache_next < CONFIG_SETTINGS_ZMS_LL_CACHE_SIZE) &&
+		    (settings_element.next_hash)) {
+			cf->ll_cache[cf->ll_cache_next] = settings_element;
+			cf->ll_cache_next = cf->ll_cache_next + 1;
+		}
+#endif
 		/* increment hash collision number if necessary */
 		if (ZMS_COLLISION_NUM(ll_last_hash_id) > cf->hash_collision_num) {
 			cf->hash_collision_num = ZMS_COLLISION_NUM(ll_last_hash_id);
@@ -381,6 +668,9 @@ static int settings_zms_get_last_hash_ids(struct settings_zms *cf)
 		ll_last_hash_id = settings_element.next_hash;
 	} while (settings_element.next_hash);
 
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = false;
+#endif
 	return 0;
 }
 
diff --git a/tests/subsys/settings/functional/src/settings_basic_test.c b/tests/subsys/settings/functional/src/settings_basic_test.c
index f77c87db71d73..03ff3eba8a89b 100644
--- a/tests/subsys/settings/functional/src/settings_basic_test.c
+++ b/tests/subsys/settings/functional/src/settings_basic_test.c
@@ -16,7 +16,7 @@
 #include <zephyr/logging/log.h>
 LOG_MODULE_REGISTER(settings_basic_test);
 
-#if defined(CONFIG_SETTINGS_FCB) || defined(CONFIG_SETTINGS_NVS)
+#if defined(CONFIG_SETTINGS_FCB) || defined(CONFIG_SETTINGS_NVS) || defined(CONFIG_SETTINGS_ZMS)
 #include <zephyr/storage/flash_map.h>
 #if DT_HAS_CHOSEN(zephyr_settings_partition)
 #define TEST_FLASH_AREA_ID DT_FIXED_PARTITION_ID(DT_CHOSEN(zephyr_settings_partition))
@@ -240,13 +240,22 @@ ZTEST(settings_functional, test_register_and_loading)
 {
 	int rc, err;
 	uint8_t val = 0;
+	ssize_t val_len = 0;
 
 	rc = settings_subsys_init();
 	zassert_true(rc == 0, "subsys init failed");
 
 
+	/* Check that key that corresponds to val2 do not exist in storage */
+	val_len = settings_get_val_len("ps/ss/ss/val2");
+	zassert_true((val_len == 0), "Failure: key should not exist");
+
 	settings_save_one("ps/ss/ss/val2", &val, sizeof(uint8_t));
 
+	/* Check that the key that corresponds to val2 exists in storage */
+	val_len = settings_get_val_len("ps/ss/ss/val2");
+	zassert_true((val_len == 1), "Failure: key should exist");
+
 	memset(&data, 0, sizeof(struct stored_data));
 
 	rc = settings_register(&val1_settings);
@@ -279,7 +288,16 @@ ZTEST(settings_functional, test_register_and_loading)
 	err = (data.en1) && (data.en2) && (!data.en3);
 	zassert_true(err, "wrong data enable found");
 
+	/* Check that key that corresponds to val3 do not exist in storage */
+	val_len = settings_get_val_len("ps/ss/val3");
+	zassert_true((val_len == 0), "Failure: key should not exist");
+
 	settings_save_one("ps/ss/val3", &val, sizeof(uint8_t));
+
+	/* Check that the key that corresponds to val3 exists in storage */
+	val_len = settings_get_val_len("ps/ss/val3");
+	zassert_true((val_len == 1), "Failure: key should exist");
+
 	memset(&data, 0, sizeof(struct stored_data));
 	/* when we load settings now data.val2 and data.val1 should receive a
 	 * value
@@ -310,7 +328,16 @@ ZTEST(settings_functional, test_register_and_loading)
 	err = (data.en1) && (data.en2) && (data.en3);
 	zassert_true(err, "wrong data enable found");
 
+	/* Check that key that corresponds to val1 do not exist in storage */
+	val_len = settings_get_val_len("ps/val1");
+	zassert_true((val_len == 0), "Failure: key should not exist");
+
 	settings_save_one("ps/val1", &val, sizeof(uint8_t));
+
+	/* Check that the key that corresponds to val1 exists in storage */
+	val_len = settings_get_val_len("ps/val1");
+	zassert_true((val_len == 1), "Failure: key should exist");
+
 	memset(&data, 0, sizeof(struct stored_data));
 	/* when we load settings all data should receive a value loaded */
 	rc = settings_load();
@@ -345,6 +372,17 @@ ZTEST(settings_functional, test_register_and_loading)
 	err = (!data.en1) && (data.en2) && (!data.en3);
 	zassert_true(err, "wrong data enable found");
 
+	memset(&data, 0, sizeof(struct stored_data));
+	/* test load_one: path "ps/ss/ss/val2". Only data.val2 should
+	 * receive a value
+	 */
+	val = 2;
+	settings_save_one("ps/ss/ss/val2", &val, sizeof(uint8_t));
+	rc = settings_load_one("ps/ss/ss/val2", &data.val2, sizeof(uint8_t));
+	zassert_true(rc >= 0, "settings_load_one failed");
+	err = (data.val1 == 0) && (data.val2 == 2) && (data.val3 == 0);
+	zassert_true(err, "wrong data value found %u != 2", data.val2);
+
 	/* clean up by deregistering settings_handler */
 	rc = settings_deregister(&val1_settings);
 	zassert_true(rc, "deregistering val1_settings failed");
diff --git a/tests/subsys/settings/functional/zms/CMakeLists.txt b/tests/subsys/settings/functional/zms/CMakeLists.txt
new file mode 100644
index 0000000000000..e29cd6c417e07
--- /dev/null
+++ b/tests/subsys/settings/functional/zms/CMakeLists.txt
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(functional_zms)
+
+# The code is in the library common to several tests.
+target_sources(app PRIVATE settings_test_zms.c)
+
+add_subdirectory(../src func_test_bindir)
diff --git a/tests/subsys/settings/functional/zms/prj.conf b/tests/subsys/settings/functional/zms/prj.conf
new file mode 100644
index 0000000000000..3de961c7c7211
--- /dev/null
+++ b/tests/subsys/settings/functional/zms/prj.conf
@@ -0,0 +1,8 @@
+CONFIG_ZTEST=y
+CONFIG_FLASH=y
+CONFIG_FLASH_MAP=y
+CONFIG_ZMS=y
+
+CONFIG_SETTINGS=y
+CONFIG_SETTINGS_RUNTIME=y
+CONFIG_SETTINGS_ZMS=y
diff --git a/tests/subsys/settings/functional/zms/settings_test_zms.c b/tests/subsys/settings/functional/zms/settings_test_zms.c
new file mode 100644
index 0000000000000..f73bbdffa8717
--- /dev/null
+++ b/tests/subsys/settings/functional/zms/settings_test_zms.c
@@ -0,0 +1,25 @@
+/* Copyright (c) 2024 BayLibre SAS
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/kernel.h>
+#include <zephyr/ztest.h>
+#include <errno.h>
+#include <zephyr/settings/settings.h>
+#include <zephyr/fs/zms.h>
+
+ZTEST(settings_functional, test_setting_storage_get)
+{
+	int rc;
+	void *storage;
+	uint32_t data = 0xdeadbeefu;
+
+	rc = settings_storage_get(&storage);
+	zassert_equal(0, rc, "Can't fetch storage reference (err=%d)", rc);
+	zassert_not_null(storage, "Null reference.");
+
+	rc = zms_write((struct zms_fs *)storage, 512, &data, sizeof(data));
+	zassert_true(rc >= 0, "Can't write ZMS entry (err=%d).", rc);
+}
+ZTEST_SUITE(settings_functional, NULL, NULL, NULL, NULL, NULL);
diff --git a/tests/subsys/settings/functional/zms/testcase.yaml b/tests/subsys/settings/functional/zms/testcase.yaml
new file mode 100644
index 0000000000000..8b7fcdf576f98
--- /dev/null
+++ b/tests/subsys/settings/functional/zms/testcase.yaml
@@ -0,0 +1,9 @@
+tests:
+  settings.functional.zms:
+    platform_allow:
+      - qemu_x86
+      - native_sim
+      - native_sim/native/64
+    tags:
+      - settings
+      - zms
diff --git a/tests/subsys/settings/performance/settings_test_perf.c b/tests/subsys/settings/performance/settings_test_perf.c
index a5de17e347503..3efe0fcaa52f0 100644
--- a/tests/subsys/settings/performance/settings_test_perf.c
+++ b/tests/subsys/settings/performance/settings_test_perf.c
@@ -19,20 +19,21 @@ static struct k_work_q settings_work_q;
 static K_THREAD_STACK_DEFINE(settings_work_stack, 2024);
 static struct k_work_delayable pending_store;
 
-#define TEST_SETTINGS_COUNT (128)
-#define TEST_STORE_ITR (5)
-#define TEST_TIMEOUT_SEC (60)
+#define TEST_SETTINGS_COUNT      (128)
+#define TEST_STORE_ITR           (5)
+#define TEST_TIMEOUT_SEC         (60)
 #define TEST_SETTINGS_WORKQ_PRIO (1)
 
-static void bt_scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
-		struct net_buf_simple *buf)
+static void bt_scan_cb([[maybe_unused]] const bt_addr_le_t *addr, [[maybe_unused]] int8_t rssi,
+		       [[maybe_unused]] uint8_t adv_type, struct net_buf_simple *buf)
 {
 	printk("len %u\n", buf->len);
 }
 
 struct test_setting {
 	uint32_t val;
-} test_settings[TEST_SETTINGS_COUNT];
+};
+struct test_setting test_settings[TEST_SETTINGS_COUNT];
 
 K_SEM_DEFINE(waitfor_work, 0, 1);
 
@@ -45,14 +46,15 @@ static void store_pending(struct k_work *work)
 		uint32_t total_measured;
 		uint32_t single_entry_max;
 		uint32_t single_entry_min;
-	} stats = {0, 0, 0, UINT32_MAX};
+	};
+	struct test_stats stats = {0, 0, 0, UINT32_MAX};
 
 	int64_t ts1 = k_uptime_get();
 
 	/* benchmark storage performance */
 	for (int j = 0; j < TEST_STORE_ITR; j++) {
 		for (int i = 0; i < TEST_SETTINGS_COUNT; i++) {
-			test_settings[i].val = TEST_SETTINGS_COUNT*j + i;
+			test_settings[i].val = TEST_SETTINGS_COUNT * j + i;
 
 			int64_t ts2 = k_uptime_get();
 
@@ -80,8 +82,7 @@ static void store_pending(struct k_work *work)
 	printk("*** storing of %u entries completed ***\n", ARRAY_SIZE(test_settings));
 	printk("total calculated: %u, total measured: %u\n", stats.total_calculated,
 	       stats.total_measured);
-	printk("entry max: %u, entry min: %u\n", stats.single_entry_max,
-	       stats.single_entry_min);
+	printk("entry max: %u, entry min: %u\n", stats.single_entry_max, stats.single_entry_min);
 
 	k_sem_give(&waitfor_work);
 }
@@ -99,8 +100,8 @@ ZTEST(settings_perf, test_performance)
 	}
 
 	k_work_queue_start(&settings_work_q, settings_work_stack,
-		K_THREAD_STACK_SIZEOF(settings_work_stack),
-		K_PRIO_COOP(TEST_SETTINGS_WORKQ_PRIO), NULL);
+			   K_THREAD_STACK_SIZEOF(settings_work_stack),
+			   K_PRIO_COOP(TEST_SETTINGS_WORKQ_PRIO), NULL);
 	k_thread_name_set(&settings_work_q.thread, "Settings workq");
 	k_work_init_delayable(&pending_store, store_pending);
 


Reviews:
user: login: Damian-Nordic
body: Reviewed first commit so far.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T22:35:53Z
user: login: Damian-Nordic
body: Reviewed a few more commits
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T10:32:23Z
user: login: Damian-Nordic
body: Remaining comments. Thanks for the robustness fixes! Overall, I think new settings API should be introduced as a separate PR to attract wider attention, not interested in ZMS specifically.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T11:54:28Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:54:07Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:55:22Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:58:27Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:59:42Z
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T15:01:36Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T15:03:45Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T15:06:02Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T15:07:01Z
user: login: de-nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T09:36:47Z
user: login: de-nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:25:51Z
user: login: de-nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:29:38Z
user: login: de-nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:31:05Z
user: login: de-nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:37:10Z
user: login: de-nordic
body: Looks ok to me. Left some minor comments.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:37:55Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a series of performance optimizations for the ZMS backend in the Settings subsystem, along with corresponding tests and documentation updates.  
- Refactored callback implementations and added default behaviors in settings_store.c  
- Updated caching and write logic in the ZMS filesystem (including new macros and lookup cache adjustments)  
- Added new functional tests and updated YAML configurations for the ZMS settings backend

### Reviewed Changes

Copilot reviewed 9 out of 14 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| tests/subsys/settings/performance/settings_test_perf.c | Added marking of unused parameters in bt_scan_cb and updated struct test_setting declaration |
| tests/subsys/settings/functional/zms/testcase.yaml | Added YAML testcase configuration for ZMS functional tests |
| tests/subsys/settings/functional/zms/settings_test_zms.c | Introduced new ZMS functional test case |
| tests/subsys/settings/functional/src/settings_basic_test.c | Updated tests to check key existence/absence before and after save operations |
| subsys/settings/src/settings_store.c | Added default callbacks and helper functions for value-length retrieval and single-key loading |
| subsys/settings/include/settings/settings_zms.h | Added new macros and extended structure for linked list cache support |
| subsys/fs/zms/zms.c | Refactored lookup cache logic and variable declarations for write operations |
| include/zephyr/settings/settings.h | Added API documentation and declarations for new settings load/get functions |
</details>


<details>
<summary>Files not reviewed (5)</summary>

* **doc/services/storage/settings/index.rst**: Language not supported
* **subsys/fs/zms/Kconfig**: Language not supported
* **subsys/settings/Kconfig**: Language not supported
* **tests/subsys/settings/functional/zms/CMakeLists.txt**: Language not supported
* **tests/subsys/settings/functional/zms/prj.conf**: Language not supported
</details>



state: COMMENTED
author_association: NONE
submitted_at: 2025-04-26T08:33:46Z
user: login: kartben
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T08:34:41Z
user: login: omkar3141
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:32:22Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:17:59Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:28:15Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:31:18Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:40:23Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:45:09Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:47:48Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:53:55Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:01:06Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:21:30Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:22:25Z
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:30:00Z
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:33:16Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:03:25Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:04:17Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:07:32Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:08:59Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:12:13Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:13:47Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T14:14:21Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:07:00Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:11:58Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:13:27Z
user: login: rghaddab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:19:25Z
user: login: omkar3141
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T10:39:21Z
user: login: omkar3141
body: Responded to existing comments for clarity. It will be nice to incorportate it if possible.

Regarding using _FEAT prefix, it is upto you and Carles to decide ... so that is ok for me, for me as a user - it will be great to be able to clearly distinguish the KCONFIGs that enable certain feature vs KCCONFIGs that tune parameters for the features.

Other than this, it looks good to me.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T10:41:58Z
user: login: carlescufi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:33:35Z
user: login: carlescufi
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T15:12:07Z
user: login: de-nordic
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T15:18:58Z
user: login: Damian-Nordic
body: I'm OK with addressing these comments post-merge.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T16:26:04Z

Review comments:
diff_hunk: @@ -71,6 +80,14 @@ struct settings_zms {
 	struct settings_store cf_store;
 	struct zms_fs cf_zms;
 	const struct device *flash_dev;
+#if CONFIG_SETTINGS_ZMS_NAME_CACHE
+	struct {
+		uint32_t name_hash;
+		uint8_t flags;
path: subsys/settings/include/settings/settings_zms.h
user: login: Damian-Nordic
body: You could save 3B per entry if you split this array of structs into two arrays. Sounds like easy 384B RAM saving.
created_at: 2025-04-06T22:05:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,9 +294,22 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 	/* MSB is always 1 */
 	name_hash |= BIT(31);
 
-	/* Let's find out if there are hash collisions in the storage */
+#ifdef CONFIG_SETTINGS_ZMS_NAME_CACHE
+	uint8_t cache_flags = 0;
+
+	cache_flags = settings_zms_cache_match(cf, name_hash & ZMS_HASH_MASK);
+	if (ZMS_CACHE_EXIST(cache_flags) && !ZMS_CACHE_HAS_COLLISION(cache_flags)) {
path: subsys/settings/src/settings_zms.c
user: login: Damian-Nordic
body: I think this code is wrong. Suppose that you have 2 keys that have the same hash: A (collision number 0) and B (collision number 1). If you call `save_one(A)`, you will add an entry to the name cache with flag HAS_COLLISION cleared. If you then call `save_one(B)`, this `if` will be satisfied and you will write the value for the wrong key.

created_at: 2025-04-06T22:35:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,15 +228,27 @@ static int settings_zms_load(struct settings_store *cs, const struct settings_lo
 							       ZMS_DATA_ID_OFFSET);
 
 		if ((rc1 <= 0) || (rc2 <= 0)) {
-			/* Settings item is not stored correctly in the ZMS.
-			 * ZMS entry for its name or value is either missing
-			 * or deleted. Clean dirty entries to make space for
-			 * future settings item.
+			/* In case we are not updating the linked list, this is an empty mode
+			 * Just continue
+			 */
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+			/* Otherwise, Settings item is not stored correctly in the ZMS.
+			 * ZMS entry for its name or value is either missing or deleted.
+			 * Clean dirty entries to make space for future settings item.
path: subsys/settings/src/settings_zms.c
user: login: Damian-Nordic
body: this was present even before this PR: this cleans dirty entries but does not update the previous and next ll node, am I right?
created_at: 2025-04-07T08:11:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -304,11 +322,18 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 		} else {
 			write_name = false;
 		}
+#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+		/* In this case the settings entry is deleted, which means that
path: subsys/settings/src/settings_zms.c
user: login: Damian-Nordic
body: The comment suggests that you planned to put it under `ZMS_CACHE_IS_DELETED(cache_flags)` condition. Otherwise, you can't tell that the settings entry is deleted or not.
created_at: 2025-04-07T08:14:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -201,6 +201,16 @@ config SETTINGS_ZMS_NO_LL_DELETE
 	  These nodes will be used again when the same Settings element that has
 	  been deleted is created again.
 
+config SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	bool "Load only subtree path if provided"
+	help
+	  Some applications use the settings_load_subtree to load only one
path: subsys/settings/Kconfig
user: login: Damian-Nordic
body: Is this description still up to date? I thought this only returns the subtree root before other keys, not that it returns the subtree root only.
created_at: 2025-04-07T10:12:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: It might make sense to return non-zero value (e.g. 1) here even if `rc == 0` to stop processing remaining keys once the desired one is found.
created_at: 2025-04-07T10:24:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * load config
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: If you find the searched value in one source, you could probably break here, right?
created_at: 2025-04-07T10:27:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,6 +255,46 @@ static int settings_zms_load_subtree(struct settings_store *cs, const struct set
 }
 #endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
 
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	char r_name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
+	ssize_t rc = 0;
+	uint32_t name_hash;
+
+	/* verify that name is not NULL */
+	if (!name || !buf) {
+		return -EINVAL;
+	}
+
+	name_hash = sys_hash32(name, strlen(name)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+
+		/* At this steps the names are equal, let's read the data */
+		return zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash) + ZMS_DATA_ID_OFFSET,
path: subsys/settings/src/settings_zms.c
user: login: Damian-Nordic
body: This doesn't really return the actual stored length as promised in the API description but `MIN(buffer_len, value_len)`, right?
created_at: 2025-04-07T10:30:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,6 +45,9 @@ extern "C" {
  */
 #define SETTINGS_EXTRA_LEN ((SETTINGS_MAX_DIR_DEPTH - 1) + 2)
 
+/* Maximum Settings name length including separators */
+#define SETTINGS_FULL_NAME_LEN SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1
path: include/zephyr/settings/settings.h
user: login: Damian-Nordic
body: it's interesting where this `+ ` comes from as now I can see that `SETTINGS_EXTRA_LEN` already adds a space for the null terminator. :)
created_at: 2025-04-07T11:39:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,8 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
+#define _POSIX_C_SOURCE 200809L /* for strnlen() */
path: subsys/settings/src/settings_zms.c
user: login: Damian-Nordic
body: Add `#undef _POSIX_C_SOURCE` or `#ifndef` before to make sure this compiles if a user sets its own POSIX version.
created_at: 2025-04-07T11:49:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -273,7 +276,7 @@ static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name
 		return -EINVAL;
 	}
 
-	name_hash = sys_hash32(name, strlen(name)) & ZMS_HASH_MASK;
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
path: subsys/settings/src/settings_zms.c
user: login: Damian-Nordic
body: This adds no security given that you use `strcmp` below. Also, it's probably worth checking that `strnlen` returned a value `< SETTINGS_FULL_NAME_LEN` (meaning that the null-terminator was found) to not truncate the user key silently.
created_at: 2025-04-07T11:52:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,15 +228,27 @@ static int settings_zms_load(struct settings_store *cs, const struct settings_lo
 							       ZMS_DATA_ID_OFFSET);
 
 		if ((rc1 <= 0) || (rc2 <= 0)) {
-			/* Settings item is not stored correctly in the ZMS.
-			 * ZMS entry for its name or value is either missing
-			 * or deleted. Clean dirty entries to make space for
-			 * future settings item.
+			/* In case we are not updating the linked list, this is an empty mode
+			 * Just continue
+			 */
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+			/* Otherwise, Settings item is not stored correctly in the ZMS.
+			 * ZMS entry for its name or value is either missing or deleted.
+			 * Clean dirty entries to make space for future settings item.
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: Yes this was cleaning only the dirty entries without their nodes.
Now the settings_zms_delete cleans as well the previous and next node
created_at: 2025-04-07T14:54:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -304,11 +322,18 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 		} else {
 			write_name = false;
 		}
+#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+		/* In this case the settings entry is deleted, which means that
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: will be removed (name_cache doesn't exist anymore)
created_at: 2025-04-07T14:55:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -201,6 +201,16 @@ config SETTINGS_ZMS_NO_LL_DELETE
 	  These nodes will be used again when the same Settings element that has
 	  been deleted is created again.
 
+config SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	bool "Load only subtree path if provided"
+	help
+	  Some applications use the settings_load_subtree to load only one
path: subsys/settings/Kconfig
user: login: rghaddab
body: @Damian-Nordic  I introduced this to load only the subtree root when settings_load_one didn't exist.
Now that settings_load_one exist I will change it so that it will load the subtree root then if needed (return 0 from handler) will continue to load all keys
created_at: 2025-04-07T14:58:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
path: subsys/settings/src/settings_store.c
user: login: rghaddab
body: if rc == 0 it means that we read 0 bytes which means something is wrong with this key.
created_at: 2025-04-07T14:59:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: oh, you're right
created_at: 2025-04-07T15:01:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * load config
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
path: subsys/settings/src/settings_store.c
user: login: rghaddab
body: Good question, I have no idea.
I think the previous behavior didn't break when the value is found in one source.
created_at: 2025-04-07T15:03:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,6 +45,9 @@ extern "C" {
  */
 #define SETTINGS_EXTRA_LEN ((SETTINGS_MAX_DIR_DEPTH - 1) + 2)
 
+/* Maximum Settings name length including separators */
+#define SETTINGS_FULL_NAME_LEN SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1
path: include/zephyr/settings/settings.h
user: login: rghaddab
body: We are also adding a null terminator whether or not the read name for persistent storage has it or not
created_at: 2025-04-07T15:06:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -273,7 +276,7 @@ static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name
 		return -EINVAL;
 	}
 
-	name_hash = sys_hash32(name, strlen(name)) & ZMS_HASH_MASK;
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: well, sonar cloud doesn't think so.
strlen causes a buffer overflow issue
created_at: 2025-04-07T15:07:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * load config
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
path: subsys/settings/src/settings_store.c
user: login: de-nordic
body: I guess it would not break, because you could have the same entry in many source and csi_load callback would decide whether it is with any of them.
created_at: 2025-04-25T09:36:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
path: subsys/settings/src/settings_zms.c
user: login: de-nordic
body: ```suggestion
			/* Name with current collision number doesn't exist, but there might be a on with a higher */
```
created_at: 2025-04-25T10:25:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
path: subsys/settings/src/settings_zms.c
user: login: de-nordic
body: You have already done `strnlen` of `name` in line 223, so you may as well cache it and split this part to length comparisoin + `memcmp`, should be faster as `memcmp` does not have to search for terminating null.
created_at: 2025-04-25T10:29:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
path: subsys/settings/src/settings_zms.c
user: login: de-nordic
body: Similar to https://github.com/zephyrproject-rtos/zephyr/pull/87792/files#r2059986186.
created_at: 2025-04-25T10:31:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -326,21 +516,86 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
 			}
 		}
 		settings_element.previous_hash = cf->last_hash_id;
-		rc = zms_write(&cf->cf_zms, name_hash | 1, &settings_element,
+		rc = zms_write(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
 			       sizeof(struct settings_hash_linked_list));
 		if (rc < 0) {
 			return rc;
 		}
+
 		/* Now update the previous linked list element */
-		settings_element.next_hash = name_hash | 1;
+		settings_element.next_hash = ZMS_LL_NODE_FROM_NAME_ID(name_hash);
 		settings_element.previous_hash = cf->second_to_last_hash_id;
 		rc = zms_write(&cf->cf_zms, cf->last_hash_id, &settings_element,
 			       sizeof(struct settings_hash_linked_list));
 		if (rc < 0) {
 			return rc;
 		}
 		cf->second_to_last_hash_id = cf->last_hash_id;
-		cf->last_hash_id = name_hash | 1;
+		cf->last_hash_id = ZMS_LL_NODE_FROM_NAME_ID(name_hash);
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+		if (cf->ll_cache_next < CONFIG_SETTINGS_ZMS_LL_CACHE_SIZE) {
+			cf->ll_cache[cf->ll_cache_next] = settings_element;
+			cf->ll_cache_next = cf->ll_cache_next + 1;
+		}
+#endif
+#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+no_ll_update:
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+		/* Now let's write the name */
+		rc = zms_write(&cf->cf_zms, name_hash, name, strnlen(name, SETTINGS_FULL_NAME_LEN));
path: subsys/settings/src/settings_zms.c
user: login: de-nordic
body: You can re-use the output of `strnlen` in line 418, and, which is not part of this PR, there is code in line 442 that could re-use the len to shorten the path to comparison result and `strcmp` could be replaced to `memcmp`.
created_at: 2025-04-25T10:37:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,15 +31,41 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at
 
 static inline size_t zms_lookup_cache_pos(uint32_t id)
 {
-	uint32_t hash;
+	uint32_t hash = id;
+
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	/*
+	 * 1. Settings subsystem is storing the name ID and the linked list node ID
+	 *    with only one bit difference at BIT(0).
+	 * 2. Settings subsystem is also storing the name ID and the data ID in two
+	 *    different ZMS entries at an exact offset of ZMS_DATA_ID_OFFSET.
+	 *
+	 * Therefore, to assure the least number of collisions in the lookup cache,
+	 * the BIT(0) of the hash indicates whether the given ZMS ID represents a
+	 * linked list entry or not, the BIT(1) indicates whether the ZMS ID is a name
+	 * or data and the remaining bits of the hash are set to a truncated part of the
+	 * original hash generated by Settings.
+	 */
+
+	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2");
path: subsys/fs/zms/zms.c
user: login: Copilot
body: The BUILD_ASSERT is checking ZMS_DATA_ID_OFFSET, but the error message refers to ZMS_NAME_ID_OFFSET. Please update the error message to correctly reference ZMS_DATA_ID_OFFSET.
```suggestion
	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_DATA_ID_OFFSET is not power of 2");
```
created_at: 2025-04-26T08:33:45Z
author_association: NONE
diff_hunk: @@ -45,6 +45,9 @@ extern "C" {
  */
 #define SETTINGS_EXTRA_LEN ((SETTINGS_MAX_DIR_DEPTH - 1) + 2)
 
+/* Maximum Settings name length including separators */
+#define SETTINGS_FULL_NAME_LEN SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1
path: include/zephyr/settings/settings.h
user: login: Copilot
body: Wrap the macro expression in parentheses to avoid potential precedence issues, for example: "#define SETTINGS_FULL_NAME_LEN ((SETTINGS_MAX_NAME_LEN) + (SETTINGS_EXTRA_LEN) + 1)".
```suggestion
#define SETTINGS_FULL_NAME_LEN ((SETTINGS_MAX_NAME_LEN) + (SETTINGS_EXTRA_LEN) + 1)
```
created_at: 2025-04-26T08:33:45Z
author_association: NONE
diff_hunk: @@ -45,6 +45,9 @@ extern "C" {
  */
 #define SETTINGS_EXTRA_LEN ((SETTINGS_MAX_DIR_DEPTH - 1) + 2)
 
+/* Maximum Settings name length including separators */
+#define SETTINGS_FULL_NAME_LEN SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1
path: include/zephyr/settings/settings.h
user: login: kartben
body: This is a valid comment and needs to be fixed as per coding guidelines
created_at: 2025-04-26T08:34:41Z
author_association: MEMBER
diff_hunk: @@ -148,6 +156,12 @@ After all data is loaded, the ``h_commit`` handler is issued,
 signalling the application that the settings were successfully
 retrieved.
 
+Alternatively, a call to :c:func:`settings_load_one()` will load only one
+Settings entry and store it in the provided buffer.
+
+To get the value's length associated with the Settings entry, a call to
+:c:func:`settings_get_val_len()` should be performed
path: doc/services/storage/settings/index.rst
user: login: omkar3141
body: "To get the value's length associated with the Settings entry, a call to" -> Is this mandatory? Is this optional?

I think it is worth to explain this with a code snippet, as it is impossible to understand what is the relationship between settings_load_one() and settings_get_val_len().
created_at: 2025-04-29T13:48:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
path: include/zephyr/settings/settings.h
user: login: omkar3141
body: Does it return "0" for zero length value? If not, can you document the behavior here for zero length value? 
```suggestion
 * @return     actual size of value that corresponds to name on success, XYZ if value length is zero, negative
```
?
created_at: 2025-04-29T13:51:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,6 +479,26 @@ struct settings_store_itf {
 	 * load callback only on the final entity.
 	 */
 
+	ssize_t (*csi_load_one)(struct settings_store *cs, const char *name,
+				char *buf, size_t buf_len);
+	/**< Loads one value from storage that corresponds to the key defined by name.
+	 *
+	 * Parameters:
+	 *  - cs - Corresponding backend handler node.
path: include/zephyr/settings/settings.h
user: login: omkar3141
body: It will be good to align this with documentation for `struct settings_handler`. I.e. include [in]/[out] details.
created_at: 2025-04-30T12:52:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	bool "ZMS Storage lookup cache optimized for settings"
+	depends on ZMS_LOOKUP_CACHE && SETTINGS_ZMS
+	help
+	  Use the lookup cache hash function that results in the least number of
+	  collissions and, in turn, the best ZMS performance provided that the ZMS
+	  is used as the settings backend only. This option should NOT be enabled
+	  if the ZMS is also written to directly, outside the settings layer.
+
path: subsys/fs/zms/Kconfig
user: login: omkar3141
body: This is bit confusion. I suggested some updated based on my understanding of these changes. Please check if this could be used instead?:

```suggestion
	  Enable usage of lookup cache based on hashes to get, the best ZMS performance,
	  provided that the ZMS is used only for the purpose of providing the settings
	  backend. This option should NOT be enabled if the ZMS is also written to
	  directly, outside the settings layer.
```
created_at: 2025-04-30T12:56:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
path: subsys/fs/zms/Kconfig
user: login: omkar3141
body: Question: Can ZMS be used outside of the settings layer, but on a different partition? So that we have two partitions - 1. Settings storage partition 2. Custom fast data storage partition that does not use settings, but instead uses ZMS directly.
created_at: 2025-04-30T12:57:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,15 +31,41 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at
 
 static inline size_t zms_lookup_cache_pos(uint32_t id)
 {
-	uint32_t hash;
+	uint32_t hash = id;
+
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	/*
+	 * 1. Settings subsystem is storing the name ID and the linked list node ID
+	 *    with only one bit difference at BIT(0).
+	 * 2. Settings subsystem is also storing the name ID and the data ID in two
+	 *    different ZMS entries at an exact offset of ZMS_DATA_ID_OFFSET.
+	 *
+	 * Therefore, to assure the least number of collisions in the lookup cache,
+	 * the BIT(0) of the hash indicates whether the given ZMS ID represents a
+	 * linked list entry or not, the BIT(1) indicates whether the ZMS ID is a name
+	 * or data and the remaining bits of the hash are set to a truncated part of the
+	 * original hash generated by Settings.
+	 */
+
+	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2");
 
+	uint32_t key_value_bit;
+	uint32_t key_value_hash;
+	uint32_t key_value_ll;
+
+	key_value_bit = (id >> LOG2(ZMS_DATA_ID_OFFSET)) & 1;
+	key_value_hash = (id & ZMS_HASH_MASK) >> (CONFIG_SETTINGS_ZMS_MAX_COLLISIONS_BITS + 1);
+	key_value_ll = id & BIT(0);
+
+	hash = (key_value_hash << 2) | (key_value_bit << 1) | key_value_ll;
+#else
 	/* 32-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
-	hash = id;
 	hash ^= hash >> 16;
path: subsys/fs/zms/zms.c
user: login: omkar3141
body: Could you remind me again, here why can't you just use "ID" as lookup, and instead you must hash it and then use hash for lookup into the cache?
created_at: 2025-04-30T13:06:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,24 +63,34 @@ extern "C" {
 
 /* some useful macros */
 #define ZMS_NAME_ID_FROM_LL_NODE(x) (x & ~BIT(0))
+#define ZMS_LL_NODE_FROM_NAME_ID(x) (x | BIT(0))
 #define ZMS_UPDATE_COLLISION_NUM(x, y)                                                             \
 	((x & ~ZMS_COLLISIONS_MASK) | ((y << 1) & ZMS_COLLISIONS_MASK))
-#define ZMS_COLLISION_NUM(x) ((x & ZMS_COLLISIONS_MASK) >> 1)
+#define ZMS_COLLISION_NUM(x)     ((x & ZMS_COLLISIONS_MASK) >> 1)
+#define ZMS_NAME_ID_FROM_HASH(x) ((x & ZMS_HASH_TOTAL_MASK) | BIT(31))
+#define ZMS_DATA_ID_FROM_HASH(x) (ZMS_NAME_ID_FROM_HASH(x) + ZMS_DATA_ID_OFFSET)
+#define ZMS_DATA_ID_FROM_NAME(x) (x + ZMS_DATA_ID_OFFSET)
+#define ZMS_DATA_ID_FROM_LL_NODE(x) (ZMS_NAME_ID_FROM_LL_NODE(x) + ZMS_DATA_ID_OFFSET)
path: subsys/settings/include/settings/settings_zms.h
user: login: omkar3141
body: The macro names appear quite misleading to me. I think it will be easier for maintainance if they reflect what actually they are doing. I think: 

```suggestion
#define ZMS_NAME_ID_HASH_FROM_HASH(x) ((x & ZMS_HASH_TOTAL_MASK) | BIT(31))
#define ZMS_DATA_ID_HASH_FROM_HASH(x) (ZMS_NAME_ID_FROM_HASH(x) + ZMS_DATA_ID_OFFSET)
#define ZMS_DATA_ID_FROM_NAME(x) (x + ZMS_DATA_ID_OFFSET)
#define ZMS_DATA_ID_FROM_LL_NODE(x) (ZMS_NAME_ID_FROM_LL_NODE(x) + ZMS_DATA_ID_OFFSET)
```
created_at: 2025-04-30T13:21:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name. Returns 0 if Key
+ * doesn't exist.
+ */
path: subsys/settings/src/settings_store.c
user: login: omkar3141
body: This says "returns zero **if** key doesn't exist", but the function below always returns zero. What is correct?
created_at: 2025-04-30T13:47:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name. Returns 0 if Key
+ * doesn't exist.
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
+	}
+
+	return 0;
+}
+
+/* Gets the value's size if the Key defined by name is in the persistent storage,
+ * if not found returns 0.
+ */
path: subsys/settings/src/settings_store.c
user: login: omkar3141
body: This does not match with the function implementation. Looks like, function always returns "val_len" if "rc" is zero or positive (and so that includes zero), and negative on error.

Please check.

created_at: 2025-04-30T13:51:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name. Returns 0 if Key
+ * doesn't exist.
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
+	}
+
+	return 0;
+}
+
+/* Gets the value's size if the Key defined by name is in the persistent storage,
+ * if not found returns 0.
+ */
+ssize_t settings_get_val_len(const char *name)
+{
+	struct settings_store *cs;
+	int rc = 0;
+	size_t val_len = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * get the value's length.
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_get_val_len) {
+			val_len = cs->cs_itf->csi_get_val_len(cs, name);
+		} else {
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_get_val_len_default_cb,
+				.param = &val_len
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
+	}
+	k_mutex_unlock(&settings_lock);
+
+	if (rc >= 0) {
+		return val_len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
path: subsys/settings/src/settings_store.c
user: login: omkar3141
body: What does "this function" mean here? Can you clarify this properly?
created_at: 2025-04-30T13:53:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * load config
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
path: subsys/settings/src/settings_store.c
user: login: omkar3141
body: What do you mean by "source" here? Partition?
created_at: 2025-04-30T13:54:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: Suggestion. If we clearly seperate features from configurable settings in KConfig names, 
that would be really helpful for end-developers. 

Ability to load subtree, is a feature, that can be enabled or disabled (and not a 'settings', like number of hash collision bits). So, naming it in that way, makes it easy to understand for users. 

```suggestion
#ifdef CONFIG_SETTINGS_ZMS_FEAT_LOAD_SUBTREE_PATH
```
created_at: 2025-04-30T13:59:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: Do you really want to bother about the name length check if read fails? This can speed up things a bit, in use-cases where application relies on existence of the settings file to do something or not do something, say during boot-up phase.

```suggestion
		if (rc1 <= 0) {
		    continue;
		}
		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
```
created_at: 2025-04-30T14:03:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: This is not exactly an ID right? Since ZMS_DATA_ID_FROM_HASH is simply setting bit 31 and adding offset to HASH. Only when you re-hash this "id", you would get the "actual ID", correct?

Or in this logic you are simply using 'hashes' as data IDs?


So if name hash is:
0x01234567 (let's say corresponding to name ID 0x00000001), then value id becomes:
0x41234567 (which could correspond to 0x40000001, but now, the system stores only hashed ID on the NVM and not the real IDs that you would expect)

Is it correct?
created_at: 2025-04-30T14:08:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this step names are equal, we found the corresponding hash */
+		return name_hash;
 	}
 
-	return rc;
+	return 0;
+}
+
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	uint32_t name_hash = 0;
+	ssize_t rc = 0;
+	uint32_t value_id;
+
+	/* verify that name is not NULL */
+	if (!name || !buf) {
+		return -EINVAL;
+	}
+
+	name_hash = settings_zms_find_hash_from_name(cf, name);
+	if (name_hash) {
+		/* we found a name_hash corresponding to name */
+		value_id = ZMS_DATA_ID_FROM_HASH(name_hash);
+		rc = zms_read(&cf->cf_zms, value_id, buf, buf_len);
+
+		return (rc == buf_len) ? zms_get_data_length(&cf->cf_zms, value_id) : rc;
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: This looks over complication due to API limitation of lower layer. First this does zms_read() (which calls zms_read_hist()), and then it calls zms_get_data_length() (which again calls zms_read_hist()). So, just to access length of the data, two expensive time consuming calls are made to the same function at different hierarchies of the code to get the information which was already available internally in the first call.

Can you please improve this in future upates?


created_at: 2025-04-30T14:25:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this step names are equal, we found the corresponding hash */
+		return name_hash;
 	}
 
-	return rc;
+	return 0;
+}
+
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	uint32_t name_hash = 0;
+	ssize_t rc = 0;
+	uint32_t value_id;
+
+	/* verify that name is not NULL */
+	if (!name || !buf) {
+		return -EINVAL;
+	}
+
+	name_hash = settings_zms_find_hash_from_name(cf, name);
+	if (name_hash) {
+		/* we found a name_hash corresponding to name */
+		value_id = ZMS_DATA_ID_FROM_HASH(name_hash);
+		rc = zms_read(&cf->cf_zms, value_id, buf, buf_len);
+
+		return (rc == buf_len) ? zms_get_data_length(&cf->cf_zms, value_id) : rc;
+	}
+
+	return 0;
+}
+
+/* Gets the next linked list node either from cache (if enabled) or from persistent
+ * storage if cache is full or cache is not enabled.
+ * It updates as well the next cache index and the next linked list node ID.
+ */
+static int settings_zms_get_next_ll(struct settings_zms *cf, uint32_t *ll_hash_id,
+				    [[maybe_unused]] uint32_t *ll_cache_index)
+{
+	struct settings_hash_linked_list settings_element;
+	int ret = 0;
+
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	if (*ll_cache_index < cf->ll_cache_next) {
+		settings_element = cf->ll_cache[*ll_cache_index];
+		*ll_cache_index = *ll_cache_index + 1;
+	} else if (*ll_hash_id == cf->second_to_last_hash_id) {
+		/* The last ll node is not stored in the cache as it is already
+		 * in the cf->last_hash_id.
+		 */
+		settings_element.next_hash = cf->last_hash_id;
+	} else {
+#endif
+		ret = zms_read(&cf->cf_zms, *ll_hash_id, &settings_element,
+			       sizeof(struct settings_hash_linked_list));
+		if (ret < 0) {
+			return ret;
+		}
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	}
+#endif
+	/* update next ll_hash_id */
+	*ll_hash_id = settings_element.next_hash;
+
+	return 0;
 }
 
 static int settings_zms_load(struct settings_store *cs, const struct settings_load_arg *arg)
 {
 	int ret = 0;
 	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
 	struct settings_zms_read_fn_arg read_fn_arg;
-	struct settings_hash_linked_list settings_element;
-	char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
+	char name[SETTINGS_FULL_NAME_LEN];
 	ssize_t rc1;
 	ssize_t rc2;
 	uint32_t ll_hash_id;
+	uint32_t prev_ll_hash_id;
+	uint32_t ll_cache_index = 0;
 
-	ret = zms_read(&cf->cf_zms, ZMS_LL_HEAD_HASH_ID, &settings_element,
-		       sizeof(struct settings_hash_linked_list));
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	/* If arg->subtree is not null we must first load settings in that subtree */
+	if (arg->subtree != NULL) {
+		ret = settings_zms_load_subtree(cs, arg);
+		if (ret) {
+			return ret;
+		}
+	}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	if (cf->ll_has_changed) {
+		/* reload the linked list in cache */
+		ret = settings_zms_get_last_hash_ids(cf);
+		if (ret < 0) {
+			return ret;
+		}
+	}
+#endif
+
+	/* If subtree is NULL then we must load all found Settings */
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: Code is not really checking for subtree not being NULL. Code will end here anyways even if subtree is not NULL since few lines above at Line 324, return is done only if error occurs while loading subtree. 

Is this comment wrong?
created_at: 2025-04-30T14:28:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
path: subsys/fs/zms/Kconfig
user: login: rghaddab
body: Yes, but only  if the IDs used in the Custom fast data storage partition do not collide with those used by Settings
created_at: 2025-05-13T10:17:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	bool "ZMS Storage lookup cache optimized for settings"
+	depends on ZMS_LOOKUP_CACHE && SETTINGS_ZMS
+	help
+	  Use the lookup cache hash function that results in the least number of
+	  collissions and, in turn, the best ZMS performance provided that the ZMS
+	  is used as the settings backend only. This option should NOT be enabled
+	  if the ZMS is also written to directly, outside the settings layer.
+
path: subsys/fs/zms/Kconfig
user: login: rghaddab
body: Well the expression : "usage of lookup cache based on hashes" is not what it is done here.
This is a special function that takes into account how Settings separate between data IDs and name IDs.
I prefer keeping my description.
created_at: 2025-05-13T10:28:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,15 +31,41 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at
 
 static inline size_t zms_lookup_cache_pos(uint32_t id)
 {
-	uint32_t hash;
+	uint32_t hash = id;
+
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	/*
+	 * 1. Settings subsystem is storing the name ID and the linked list node ID
+	 *    with only one bit difference at BIT(0).
+	 * 2. Settings subsystem is also storing the name ID and the data ID in two
+	 *    different ZMS entries at an exact offset of ZMS_DATA_ID_OFFSET.
+	 *
+	 * Therefore, to assure the least number of collisions in the lookup cache,
+	 * the BIT(0) of the hash indicates whether the given ZMS ID represents a
+	 * linked list entry or not, the BIT(1) indicates whether the ZMS ID is a name
+	 * or data and the remaining bits of the hash are set to a truncated part of the
+	 * original hash generated by Settings.
+	 */
+
+	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2");
 
+	uint32_t key_value_bit;
+	uint32_t key_value_hash;
+	uint32_t key_value_ll;
+
+	key_value_bit = (id >> LOG2(ZMS_DATA_ID_OFFSET)) & 1;
+	key_value_hash = (id & ZMS_HASH_MASK) >> (CONFIG_SETTINGS_ZMS_MAX_COLLISIONS_BITS + 1);
+	key_value_ll = id & BIT(0);
+
+	hash = (key_value_hash << 2) | (key_value_bit << 1) | key_value_ll;
+#else
 	/* 32-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
-	hash = id;
 	hash ^= hash >> 16;
path: subsys/fs/zms/zms.c
user: login: rghaddab
body: cache is smaller than the ID namespace.
we need to use a hash function to span the IDs accross all the cache lines equally (almost) to avoid collisions between IDs that share the same lower bits
created_at: 2025-05-13T10:31:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: cache is also a feature but we never add _FEAT prefix
Is it a common usage in Zephyr to add _FEAT prefix for feature configs ?
created_at: 2025-05-13T10:40:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
path: include/zephyr/settings/settings.h
user: login: rghaddab
body: Yes it returns 0 for zero length value
created_at: 2025-05-13T10:45:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,6 +479,26 @@ struct settings_store_itf {
 	 * load callback only on the final entity.
 	 */
 
+	ssize_t (*csi_load_one)(struct settings_store *cs, const char *name,
+				char *buf, size_t buf_len);
+	/**< Loads one value from storage that corresponds to the key defined by name.
+	 *
+	 * Parameters:
+	 *  - cs - Corresponding backend handler node.
path: include/zephyr/settings/settings.h
user: login: rghaddab
body: Yes, but it should be done in a different PR where all the documented functions in this struct should be changed to this format.
We cannot change the format of the documentation only for this function inside the struct
created_at: 2025-05-13T10:47:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * load config
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
path: subsys/settings/src/settings_store.c
user: login: rghaddab
body: > What do you mean by "source" here? Partition?

backend
created_at: 2025-05-13T10:53:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,67 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * load config
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
+			val_len = (rc >= 0) ? rc : 0;
+		} else {
+			struct default_param param = {
+				.buf = buf,
+				.buf_len = buf_len,
+				.val_len = &val_len
+			};
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_set_default_cb,
+				.param = &param
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
path: subsys/settings/src/settings_store.c
user: login: rghaddab
body: > I guess it would not break, because you could have the same entry in many source and csi_load callback would decide whether it is with any of them.

I think csi_load will return 0 whether it finds the entry or not.
So if we return here we will break the behavior.
csi_load will call (settings_load_subtree + settings_commit_subtree) and returns only the settings_commit_subtree return value
created_at: 2025-05-13T11:01:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name. Returns 0 if Key
+ * doesn't exist.
+ */
path: subsys/settings/src/settings_store.c
user: login: rghaddab
body: will be fixed
created_at: 2025-05-13T11:12:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name. Returns 0 if Key
+ * doesn't exist.
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
+	}
+
+	return 0;
+}
+
+/* Gets the value's size if the Key defined by name is in the persistent storage,
+ * if not found returns 0.
+ */
path: subsys/settings/src/settings_store.c
user: login: rghaddab
body: Actually the description is correct: if name is not found in the persistent storage it returns 0.
rc = cs->cs_itf->csi_load(cs, &arg);
csi_load will always return 0 even if the name is not found
created_at: 2025-05-13T11:23:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,6 +156,12 @@ After all data is loaded, the ``h_commit`` handler is issued,
 signalling the application that the settings were successfully
 retrieved.
 
+Alternatively, a call to :c:func:`settings_load_one()` will load only one
+Settings entry and store it in the provided buffer.
+
+To get the value's length associated with the Settings entry, a call to
+:c:func:`settings_get_val_len()` should be performed
path: doc/services/storage/settings/index.rst
user: login: rghaddab
body: There is no relation with both functions.
I will add explanation that this is an optional API
created_at: 2025-05-13T11:34:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: This is not reading the name length.
It is reading the data length.
This is mandatory for robustness in case we find that a zombie data exists without its associated name or the opposite. In this case we delete these entries
created_at: 2025-05-13T11:42:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: This is exactly the Data ID.
What do you mean by the real ID ? Using Settings on top of ZMS is different than using ZMS directly.
Using Settings the IDs are the values that corresponds to the Name and Data. In our case, they are defined by hashes
created_at: 2025-05-13T11:45:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this step names are equal, we found the corresponding hash */
+		return name_hash;
 	}
 
-	return rc;
+	return 0;
+}
+
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	uint32_t name_hash = 0;
+	ssize_t rc = 0;
+	uint32_t value_id;
+
+	/* verify that name is not NULL */
+	if (!name || !buf) {
+		return -EINVAL;
+	}
+
+	name_hash = settings_zms_find_hash_from_name(cf, name);
+	if (name_hash) {
+		/* we found a name_hash corresponding to name */
+		value_id = ZMS_DATA_ID_FROM_HASH(name_hash);
+		rc = zms_read(&cf->cf_zms, value_id, buf, buf_len);
+
+		return (rc == buf_len) ? zms_get_data_length(&cf->cf_zms, value_id) : rc;
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: Let me explain this.
When rc == buf_len, there could be a case where the zms_read function only copied the maximum size of the buffer provided, but in reality the data's size is much bigger.
Only in this case, we read the real data length using zms_get_data_length.
settings_zms_load_one returns the real size of the data without taking into account the provided buffer length
created_at: 2025-05-13T11:56:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
+
+		/* We should return here as there is no need to look for the next
+		 * hash collision
+		 */
+		return settings_call_set_handler(arg->subtree, rc2, settings_zms_read_fn,
+						 &read_fn_arg, arg);
 	}
 
-	/* Now delete the current linked list element */
-	rc = zms_delete(&cf->cf_zms, name_hash | 1);
-	if (rc < 0) {
-		return rc;
+	return 0;
+}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+/* Search for the name_hash that corresponds to name.
+ * If no hash that corresponds to name is found in the persistent storage,
+ * returns 0.
+ */
+static uint32_t settings_zms_find_hash_from_name(struct settings_zms *cf, const char *name)
+{
+	uint32_t name_hash = 0;
+	int rc = 0;
+	char r_name[SETTINGS_FULL_NAME_LEN];
+
+	name_hash = sys_hash32(name, strnlen(name, SETTINGS_FULL_NAME_LEN)) & ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), r_name,
+			      sizeof(r_name) - 1);
+		if (rc <= 0) {
+			/* Name doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		r_name[rc] = '\0';
+		if (strcmp(name, r_name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this step names are equal, we found the corresponding hash */
+		return name_hash;
 	}
 
-	return rc;
+	return 0;
+}
+
+static ssize_t settings_zms_load_one(struct settings_store *cs, const char *name, char *buf,
+				     size_t buf_len)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	uint32_t name_hash = 0;
+	ssize_t rc = 0;
+	uint32_t value_id;
+
+	/* verify that name is not NULL */
+	if (!name || !buf) {
+		return -EINVAL;
+	}
+
+	name_hash = settings_zms_find_hash_from_name(cf, name);
+	if (name_hash) {
+		/* we found a name_hash corresponding to name */
+		value_id = ZMS_DATA_ID_FROM_HASH(name_hash);
+		rc = zms_read(&cf->cf_zms, value_id, buf, buf_len);
+
+		return (rc == buf_len) ? zms_get_data_length(&cf->cf_zms, value_id) : rc;
+	}
+
+	return 0;
+}
+
+/* Gets the next linked list node either from cache (if enabled) or from persistent
+ * storage if cache is full or cache is not enabled.
+ * It updates as well the next cache index and the next linked list node ID.
+ */
+static int settings_zms_get_next_ll(struct settings_zms *cf, uint32_t *ll_hash_id,
+				    [[maybe_unused]] uint32_t *ll_cache_index)
+{
+	struct settings_hash_linked_list settings_element;
+	int ret = 0;
+
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	if (*ll_cache_index < cf->ll_cache_next) {
+		settings_element = cf->ll_cache[*ll_cache_index];
+		*ll_cache_index = *ll_cache_index + 1;
+	} else if (*ll_hash_id == cf->second_to_last_hash_id) {
+		/* The last ll node is not stored in the cache as it is already
+		 * in the cf->last_hash_id.
+		 */
+		settings_element.next_hash = cf->last_hash_id;
+	} else {
+#endif
+		ret = zms_read(&cf->cf_zms, *ll_hash_id, &settings_element,
+			       sizeof(struct settings_hash_linked_list));
+		if (ret < 0) {
+			return ret;
+		}
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	}
+#endif
+	/* update next ll_hash_id */
+	*ll_hash_id = settings_element.next_hash;
+
+	return 0;
 }
 
 static int settings_zms_load(struct settings_store *cs, const struct settings_load_arg *arg)
 {
 	int ret = 0;
 	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
 	struct settings_zms_read_fn_arg read_fn_arg;
-	struct settings_hash_linked_list settings_element;
-	char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
+	char name[SETTINGS_FULL_NAME_LEN];
 	ssize_t rc1;
 	ssize_t rc2;
 	uint32_t ll_hash_id;
+	uint32_t prev_ll_hash_id;
+	uint32_t ll_cache_index = 0;
 
-	ret = zms_read(&cf->cf_zms, ZMS_LL_HEAD_HASH_ID, &settings_element,
-		       sizeof(struct settings_hash_linked_list));
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	/* If arg->subtree is not null we must first load settings in that subtree */
+	if (arg->subtree != NULL) {
+		ret = settings_zms_load_subtree(cs, arg);
+		if (ret) {
+			return ret;
+		}
+	}
+#endif /* CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH */
+
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	if (cf->ll_has_changed) {
+		/* reload the linked list in cache */
+		ret = settings_zms_get_last_hash_ids(cf);
+		if (ret < 0) {
+			return ret;
+		}
+	}
+#endif
+
+	/* If subtree is NULL then we must load all found Settings */
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: I will change the comment.
created_at: 2025-05-13T12:05:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,24 +63,34 @@ extern "C" {
 
 /* some useful macros */
 #define ZMS_NAME_ID_FROM_LL_NODE(x) (x & ~BIT(0))
+#define ZMS_LL_NODE_FROM_NAME_ID(x) (x | BIT(0))
 #define ZMS_UPDATE_COLLISION_NUM(x, y)                                                             \
 	((x & ~ZMS_COLLISIONS_MASK) | ((y << 1) & ZMS_COLLISIONS_MASK))
-#define ZMS_COLLISION_NUM(x) ((x & ZMS_COLLISIONS_MASK) >> 1)
+#define ZMS_COLLISION_NUM(x)     ((x & ZMS_COLLISIONS_MASK) >> 1)
+#define ZMS_NAME_ID_FROM_HASH(x) ((x & ZMS_HASH_TOTAL_MASK) | BIT(31))
+#define ZMS_DATA_ID_FROM_HASH(x) (ZMS_NAME_ID_FROM_HASH(x) + ZMS_DATA_ID_OFFSET)
+#define ZMS_DATA_ID_FROM_NAME(x) (x + ZMS_DATA_ID_OFFSET)
+#define ZMS_DATA_ID_FROM_LL_NODE(x) (ZMS_NAME_ID_FROM_LL_NODE(x) + ZMS_DATA_ID_OFFSET)
path: subsys/settings/include/settings/settings_zms.h
user: login: rghaddab
body: These are the NAME_ID and DATA_ID not the NAME_ID_HASH and DATA_ID_HASH
created_at: 2025-05-13T12:22:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
path: include/zephyr/settings/settings.h
user: login: Damian-Nordic
body: actually, values in settings cannot have zero length (writing zero length value is treated as "delete").
created_at: 2025-05-13T12:30:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
path: subsys/fs/zms/Kconfig
user: login: Damian-Nordic
body: I think @omkar3141 is not asking whether you can use a single ZMS partition for both settings and directly - the question is whether you can have two ZMS partitions: one for settings and another one used directly. And I think the answer is 'yes' :)
created_at: 2025-05-13T12:33:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
+/* Loads first the key which is defined by the name found in "subtree" root.
+ * If the key is not found or further keys under the same subtree are needed
+ * by the caller, returns 0.
+ */
+static int settings_zms_load_subtree(struct settings_store *cs, const struct settings_load_arg *arg)
+{
+	struct settings_zms *cf = CONTAINER_OF(cs, struct settings_zms, cf_store);
+	struct settings_zms_read_fn_arg read_fn_arg;
+	char name[SETTINGS_FULL_NAME_LEN];
+	ssize_t rc1;
+	ssize_t rc2;
+	uint32_t name_hash;
+
+	name_hash = sys_hash32(arg->subtree, strnlen(arg->subtree, SETTINGS_FULL_NAME_LEN)) &
+		    ZMS_HASH_MASK;
+	for (int i = 0; i <= cf->hash_collision_num; i++) {
+		name_hash = ZMS_UPDATE_COLLISION_NUM(name_hash, i);
+		/* Get the name entry from ZMS */
+		rc1 = zms_read(&cf->cf_zms, ZMS_NAME_ID_FROM_HASH(name_hash), &name,
+			       sizeof(name) - 1);
+		/* get the length of data and verify that it exists */
+		rc2 = zms_get_data_length(&cf->cf_zms, ZMS_DATA_ID_FROM_HASH(name_hash));
+		if ((rc1 <= 0) || (rc2 <= 0)) {
+			/* Name or data doesn't exist */
+			continue;
+		}
+		/* Found a name, this might not include a trailing \0 */
+		name[rc1] = '\0';
+		if (strcmp(arg->subtree, name)) {
+			/* Names are not equal let's continue to the next collision hash
+			 * if it exists.
+			 */
+			continue;
+		}
+		/* At this steps the names are equal, let's set the handler */
+		read_fn_arg.fs = &cf->cf_zms;
+		read_fn_arg.id = ZMS_DATA_ID_FROM_HASH(name_hash);
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: Never mind. I think the confusion comes because names are written using name IDs.
created_at: 2025-05-13T14:03:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
path: subsys/settings/src/settings_zms.c
user: login: omkar3141
body: I'm not sure about that. Cache should have had the _FEAT prefix. Of course there is no harm to improve this. But it is upto you and Carles.
created_at: 2025-05-13T14:04:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,15 +31,41 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at
 
 static inline size_t zms_lookup_cache_pos(uint32_t id)
 {
-	uint32_t hash;
+	uint32_t hash = id;
+
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	/*
+	 * 1. Settings subsystem is storing the name ID and the linked list node ID
+	 *    with only one bit difference at BIT(0).
+	 * 2. Settings subsystem is also storing the name ID and the data ID in two
+	 *    different ZMS entries at an exact offset of ZMS_DATA_ID_OFFSET.
+	 *
+	 * Therefore, to assure the least number of collisions in the lookup cache,
+	 * the BIT(0) of the hash indicates whether the given ZMS ID represents a
+	 * linked list entry or not, the BIT(1) indicates whether the ZMS ID is a name
+	 * or data and the remaining bits of the hash are set to a truncated part of the
+	 * original hash generated by Settings.
+	 */
+
+	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2");
 
+	uint32_t key_value_bit;
+	uint32_t key_value_hash;
+	uint32_t key_value_ll;
+
+	key_value_bit = (id >> LOG2(ZMS_DATA_ID_OFFSET)) & 1;
+	key_value_hash = (id & ZMS_HASH_MASK) >> (CONFIG_SETTINGS_ZMS_MAX_COLLISIONS_BITS + 1);
+	key_value_ll = id & BIT(0);
+
+	hash = (key_value_hash << 2) | (key_value_bit << 1) | key_value_ll;
+#else
 	/* 32-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
-	hash = id;
 	hash ^= hash >> 16;
path: subsys/fs/zms/zms.c
user: login: omkar3141
body: But such collisions will be unavoidable right? 
created_at: 2025-05-13T14:07:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
path: subsys/fs/zms/Kconfig
user: login: omkar3141
body: Yes, I meant one should be able to create and use multiple instances of ZMS, each on different partition.
created_at: 2025-05-13T14:08:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	bool "ZMS Storage lookup cache optimized for settings"
+	depends on ZMS_LOOKUP_CACHE && SETTINGS_ZMS
+	help
+	  Use the lookup cache hash function that results in the least number of
+	  collissions and, in turn, the best ZMS performance provided that the ZMS
+	  is used as the settings backend only. This option should NOT be enabled
+	  if the ZMS is also written to directly, outside the settings layer.
+
path: subsys/fs/zms/Kconfig
user: login: omkar3141
body: Can you use - "Settings SMZ Backend + ZMS Cache + ZMS" on partition A, and directly use "ZMS" on partition B? I guess the answer is yes. So, this comment applies to only that partition, which is used for storing settings using ZMS.
created_at: 2025-05-13T14:12:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,6 +479,26 @@ struct settings_store_itf {
 	 * load callback only on the final entity.
 	 */
 
+	ssize_t (*csi_load_one)(struct settings_store *cs, const char *name,
+				char *buf, size_t buf_len);
+	/**< Loads one value from storage that corresponds to the key defined by name.
+	 *
+	 * Parameters:
+	 *  - cs - Corresponding backend handler node.
path: include/zephyr/settings/settings.h
user: login: omkar3141
body: This "settings.h" has different style for different structures. The comment was made to help align this one with other things.
created_at: 2025-05-13T14:13:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
path: include/zephyr/settings/settings.h
user: login: omkar3141
body: Then we need to specify this clearly in the docs.
created_at: 2025-05-13T14:14:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
path: subsys/fs/zms/Kconfig
user: login: rghaddab
body: @omkar3141 I didn't pay attention that you meant on "different partition".
In that case Yes it is possible
created_at: 2025-05-13T15:07:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,15 +31,41 @@ static int zms_ate_valid_different_sector(struct zms_fs *fs, const struct zms_at
 
 static inline size_t zms_lookup_cache_pos(uint32_t id)
 {
-	uint32_t hash;
+	uint32_t hash = id;
+
+#ifdef CONFIG_ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	/*
+	 * 1. Settings subsystem is storing the name ID and the linked list node ID
+	 *    with only one bit difference at BIT(0).
+	 * 2. Settings subsystem is also storing the name ID and the data ID in two
+	 *    different ZMS entries at an exact offset of ZMS_DATA_ID_OFFSET.
+	 *
+	 * Therefore, to assure the least number of collisions in the lookup cache,
+	 * the BIT(0) of the hash indicates whether the given ZMS ID represents a
+	 * linked list entry or not, the BIT(1) indicates whether the ZMS ID is a name
+	 * or data and the remaining bits of the hash are set to a truncated part of the
+	 * original hash generated by Settings.
+	 */
+
+	BUILD_ASSERT(IS_POWER_OF_TWO(ZMS_DATA_ID_OFFSET), "ZMS_NAME_ID_OFFSET is not power of 2");
 
+	uint32_t key_value_bit;
+	uint32_t key_value_hash;
+	uint32_t key_value_ll;
+
+	key_value_bit = (id >> LOG2(ZMS_DATA_ID_OFFSET)) & 1;
+	key_value_hash = (id & ZMS_HASH_MASK) >> (CONFIG_SETTINGS_ZMS_MAX_COLLISIONS_BITS + 1);
+	key_value_ll = id & BIT(0);
+
+	hash = (key_value_hash << 2) | (key_value_bit << 1) | key_value_ll;
+#else
 	/* 32-bit integer hash function found by https://github.com/skeeto/hash-prospector. */
-	hash = id;
 	hash ^= hash >> 16;
path: subsys/fs/zms/zms.c
user: login: rghaddab
body: @omkar3141 yes they are unavoidable, but using a hash function will make them occur less (in general)
I think you are referring to some application usage where the ID is incremented starting from 0x1, right ?
In that case, ID is also an option for the cache, but ZMS is agnostic to the application usage of IDs and they could be totally different.
Using a hash function guarantees that the maximum number of cache lines are used.
created_at: 2025-05-13T15:11:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
path: subsys/settings/src/settings_zms.c
user: login: rghaddab
body: @carlescufi Is it a common usage for the CONFIGs to add a _FEAT suffix if it is a feature ?
created_at: 2025-05-13T15:13:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
path: include/zephyr/settings/settings.h
user: login: rghaddab
body: @omkar3141 Are you referring to the settings elements that have a "name" written in the storage but a value that is empty ? ("")
If this is the case, such elements do not exist in the settings subsystem.
What do you want to document in this case ? The function returns the actual size of the Settings value
created_at: 2025-05-13T15:19:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -278,6 +281,25 @@ int settings_load(void);
  */
 int settings_load_subtree(const char *subtree);
 
+/**
+ * Load one serialized item from registered persistence sources.
+ *
+ * @param[in]  name    Name/key of the settings item.
+ * @param[out] buf     Pointer to the buffer where the data is going to be loaded
+ * @param[in]  buf_len Length of the allocated buffer.
+ * @return     actual size of value that corresponds to name on success, negative
path: include/zephyr/settings/settings.h
user: login: omkar3141
body: Something like this:

```suggestion
 * @return     actual size of value that corresponds to name on success, negative value on failure. By design of settings subsystems, values with zero length will not be written to the storage and hence this function will never return zero.
```

Reason: Adding this, avoids look up into actual code, and also prevent unnecessary checks `if (ret== 0) { do something }`, in the upper layer.
created_at: 2025-05-14T10:39:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,117 +133,262 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
 		 */
 		cf->last_hash_id = settings_element.previous_hash;
 	}
-	/* update the previous element */
-	if (settings_element.previous_hash) {
-		rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
-			      sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-		if (!settings_element.next_hash) {
-			/* we are deleting the last element of the linked list,
-			 * let's update the second_to_last_hash_id
-			 */
-			cf->second_to_last_hash_id = settings_update_element.previous_hash;
-		}
-		settings_update_element.next_hash = settings_element.next_hash;
-		rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
-			       &settings_update_element, sizeof(struct settings_hash_linked_list));
-		if (rc < 0) {
-			return rc;
-		}
-	}
 
-	return rc;
+	return 0;
 }
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
 
 static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
 {
 	int rc = 0;
 
 	rc = zms_delete(&cf->cf_zms, name_hash);
 	if (rc >= 0) {
-		rc = zms_delete(&cf->cf_zms, name_hash + ZMS_DATA_ID_OFFSET);
+		rc = zms_delete(&cf->cf_zms, ZMS_DATA_ID_FROM_NAME(name_hash));
 	}
 	if (rc < 0) {
 		return rc;
 	}
 
+#ifndef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
+#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
+	cf->ll_has_changed = true;
+#endif
 	rc = settings_zms_unlink_ll_node(cf, name_hash);
-	if (rc < 0) {
-		return rc;
+#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
+
+	return rc;
+}
+
+#ifdef CONFIG_SETTINGS_ZMS_LOAD_SUBTREE_PATH
path: subsys/settings/src/settings_zms.c
user: login: carlescufi
body: No, not really. We are actually working on naming conventions for Kconfig now, but I would recommend not adding `_FEAT_` as an infix here for consistency with other areas.
created_at: 2025-05-14T14:33:35Z
author_association: MEMBER
diff_hunk: @@ -148,6 +156,14 @@ After all data is loaded, the ``h_commit`` handler is issued,
 signalling the application that the settings were successfully
 retrieved.
 
+Alternatively, a call to :c:func:`settings_load_one()` will load only one
+Settings entry and store it in the provided buffer.
+
+Optionally, to get only the value's length associated with the Settings entry,
+a call to :c:func:`settings_get_val_len()` can be performed.
+This is used for example by applications that allocates dynamically the data
path: doc/services/storage/settings/index.rst
user: login: Damian-Nordic
body: ```suggestion
This is used for example by an application that allocates dynamically the data
```
created_at: 2025-05-14T15:53:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,6 +50,15 @@ config ZMS_CUSTOM_BLOCK_SIZE
 	help
 	  Changes the internal buffer size of ZMS
 
+config ZMS_LOOKUP_CACHE_FOR_SETTINGS
+	bool "ZMS Storage lookup cache optimized for settings"
+	depends on ZMS_LOOKUP_CACHE && SETTINGS_ZMS
+	help
+	  Use the lookup cache hash function that results in the least number of
+	  collissions and, in turn, the best ZMS performance provided that the ZMS
+	  is used as the settings backend only. This option should NOT be enabled
+	  if the ZMS is also written to directly, outside the settings layer.
+
path: subsys/fs/zms/Kconfig
user: login: Damian-Nordic
body: To be honest, I don't understand what "Enable usage of lookup cache based on hashes to get" means. This config replaces the hash function so the previous description what much more accurate.
created_at: 2025-05-14T15:58:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -171,6 +190,26 @@ config SETTINGS_ZMS_MAX_COLLISIONS_BITS
 	  The maximum number of hash collisions needs to be well sized depending
 	  on the data that is going to be stored in ZMS and its hash values
 
+config SETTINGS_ZMS_NO_LL_DELETE
+	bool "Disable deletion of Linked list hashes"
+	help
+	  For some applications, the Settings delete operation is too long for
+	  ZMS because of the linked list update.
+	  As a tradeoff for performance the linked list is not updated. As a
+	  result, some nodes will be unused and will occupy some space in the
+	  storage.
+	  These nodes will be used again when the same Settings element that has
+	  been deleted is created again.
+
+config SETTINGS_ZMS_LOAD_SUBTREE_PATH
+	bool "Load only subtree path if provided"
path: subsys/settings/Kconfig
user: login: Damian-Nordic
body: Based on the description below, "only" is incorrect. This config is supposed to be named "Load subtree root before descendants" or equivalently.
created_at: 2025-05-14T16:03:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: nit: `default_param` is pretty vague name :)
created_at: 2025-05-14T16:06:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name.
+ * Returns the value's length in the provided `param` pointer
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: `return 1` to stop processing further keys
created_at: 2025-05-14T16:12:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name.
+ * Returns the value's length in the provided `param` pointer
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
+	}
+
+	return 0;
+}
+
+/* Gets the value's size if the Key defined by name is in the persistent storage,
+ * Returns 0 if the Key doesn't exist.
+ */
+ssize_t settings_get_val_len(const char *name)
+{
+	struct settings_store *cs;
+	int rc = 0;
+	size_t val_len = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * get the value's length.
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_get_val_len) {
+			val_len = cs->cs_itf->csi_get_val_len(cs, name);
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: In case there are indeed multiple stores, and the key was found in the previous store, this may override `val_len`.
created_at: 2025-05-14T16:17:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -88,6 +88,122 @@ int settings_load_subtree_direct(
 	return 0;
 }
 
+struct default_param {
+	void *buf;
+	size_t buf_len;
+	size_t *val_len;
+};
+
+/* Default callback to set a Key/Value pair */
+static int settings_set_default_cb(const char *name, size_t len, settings_read_cb read_cb,
+				   void *cb_arg, void *param)
+{
+	int rc = 0;
+	const char *next;
+	size_t name_len;
+	struct default_param *dest = (struct default_param *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		rc = read_cb(cb_arg, dest->buf, MIN(dest->buf_len, len));
+		*dest->val_len = len;
+	}
+
+	return rc;
+}
+
+/* Default callback to get the value's length of the Key defined by name.
+ * Returns the value's length in the provided `param` pointer
+ */
+static int settings_get_val_len_default_cb(const char *name, size_t len,
+					   [[maybe_unused]] settings_read_cb read_cb,
+					   [[maybe_unused]] void *cb_arg, void *param)
+{
+	const char *next;
+	size_t name_len;
+	size_t *val_len = (size_t *)param;
+
+	name_len = settings_name_next(name, &next);
+	if (name_len == 0) {
+		*val_len = len;
+	}
+
+	return 0;
+}
+
+/* Gets the value's size if the Key defined by name is in the persistent storage,
+ * Returns 0 if the Key doesn't exist.
+ */
+ssize_t settings_get_val_len(const char *name)
+{
+	struct settings_store *cs;
+	int rc = 0;
+	size_t val_len = 0;
+
+	/*
+	 * for every config store that supports this function
+	 * get the value's length.
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_get_val_len) {
+			val_len = cs->cs_itf->csi_get_val_len(cs, name);
+		} else {
+			const struct settings_load_arg arg = {
+				.subtree = name,
+				.cb = &settings_get_val_len_default_cb,
+				.param = &val_len
+			};
+			rc = cs->cs_itf->csi_load(cs, &arg);
+		}
+	}
+	k_mutex_unlock(&settings_lock);
+
+	if (rc >= 0) {
+		return val_len;
+	}
+
+	return rc;
+}
+
+/* Load a single key/value from persistent storage */
+ssize_t settings_load_one(const char *name, void *buf, size_t buf_len)
+{
+	struct settings_store *cs;
+	size_t val_len = 0;
+	int rc = 0;
+
+	/*
+	 * For every config store that defines csi_load_one() function use it.
+	 * Otherwise, use the csi_load() function to load the key/value pair
+	 */
+	k_mutex_lock(&settings_lock, K_FOREVER);
+	SYS_SLIST_FOR_EACH_CONTAINER(&settings_load_srcs, cs, cs_next) {
+		if (cs->cs_itf->csi_load_one) {
+			rc = cs->cs_itf->csi_load_one(cs, name, (char *)buf, buf_len);
path: subsys/settings/src/settings_store.c
user: login: Damian-Nordic
body: This may override the result from the previous store where `name` was successfully found
created_at: 2025-05-14T16:21:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-13T22:56:13Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=zephyrproject-rtos_zephyr&pullRequest=87792) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&pullRequest=87792&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&pullRequest=87792&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=zephyrproject-rtos_zephyr&pullRequest=87792&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=zephyrproject-rtos_zephyr&pullRequest=87792&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=zephyrproject-rtos_zephyr&pullRequest=87792&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=zephyrproject-rtos_zephyr&pullRequest=87792)


author_association: NONE
user: login: carlescufi
created_at: 2025-05-14T15:20:42Z
body: DNM for an internal test before merging
author_association: MEMBER
user: login: carlescufi
created_at: 2025-05-15T14:38:06Z
body: > I'm OK with addressing these comments post-merge.

Yes, let's do that @rghaddab. This PR has been open long enough.
author_association: MEMBER
user: login: bryceschober
created_at: 2025-05-24T20:06:29Z
body: @rghaddab @kartben Out of curiosity, were there any before/after metrics for these optimizations?
author_association: NONE
user: login: rghaddab
created_at: 2025-05-26T08:58:17Z
body: > @rghaddab @kartben Out of curiosity, were there any before/after metrics for these optimizations?

@bryceschober Unfortunately we didn't have a "previous" measures for the ZMS Settings backend as this is still a new backend.
But I tested this using different scenarios and different configurations.
Depending on the usage, I can see a huge performance improvement ( up to 10 times).
author_association: CONTRIBUTOR
user: login: bryceschober
created_at: 2025-05-27T19:27:57Z
body: > Depending on the usage, I can see a huge performance improvement ( up to 10 times).

@rghaddab I didn't notice any mention of which kind of performance was actually improved, and how it might affect the comparisons to other backends.
author_association: NONE
user: login: rghaddab
created_at: 2025-06-05T11:48:31Z
body: > @rghaddab I didn't notice any mention of which kind of performance was actually improved, and how it might affect the comparisons to other backends.

@bryceschober mainly read/write performance.
And the improvement is compared to the first ZMS backend that was merged
author_association: CONTRIBUTOR