Safe Data API
CRC-protected (optionally redundant) data containers for Zephyr
v0.0-dev+gbb519af
Loading...
Searching...
No Matches
safe_data.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2026 The safety-toolbox contributors
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * Safe Data API - generic CRC-protected (optionally redundant) containers.
6 *
7 * A "safe container" bundles a user payload together with a mutex and an
8 * integrity tag (CRC, plus an optional inverted shadow copy). All access goes
9 * through a small set of functions/macros that hold the lock across the whole
10 * read-modify-write and (re)seal the integrity tag automatically, so a stale
11 * tag or a torn update cannot happen "by construction".
12 *
13 * The heavy lifting lives in a single type-erased core (safe_data.c); the
14 * macros below only derive (&payload, sizeof(payload), &crc, &lock, shadow)
15 * from a typed container and forward to that core.
16 */
17
18#ifndef SAFE_DATA_H_
19#define SAFE_DATA_H_
20
21#include <stdint.h>
22#include <stddef.h>
23#include <errno.h>
24
25#include <zephyr/kernel.h>
26#include <zephyr/sys/util.h>
27#include <zephyr/toolchain.h>
28
29#ifdef __cplusplus
30extern "C" {
31#endif
32
33/*
34 * The library may be built standalone (e.g. for host unit testing) without the
35 * Kconfig fragment present. Provide safe defaults so the header is usable
36 * either way.
37 */
38#if !defined(CONFIG_SAFE_DATA_REDUNDANT)
39#define CONFIG_SAFE_DATA_REDUNDANT 0
40#endif
41
42/* Locking defaults to on; standalone builds without the Kconfig fragment get a
43 * mutex per container too. Set CONFIG_SAFE_DATA_LOCKING=n to drop the mutex when
44 * the data is only ever accessed from a single context.
45 */
46#if !defined(CONFIG_SAFE_DATA_LOCKING) && !defined(CONFIG_SAFE_DATA)
47#define CONFIG_SAFE_DATA_LOCKING 1
48#endif
49
50/* Remaining standalone defaults (Kconfig fragment absent): wait forever for
51 * the lock, keep the full API surface.
52 */
53#if !defined(CONFIG_SAFE_DATA)
54#if !defined(CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS)
55#define CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS 0
56#endif
57#if !defined(CONFIG_SAFE_DATA_GNU_EXTENSIONS)
58#define CONFIG_SAFE_DATA_GNU_EXTENSIONS 1
59#endif
60#if !defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
61#define CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT 1
62#endif
63#endif /* !CONFIG_SAFE_DATA */
64
76
83#define SAFE_DATA_RECOVERED (-EAGAIN)
84
98typedef int (*safe_data_mutator_t)(void *payload, void *user);
99 /* safe_data_core_apis */
101
112
128
134 const void *payload;
136 size_t len;
137};
138
149typedef void (*safe_data_fault_cb_t)(const struct safe_data_fault_info *info);
150
165
180void safe_data_fault_handler(const void *payload, size_t len);
181
197
207int safe_data_stats_get(struct safe_data_stats *out);
208
210void safe_data_stats_reset(void);
211
212#if defined(CONFIG_SAFE_DATA_SELFTEST) || defined(__DOXYGEN__)
225int safe_data_selftest(void);
226
234#endif /* CONFIG_SAFE_DATA_SELFTEST */
235 /* safe_data_diag_apis */
237
238/* ------------------------------------------------------------------------- *
239 * Type-erased core API. Prefer the typed macros below in application code.
240 * All functions return 0 on success or a negative errno; NULL pointers for
241 * required arguments yield -EINVAL. The @p lock argument is optional: pass NULL
242 * (as the typed macros do when CONFIG_SAFE_DATA_LOCKING=n) to skip locking.
243 * ------------------------------------------------------------------------- */
244
249
266int safe_data_init(struct k_mutex *lock, void *payload, size_t len,
267 uint32_t *crc, void *shadow);
268
287int safe_data_verify(const void *payload, size_t len, uint32_t crc,
288 const void *shadow);
289
313int safe_data_verify_repair(void *payload, size_t len, uint32_t crc,
314 void *shadow);
315
337int safe_data_read(struct k_mutex *lock, void *payload, size_t len,
338 uint32_t *crc, void *shadow, void *out);
339
363int safe_data_write(struct k_mutex *lock, void *payload, size_t len,
364 uint32_t *crc, void *shadow, const void *in);
365
393int safe_data_update(struct k_mutex *lock, void *payload, size_t len,
394 uint32_t *crc, void *shadow,
395 safe_data_mutator_t fn, void *user);
396
397#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT) || defined(__DOXYGEN__)
423int safe_data_commit(struct k_mutex *lock, void *payload, size_t len,
424 uint32_t *crc, void *shadow);
425#endif /* CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT */
426 /* safe_data_core_apis */
428
429/* Implementation details shared with the SAFE_SECTION macro. */
430void z_safe_data_seal(void *payload, size_t len, uint32_t *crc, void *shadow);
431void z_safe_data_lock_failed(void);
432
433/* Bound on lock acquisition; 0 keeps the legacy wait-forever behaviour. */
434#if CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS > 0
435#define Z_SAFE_LOCK_TIMEOUT K_MSEC(CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS)
436#else
437#define Z_SAFE_LOCK_TIMEOUT K_FOREVER
438#endif
439
440/* NULL-tolerant lock helpers: a NULL lock (locking disabled) is a no-op. Used
441 * by the core and by SAFE_SECTION so the same code works with or without a mutex.
442 *
443 * Returns 0 when the lock is held (or not needed), -ETIMEDOUT when the
444 * configured acquisition bound expired. Never legal from an ISR.
445 */
446static inline int z_safe_lock(struct k_mutex *lock)
447{
448 if (lock != NULL) {
449 __ASSERT(!k_is_in_isr(),
450 "safe_data: locking API is not ISR-safe");
451 if (k_mutex_lock(lock, Z_SAFE_LOCK_TIMEOUT) != 0) {
452 /* Map -EAGAIN (kernel timeout) away from the
453 * SAFE_DATA_RECOVERED alias.
454 */
455 return -ETIMEDOUT;
456 }
457 }
458 return 0;
459}
460
461static inline void z_safe_unlock(struct k_mutex *lock)
462{
463 if (lock != NULL) {
464 k_mutex_unlock(lock);
465 }
466}
467
468/* ------------------------------------------------------------------------- *
469 * Typed convenience layer.
470 * ------------------------------------------------------------------------- */
471
482
483/* Detection strength of a CRC collapses once the payload outgrows it. These
484 * bounds keep the configured tag inside its useful Hamming-distance range; use
485 * SAFE_CONTAINER_DEFINE_UNCHECKED only with a documented deviation.
486 */
487#if defined(CONFIG_SAFE_DATA_CRC8_CCITT)
488#define Z_SAFE_DATA_MAX_PAYLOAD 16
489#elif defined(CONFIG_SAFE_DATA_CRC16_CCITT)
490#define Z_SAFE_DATA_MAX_PAYLOAD 256
491#else /* CRC32 */
492#define Z_SAFE_DATA_MAX_PAYLOAD SIZE_MAX
493#endif
494
495#define Z_SAFE_CONTAINER_BODY(_payload_type) \
496 IF_ENABLED(CONFIG_SAFE_DATA_LOCKING, (struct k_mutex _lock;)) \
497 uint32_t _crc; \
498 IF_ENABLED(CONFIG_SAFE_DATA_REDUNDANT, \
499 (uint8_t _shadow[sizeof(_payload_type)];)) \
500 _payload_type payload;
501
523#define SAFE_CONTAINER_DEFINE(_container_name, _payload_type) \
524 struct _container_name { \
525 BUILD_ASSERT(sizeof(_payload_type) > 0, \
526 "safe_data: zero-size payload"); \
527 BUILD_ASSERT(sizeof(_payload_type) <= Z_SAFE_DATA_MAX_PAYLOAD, \
528 "safe_data: payload too large for the configured" \
529 " integrity tag (see Z_SAFE_DATA_MAX_PAYLOAD)"); \
530 Z_SAFE_CONTAINER_BODY(_payload_type) \
531 }
532
540#define SAFE_CONTAINER_DEFINE_UNCHECKED(_container_name, _payload_type) \
541 struct _container_name { \
542 BUILD_ASSERT(sizeof(_payload_type) > 0, \
543 "safe_data: zero-size payload"); \
544 Z_SAFE_CONTAINER_BODY(_payload_type) \
545 }
546
547/* Resolve the shadow pointer to either &_shadow[0] or NULL at compile time. */
548#define _SAFE_SHADOW(_c) \
549 COND_CODE_1(CONFIG_SAFE_DATA_REDUNDANT, ((_c)->_shadow), (NULL))
550
551/* Resolve the lock pointer to either &_lock or NULL at compile time. When
552 * locking is disabled the core functions receive NULL and skip serialisation.
553 */
554#define _SAFE_LOCK(_c) \
555 COND_CODE_1(CONFIG_SAFE_DATA_LOCKING, (&(_c)->_lock), (NULL))
556
562#define SAFE_INIT(_c) \
563 safe_data_init(_SAFE_LOCK(_c), &(_c)->payload, sizeof((_c)->payload), \
564 &(_c)->_crc, _SAFE_SHADOW(_c))
565
572#define SAFE_VERIFY(_c) \
573 safe_data_verify(&(_c)->payload, sizeof((_c)->payload), (_c)->_crc, \
574 _SAFE_SHADOW(_c))
575
582#define SAFE_READ(_c, _out_ptr) \
583 safe_data_read(_SAFE_LOCK(_c), &(_c)->payload, sizeof((_c)->payload), \
584 &(_c)->_crc, _SAFE_SHADOW(_c), (_out_ptr))
585
592#define SAFE_WRITE(_c, _in_ptr) \
593 safe_data_write(_SAFE_LOCK(_c), &(_c)->payload, sizeof((_c)->payload), \
594 &(_c)->_crc, _SAFE_SHADOW(_c), (_in_ptr))
595
603#define SAFE_UPDATE(_c, _fn, _user) \
604 safe_data_update(_SAFE_LOCK(_c), &(_c)->payload, sizeof((_c)->payload),\
605 &(_c)->_crc, _SAFE_SHADOW(_c), (_fn), (_user))
606
607#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT) || defined(__DOXYGEN__)
616#define SAFE_COMMIT(_c) \
617 safe_data_commit(_SAFE_LOCK(_c), &(_c)->payload, \
618 sizeof((_c)->payload), &(_c)->_crc, _SAFE_SHADOW(_c))
619#endif /* CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT */
620
662#if defined(CONFIG_SAFE_DATA_GNU_EXTENSIONS) || defined(__DOXYGEN__)
663#define SAFE_SECTION(_c, _pvar) \
664 for (int _sd_st = ((z_safe_lock(_SAFE_LOCK(_c)) == 0) \
665 ? safe_data_verify_repair(&(_c)->payload, \
666 sizeof((_c)->payload), (_c)->_crc, \
667 _SAFE_SHADOW(_c)) \
668 : (z_safe_data_lock_failed(), -ETIMEDOUT)); \
669 /* enter on valid (0) or recovered (-EAGAIN) */ \
670 ({ bool _ok = (_sd_st == 0 || _sd_st == SAFE_DATA_RECOVERED); \
671 if (!_ok && _sd_st != -ETIMEDOUT) { \
672 z_safe_unlock(_SAFE_LOCK(_c)); \
673 } \
674 _ok; }); \
675 z_safe_data_seal(&(_c)->payload, sizeof((_c)->payload), \
676 &(_c)->_crc, _SAFE_SHADOW(_c)), \
677 z_safe_unlock(_SAFE_LOCK(_c)), _sd_st = 1) \
678 for (__typeof__((_c)->payload) *_pvar = &(_c)->payload; \
679 _pvar != NULL; _pvar = NULL)
680#endif /* CONFIG_SAFE_DATA_GNU_EXTENSIONS */
681 /* safe_data_container_apis */
683
684#ifdef __cplusplus
685}
686#endif
687
688#endif /* SAFE_DATA_H_ */
int safe_data_verify_repair(void *payload, size_t len, uint32_t crc, void *shadow)
Verify payload integrity and repair a recoverable fault in place.
Definition safe_data.c:290
int safe_data_update(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, safe_data_mutator_t fn, void *user)
Atomic read-modify-write: lock, verify, run fn, reseal on success.
Definition safe_data.c:386
int safe_data_read(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, void *out)
Verify under lock and copy the payload out to out.
Definition safe_data.c:326
int(* safe_data_mutator_t)(void *payload, void *user)
Mutator callback used by safe_data_update() / SAFE_UPDATE().
Definition safe_data.h:98
int safe_data_verify(const void *payload, size_t len, uint32_t crc, const void *shadow)
Verify payload integrity. Pure: read-only and lock-free; it NEVER modifies the payload,...
Definition safe_data.c:277
int safe_data_write(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, const void *in)
Copy in into the payload under lock and reseal the integrity tag.
Definition safe_data.c:354
int safe_data_init(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow)
Initialise the lock and seal the current payload contents.
Definition safe_data.c:224
int safe_data_commit(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow)
Reseal after an explicit external modification (advanced use). Lock, recompute the integrity tag,...
Definition safe_data.c:454
void(* safe_data_fault_cb_t)(const struct safe_data_fault_info *info)
Fault/diagnostic event callback.
Definition safe_data.h:149
safe_data_fault_kind
Kind of integrity event reported to the fault handler / statistics.
Definition safe_data.h:114
int safe_data_selftest_result(void)
Result of the boot-time self-test (CONFIG_SAFE_DATA_SELFTEST_BOOT).
Definition safe_data.c:551
void safe_data_stats_reset(void)
Reset all statistics counters to zero.
Definition safe_data.c:134
int safe_data_stats_get(struct safe_data_stats *out)
Snapshot the global statistics.
Definition safe_data.c:112
void safe_data_fault_handler(const void *payload, size_t len)
Legacy user-overridable (weak) fault handler.
Definition safe_data.c:90
int safe_data_selftest(void)
Self-test of the integrity mechanism itself.
Definition safe_data.c:490
int safe_data_fault_handler_register(safe_data_fault_cb_t cb)
Register the fault handler callback.
Definition safe_data.c:97
@ SAFE_DATA_FAULT_KIND_UNRECOVERABLE
Definition safe_data.h:116
@ SAFE_DATA_FAULT_KIND_LOCK_TIMEOUT
Definition safe_data.h:122
@ SAFE_DATA_FAULT_KIND_RECOVERED_PAYLOAD
Definition safe_data.h:120
@ SAFE_DATA_FAULT_KIND_MUTATOR_CLAMPED
Definition safe_data.h:126
@ SAFE_DATA_FAULT_KIND_RECOVERED_TAG
Definition safe_data.h:118
@ SAFE_DATA_FAULT_KIND_OVERWRITTEN
Definition safe_data.h:124
Event descriptor passed to the registered fault handler.
Definition safe_data.h:130
const void * payload
Definition safe_data.h:134
enum safe_data_fault_kind kind
Definition safe_data.h:132
Global fault/recovery statistics (CONFIG_SAFE_DATA_STATS).
Definition safe_data.h:183
uint32_t unrecoverable
Definition safe_data.h:189
uint32_t overwritten
Definition safe_data.h:193
uint32_t lock_timeouts
Definition safe_data.h:191
uint32_t recovered_tag
Definition safe_data.h:185
uint32_t recovered_payload
Definition safe_data.h:187
uint32_t mutator_clamped
Definition safe_data.h:195