1 /*
2  * Copyright (c) 2023, Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT zephyr_retention
8 
9 #include <string.h>
10 #include <sys/types.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/sys/util.h>
13 #include <zephyr/sys/crc.h>
14 #include <zephyr/device.h>
15 #include <zephyr/devicetree.h>
16 #include <zephyr/drivers/retained_mem.h>
17 #include <zephyr/retention/retention.h>
18 #include <zephyr/logging/log.h>
19 
20 LOG_MODULE_REGISTER(retention, CONFIG_RETENTION_LOG_LEVEL);
21 
22 #define DATA_VALID_VALUE 1
23 
24 #define INST_HAS_CHECKSUM(n) DT_INST_PROP(n, checksum) ||
25 
26 #define INST_HAS_PREFIX(n) COND_CODE_1(DT_INST_NODE_HAS_PROP(n, prefix), (1), (0)) ||
27 
28 #if (DT_INST_FOREACH_STATUS_OKAY(INST_HAS_CHECKSUM) 0)
29 #define ANY_HAS_CHECKSUM
30 #endif
31 
32 #if (DT_INST_FOREACH_STATUS_OKAY(INST_HAS_PREFIX) 0)
33 #define ANY_HAS_PREFIX
34 #endif
35 
36 enum {
37 	CHECKSUM_NONE = 0,
38 	CHECKSUM_CRC8,
39 	CHECKSUM_CRC16,
40 	CHECKSUM_UNUSED,
41 	CHECKSUM_CRC32,
42 };
43 
44 struct retention_data {
45 	bool header_written;
46 #ifdef CONFIG_RETENTION_MUTEXES
47 	struct k_mutex lock;
48 #endif
49 };
50 
51 struct retention_config {
52 	const struct device *parent;
53 	size_t offset;
54 	size_t size;
55 	size_t reserved_size;
56 	uint8_t checksum_size;
57 	uint8_t prefix_len;
58 	uint8_t prefix[];
59 };
60 
retention_lock_take(const struct device * dev)61 static inline void retention_lock_take(const struct device *dev)
62 {
63 #ifdef CONFIG_RETENTION_MUTEXES
64 	struct retention_data *data = dev->data;
65 
66 	k_mutex_lock(&data->lock, K_FOREVER);
67 #else
68 	ARG_UNUSED(dev);
69 #endif
70 }
71 
retention_lock_release(const struct device * dev)72 static inline void retention_lock_release(const struct device *dev)
73 {
74 #ifdef CONFIG_RETENTION_MUTEXES
75 	struct retention_data *data = dev->data;
76 
77 	k_mutex_unlock(&data->lock);
78 #else
79 	ARG_UNUSED(dev);
80 #endif
81 }
82 
83 #ifdef ANY_HAS_CHECKSUM
retention_checksum(const struct device * dev,uint32_t * output)84 static int retention_checksum(const struct device *dev, uint32_t *output)
85 {
86 	const struct retention_config *config = dev->config;
87 	int rc = -ENOSYS;
88 
89 	if (config->checksum_size == CHECKSUM_CRC8 ||
90 	    config->checksum_size == CHECKSUM_CRC16 ||
91 	    config->checksum_size == CHECKSUM_CRC32) {
92 		size_t pos = config->offset + config->prefix_len;
93 		size_t end = config->offset + config->size - config->checksum_size;
94 		uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE];
95 
96 		*output = 0;
97 
98 		while (pos < end) {
99 			uint16_t read_size = MIN((end - pos), sizeof(buffer));
100 
101 			rc = retained_mem_read(config->parent, pos, buffer, read_size);
102 
103 			if (rc < 0) {
104 				goto finish;
105 			}
106 
107 			if (config->checksum_size == CHECKSUM_CRC8) {
108 				*output = (uint32_t)crc8(buffer, read_size, 0x12,
109 							 (uint8_t)*output, false);
110 			} else if (config->checksum_size == CHECKSUM_CRC16) {
111 				*output = (uint32_t)crc16_itu_t((uint16_t)*output,
112 								buffer, read_size);
113 			} else if (config->checksum_size == CHECKSUM_CRC32) {
114 				*output = crc32_ieee_update(*output, buffer, read_size);
115 			}
116 
117 			pos += read_size;
118 		}
119 	}
120 
121 finish:
122 	return rc;
123 }
124 #endif
125 
retention_init(const struct device * dev)126 static int retention_init(const struct device *dev)
127 {
128 	const struct retention_config *config = dev->config;
129 #ifdef CONFIG_RETENTION_MUTEXES
130 	struct retention_data *data = dev->data;
131 #endif
132 	ssize_t area_size;
133 
134 	if (!device_is_ready(config->parent)) {
135 		LOG_ERR("Parent device is not ready");
136 		return -ENODEV;
137 	}
138 
139 	/* Ensure backend has a large enough storage area for the requirements of
140 	 * this retention area
141 	 */
142 	area_size = retained_mem_size(config->parent);
143 
144 	if (area_size < 0) {
145 		LOG_ERR("Parent initialisation failure: %d", area_size);
146 		return area_size;
147 	}
148 
149 	if ((config->offset + config->size) > area_size) {
150 		/* Backend storage is insufficient */
151 		LOG_ERR("Underlying area size is insufficient, requires: 0x%x, has: 0x%x",
152 			(config->offset + config->size), area_size);
153 		return -EINVAL;
154 	}
155 
156 #ifdef CONFIG_RETENTION_MUTEXES
157 	k_mutex_init(&data->lock);
158 #endif
159 
160 	return 0;
161 }
162 
retention_size(const struct device * dev)163 ssize_t retention_size(const struct device *dev)
164 {
165 	const struct retention_config *config = dev->config;
166 
167 	return (config->size - config->reserved_size);
168 }
169 
retention_is_valid(const struct device * dev)170 int retention_is_valid(const struct device *dev)
171 {
172 	const struct retention_config *config = dev->config;
173 	int rc = 0;
174 
175 	retention_lock_take(dev);
176 
177 	/* If neither the header or checksum are enabled, return a not supported error */
178 	if (config->prefix_len == 0 && config->checksum_size == 0) {
179 		rc = -ENOTSUP;
180 		goto finish;
181 	}
182 
183 #ifdef ANY_HAS_PREFIX
184 	if (config->prefix_len != 0) {
185 		/* Check magic header is present at the start of the section */
186 		struct retention_data *data = dev->data;
187 		uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE];
188 		off_t pos = 0;
189 
190 		while (pos < config->prefix_len) {
191 			uint16_t read_size = MIN((config->prefix_len - pos), sizeof(buffer));
192 
193 			rc = retained_mem_read(config->parent, (config->offset + pos), buffer,
194 					       read_size);
195 
196 			if (rc < 0) {
197 				goto finish;
198 			}
199 
200 			if (memcmp(&config->prefix[pos], buffer, read_size) != 0) {
201 				/* If the magic header does not match, do not check the rest of
202 				 * the validity of the data, assume it is invalid
203 				 */
204 				data->header_written = false;
205 				rc = 0;
206 				goto finish;
207 			}
208 
209 			pos += read_size;
210 		}
211 
212 		/* Header already exists so no need to re-write it again */
213 		data->header_written = true;
214 	}
215 #endif
216 
217 #ifdef ANY_HAS_CHECKSUM
218 	if (config->checksum_size != 0) {
219 		/* Check the checksum validity, for this all the data must be read out */
220 		uint32_t checksum = 0;
221 		uint32_t expected_checksum = 0;
222 		ssize_t data_size = config->size - config->checksum_size;
223 
224 		rc = retention_checksum(dev, &checksum);
225 
226 		if (rc < 0) {
227 			goto finish;
228 		}
229 
230 		if (config->checksum_size == CHECKSUM_CRC8) {
231 			uint8_t read_checksum;
232 
233 			rc = retained_mem_read(config->parent, (config->offset + data_size),
234 					       (void *)&read_checksum, sizeof(read_checksum));
235 			expected_checksum = (uint32_t)read_checksum;
236 		} else if (config->checksum_size == CHECKSUM_CRC16) {
237 			uint16_t read_checksum;
238 
239 			rc = retained_mem_read(config->parent, (config->offset + data_size),
240 					       (void *)&read_checksum, sizeof(read_checksum));
241 			expected_checksum = (uint32_t)read_checksum;
242 		} else if (config->checksum_size == CHECKSUM_CRC32) {
243 			rc = retained_mem_read(config->parent, (config->offset + data_size),
244 					       (void *)&expected_checksum,
245 					       sizeof(expected_checksum));
246 		}
247 
248 		if (rc < 0) {
249 			goto finish;
250 		}
251 
252 		if (checksum != expected_checksum) {
253 			goto finish;
254 		}
255 	}
256 #endif
257 
258 	/* At this point, checks have passed (if enabled), mark data as being valid */
259 	rc = DATA_VALID_VALUE;
260 
261 finish:
262 	retention_lock_release(dev);
263 
264 	return rc;
265 }
266 
retention_read(const struct device * dev,off_t offset,uint8_t * buffer,size_t size)267 int retention_read(const struct device *dev, off_t offset, uint8_t *buffer, size_t size)
268 {
269 	const struct retention_config *config = dev->config;
270 	int rc;
271 
272 	if (offset < 0 || ((size_t)offset + size) > (config->size - config->reserved_size)) {
273 		/* Disallow reading past the virtual data size or before it */
274 		return -EINVAL;
275 	}
276 
277 	retention_lock_take(dev);
278 
279 	rc = retained_mem_read(config->parent, (config->offset + config->prefix_len +
280 			       (size_t)offset), buffer, size);
281 
282 	retention_lock_release(dev);
283 
284 	return rc;
285 }
286 
retention_write(const struct device * dev,off_t offset,const uint8_t * buffer,size_t size)287 int retention_write(const struct device *dev, off_t offset, const uint8_t *buffer, size_t size)
288 {
289 	const struct retention_config *config = dev->config;
290 	int rc;
291 
292 #ifdef ANY_HAS_PREFIX
293 	struct retention_data *data = dev->data;
294 #endif
295 
296 	retention_lock_take(dev);
297 
298 	if (offset < 0 || ((size_t)offset + size) > (config->size - config->reserved_size)) {
299 		/* Disallow writing past the virtual data size or before it */
300 		rc = -EINVAL;
301 		goto finish;
302 	}
303 
304 	rc = retained_mem_write(config->parent, (config->offset + config->prefix_len +
305 				(size_t)offset), buffer, size);
306 
307 	if (rc < 0) {
308 		goto finish;
309 	}
310 
311 #ifdef ANY_HAS_PREFIX
312 	/* Write optional header and footer information, these are done last to ensure data
313 	 * validity before marking it as being valid
314 	 */
315 	if (config->prefix_len != 0 && data->header_written == false) {
316 		rc = retained_mem_write(config->parent, config->offset, (void *)config->prefix,
317 					config->prefix_len);
318 
319 		if (rc < 0) {
320 			goto finish;
321 		}
322 
323 		data->header_written = true;
324 	}
325 #endif
326 
327 #ifdef ANY_HAS_CHECKSUM
328 	if (config->checksum_size != 0) {
329 		/* Generating a checksum requires reading out all the data in the region */
330 		uint32_t checksum = 0;
331 
332 		rc = retention_checksum(dev, &checksum);
333 
334 		if (rc < 0) {
335 			goto finish;
336 		}
337 
338 		if (config->checksum_size == CHECKSUM_CRC8) {
339 			uint8_t output_checksum = (uint8_t)checksum;
340 
341 			rc = retained_mem_write(config->parent,
342 					(config->offset + config->size - config->checksum_size),
343 					(void *)&output_checksum, sizeof(output_checksum));
344 		} else if (config->checksum_size == CHECKSUM_CRC16) {
345 			uint16_t output_checksum = (uint16_t)checksum;
346 
347 			rc = retained_mem_write(config->parent,
348 					(config->offset + config->size - config->checksum_size),
349 					(void *)&output_checksum, sizeof(output_checksum));
350 		} else if (config->checksum_size == CHECKSUM_CRC32) {
351 			rc = retained_mem_write(config->parent,
352 					(config->offset + config->size - config->checksum_size),
353 					(void *)&checksum, sizeof(checksum));
354 		}
355 	}
356 #endif
357 
358 finish:
359 	retention_lock_release(dev);
360 
361 	return rc;
362 }
363 
retention_clear(const struct device * dev)364 int retention_clear(const struct device *dev)
365 {
366 	const struct retention_config *config = dev->config;
367 	struct retention_data *data = dev->data;
368 	int rc = 0;
369 	uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE];
370 	off_t pos = 0;
371 
372 	memset(buffer, 0, sizeof(buffer));
373 
374 	retention_lock_take(dev);
375 
376 	data->header_written = false;
377 
378 	while (pos < config->size) {
379 		rc = retained_mem_write(config->parent, (config->offset + pos), buffer,
380 					MIN((config->size - pos), sizeof(buffer)));
381 
382 		if (rc < 0) {
383 			goto finish;
384 		}
385 
386 		pos += MIN((config->size - pos), sizeof(buffer));
387 	}
388 
389 finish:
390 	retention_lock_release(dev);
391 
392 	return rc;
393 }
394 
395 static const struct retention_api retention_api = {
396 	.size = retention_size,
397 	.is_valid = retention_is_valid,
398 	.read = retention_read,
399 	.write = retention_write,
400 	.clear = retention_clear,
401 };
402 
403 #define RETENTION_DEVICE(inst)									\
404 	static struct retention_data								\
405 		retention_data_##inst = {							\
406 		.header_written = false,							\
407 	};											\
408 	static const struct retention_config							\
409 		retention_config_##inst = {							\
410 		.parent = DEVICE_DT_GET(DT_PARENT(DT_INST(inst, DT_DRV_COMPAT))),		\
411 		.checksum_size = DT_INST_PROP(inst, checksum),					\
412 		.offset = DT_INST_REG_ADDR(inst),						\
413 		.size = DT_INST_REG_SIZE(inst),							\
414 		.reserved_size = (COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prefix),		\
415 					      (DT_INST_PROP_LEN(inst, prefix)), (0)) +		\
416 				  DT_INST_PROP(inst, checksum)),				\
417 		.prefix_len = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prefix),			\
418 					  (DT_INST_PROP_LEN(inst, prefix)), (0)),		\
419 		.prefix = DT_INST_PROP_OR(inst, prefix, {0}),					\
420 	};											\
421 	DEVICE_DT_INST_DEFINE(inst,								\
422 			      &retention_init,							\
423 			      NULL,								\
424 			      &retention_data_##inst,						\
425 			      &retention_config_##inst,						\
426 			      POST_KERNEL,							\
427 			      CONFIG_RETENTION_INIT_PRIORITY,					\
428 			      &retention_api);
429 
430 DT_INST_FOREACH_STATUS_OKAY(RETENTION_DEVICE)
431