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 uint8_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 uint8_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