1 /*
2  * Copyright (c) 2019 Laczen
3  * Copyright (c) 2019 Nordic Semiconductor ASA
4  * Copyright (c) 2025 Analog Devices, Inc.
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #include <psa/internal_trusted_storage.h>
10 #include <zephyr/settings/settings.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/logging/log.h>
13 #include <zephyr/psa/its_ids.h>
14 
15 LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL);
16 
17 K_MUTEX_DEFINE(worker_mutex);
18 static struct k_work_delayable worker;
19 
20 struct setting_entry {
21 	char name[SETTINGS_MAX_NAME_LEN];
22 	char value[SETTINGS_MAX_VAL_LEN];
23 	size_t val_len;
24 };
25 
26 static struct setting_entry entries[CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES];
27 static int entries_count;
28 
29 static int settings_its_load(struct settings_store *cs, const struct settings_load_arg *arg);
30 static int settings_its_save(struct settings_store *cs, const char *name, const char *value,
31 			     size_t val_len);
32 
33 static const struct settings_store_itf settings_its_itf = {
34 	.csi_load = settings_its_load,
35 	.csi_save = settings_its_save,
36 };
37 
38 static struct settings_store default_settings_its = {.cs_itf = &settings_its_itf};
39 
40 /* Ensure Key configured max size does not exceed reserved Key range */
41 BUILD_ASSERT(sizeof(entries) / CONFIG_TFM_ITS_MAX_ASSET_SIZE <=
42 	     ZEPHYR_PSA_SETTINGS_TFM_ITS_UID_RANGE_SIZE,
43 	     "entries array exceeds reserved ITS UID range");
44 
store_entries(void)45 static int store_entries(void)
46 {
47 	psa_status_t status;
48 	psa_storage_uid_t uid = ZEPHYR_PSA_SETTINGS_TFM_ITS_UID_RANGE_BEGIN;
49 	size_t remaining = sizeof(entries);
50 	size_t chunk_size = CONFIG_TFM_ITS_MAX_ASSET_SIZE;
51 	const uint8_t *data_ptr = (const uint8_t *)&entries;
52 
53 	/*
54 	 * Each ITS UID is treated like a sector. Data is written to each ITS node until
55 	 * that node is full, before incrementing the UID. This is done to minimize the
56 	 * number of allocated ITS nodes and to avoid wasting allocated bytes.
57 	 */
58 	while (remaining > 0) {
59 		size_t write_size = (remaining > chunk_size) ? chunk_size : remaining;
60 
61 		status = psa_its_set(uid, write_size, data_ptr, PSA_STORAGE_FLAG_NONE);
62 		if (status) {
63 			LOG_ERR("Error storing %d bytes of metadata! Bytes Remaining: %d, status: "
64 				"%d",
65 				write_size, remaining, status);
66 			return status;
67 		}
68 
69 		data_ptr += write_size;
70 		remaining -= write_size;
71 		uid++;
72 	}
73 
74 	LOG_DBG("ITS entries stored successfully - bytes_saved: %d num_entries: %d max_uid: %lld",
75 		sizeof(entries), entries_count, uid);
76 
77 	return 0;
78 }
79 
load_entries(void)80 static int load_entries(void)
81 {
82 	psa_status_t status;
83 	size_t bytes_read;
84 	psa_storage_uid_t uid = ZEPHYR_PSA_SETTINGS_TFM_ITS_UID_RANGE_BEGIN;
85 	size_t remaining = sizeof(entries);
86 	size_t chunk_size = CONFIG_TFM_ITS_MAX_ASSET_SIZE;
87 	uint8_t *data_ptr = (uint8_t *)&entries;
88 
89 	/*
90 	 * Each ITS UID is treated like a sector. Data is written to each ITS node until
91 	 * that node is full, before incrementing the UID. This is done to minimize the
92 	 * number of allocated ITS nodes and to avoid wasting allocated bytes.
93 	 */
94 	while (remaining > 0) {
95 		size_t to_read = (remaining > chunk_size) ? chunk_size : remaining;
96 
97 		status = psa_its_get(uid, 0, to_read, data_ptr, &bytes_read);
98 		if (status) {
99 			return status;
100 		}
101 
102 		data_ptr += bytes_read;
103 		remaining -= bytes_read;
104 		uid++;
105 	}
106 
107 	for (int i = 0; i < CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES; i++) {
108 		if (strnlen(entries[i].name, SETTINGS_MAX_NAME_LEN) != 0) {
109 			entries_count++;
110 		}
111 	}
112 
113 	LOG_DBG("ITS entries restored successfully - bytes_loaded: %d, num_entries: %d",
114 		sizeof(entries), entries_count);
115 
116 	return 0;
117 }
118 
119 /* void *back_end is the index of the entry in metadata entries struct */
settings_its_read_fn(void * back_end,void * data,size_t len)120 static ssize_t settings_its_read_fn(void *back_end, void *data, size_t len)
121 {
122 	int index = *(int *)back_end;
123 
124 	LOG_DBG("ITS Read - index: %d", index);
125 
126 	if (index < 0 || index >= CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES) {
127 		LOG_ERR("Invalid index %d in ITS metadata", index);
128 		return 0;
129 	}
130 
131 	memcpy(data, entries[index].value, len);
132 
133 	/*
134 	 * Callback expects return value of the number of bytes read
135 	 */
136 	return entries[index].val_len;
137 }
138 
settings_its_load(struct settings_store * cs,const struct settings_load_arg * arg)139 static int settings_its_load(struct settings_store *cs, const struct settings_load_arg *arg)
140 {
141 	int ret;
142 
143 	for (int i = 0; i < entries_count; i++) {
144 		if (strnlen(entries[i].name, SETTINGS_MAX_NAME_LEN) != 0) {
145 			/*
146 			 * Pass the key to the settings handler with it's index as an argument,
147 			 * to be read during callback function later.
148 			 */
149 			ret = settings_call_set_handler(entries[i].name, entries[i].val_len,
150 							settings_its_read_fn, (void *)&i,
151 							(void *)arg);
152 			if (ret) {
153 				return ret;
154 			}
155 		}
156 	}
157 
158 	return 0;
159 }
160 
settings_its_save(struct settings_store * cs,const char * name,const char * value,size_t val_len)161 static int settings_its_save(struct settings_store *cs, const char *name, const char *value,
162 			     size_t val_len)
163 {
164 	if (entries_count >= CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES) {
165 		LOG_ERR("%s: Max settings reached: %d", __func__,
166 			CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES);
167 		return -ENOMEM;
168 	}
169 
170 	if (val_len > SETTINGS_MAX_VAL_LEN) {
171 		LOG_ERR("%s: Invalid settings size - val_len: %d", __func__, val_len);
172 		return -EINVAL;
173 	}
174 
175 	int index;
176 	bool delete;
177 
178 	/* Find out if we are doing a delete */
179 	delete = ((value == NULL) || (val_len == 0));
180 
181 	/* Lock mutex before manipulating settings array */
182 	k_mutex_lock(&worker_mutex, K_FOREVER);
183 
184 	/*
185 	 * Search metadata to see if entry already exists. Array is compacted, so first blank entry
186 	 * signals end of settings.
187 	 */
188 	for (index = 0; index < CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES; index++) {
189 		if (strncmp(entries[index].name, name, SETTINGS_MAX_NAME_LEN) == 0) {
190 			break;
191 		} else if (entries[index].val_len == 0) {
192 
193 			/* Setting already deleted */
194 			if (delete) {
195 				LOG_DBG("%s: %s Already deleted!", __func__, name);
196 				k_mutex_unlock(&worker_mutex);
197 				return 0;
198 			}
199 
200 			/* New setting being entered */
201 			entries_count++;
202 			break;
203 		}
204 	}
205 
206 	LOG_DBG("ITS Save - index %d: name %s, val_len %d", index, name, val_len);
207 
208 	if (delete) {
209 		/* Clear metadata */
210 		memset(entries[index].name, 0, SETTINGS_MAX_NAME_LEN);
211 		memset(entries[index].value, 0, SETTINGS_MAX_VAL_LEN);
212 		entries[index].val_len = 0;
213 
214 		/* If setting not at end of array, shift entries */
215 		if (index < entries_count - 1) {
216 			memcpy(&entries[index], &entries[index + 1],
217 			       (entries_count - index - 1) * sizeof(struct setting_entry));
218 			/* Remove duplicate entry at end of array */
219 			memset(&entries[entries_count - 1], 0, sizeof(struct setting_entry));
220 		}
221 
222 		entries_count--;
223 	} else {
224 		/* Update metadata */
225 		strncpy(entries[index].name, name, SETTINGS_MAX_NAME_LEN);
226 		memcpy(entries[index].value, value, val_len);
227 		entries[index].val_len = val_len;
228 	}
229 
230 	k_mutex_unlock(&worker_mutex);
231 	k_work_schedule(&worker, K_MSEC(CONFIG_SETTINGS_TFM_ITS_LAZY_PERSIST_DELAY_MS));
232 
233 	return 0;
234 }
235 
worker_persist_entries_struct_fn(struct k_work * work)236 void worker_persist_entries_struct_fn(struct k_work *work)
237 {
238 	k_mutex_lock(&worker_mutex, K_FOREVER);
239 	store_entries();
240 	k_mutex_unlock(&worker_mutex);
241 }
242 
settings_backend_init(void)243 int settings_backend_init(void)
244 {
245 	psa_status_t status;
246 
247 	/* Load ITS metadata */
248 	status = load_entries();
249 
250 	/* If resource DNE, we need to allocate it */
251 	if (status == PSA_ERROR_DOES_NOT_EXIST) {
252 		status = store_entries();
253 		if (status) {
254 			LOG_ERR("Error storing metadata in %s: (status %d)", __func__, status);
255 			return -EIO;
256 		}
257 	} else if (status) {
258 		LOG_ERR("Error loading metadata in %s: (status %d)", __func__, status);
259 		return -EIO;
260 	}
261 
262 	settings_dst_register(&default_settings_its);
263 	settings_src_register(&default_settings_its);
264 
265 	k_work_init_delayable(&worker, worker_persist_entries_struct_fn);
266 
267 	return 0;
268 }
269