Appearance
Reboot-safe Storage
RAM that survives soft resets, protected by magic cookie and checksum.
What it is
A dedicated RAM region (.cw_reboot_safe) whose contents are not zeroed by the startup code. Anything you place there keeps its value across soft-reset events — NVIC_SystemReset, a watchdog timeout, or a HardFault → reset path — so the next firmware run can read what the previous run left behind.
When NOT to rely on it
The region lives in SRAM. It is lost on:
- power-on / cold boot,
- brown-outs that drop VDD below SRAM retention voltage,
- debugger-driven full chip resets on some MCUs.
Because of that, every read must validate first. The helpers in this group make that cheap: a magic cookie plus a folding checksum over the payload tells you whether the bytes you see were written by a previous firmware run or are just whatever the SRAM cells happened to power up to.
Two flavors
- Raw attribute (cw_reboot_safe) — drops an arbitrary variable into the section with no header and no validation. You are on your own for deciding whether the bytes are trustworthy. Use this only for write-mostly buffers that the consumer can treat as opportunistic (e.g. the fault handler's backtrace ring).
- Structured blob (CW_REBOOT_SAFE_BLOB + CW_RB_VALID / CW_RB_INIT / CW_RB_COMMIT / CW_RB_PAYLOAD) — wraps your payload in a hidden header. This is what you want for anything you actually read back.
The four-step lifecycle (structured blob)
Every consumer follows the same pattern:
cpp
if (!CW_RB_VALID(blob)) // 1. is the data trustworthy?
CW_RB_INIT(blob); // 2. no → zero it and stamp the header
CW_RB_PAYLOAD(blob)->x++; // 3. read / mutate the payload
CW_RB_COMMIT(blob); // 4. refresh the checksum so step 1 passes next timeForgetting step 4 is the most common bug — after the next reset the blob will look corrupted and CW_RB_INIT will silently wipe your changes.
Example 1 — Boot counter (typical "did we survive last time?" case)
cpp
#include <kernel.h>
typedef struct { uint32_t reboot_count; } boot_stats_t;
CW_REBOOT_SAFE_BLOB(g_boot, boot_stats_t);
void on_boot(void) {
if (!CW_RB_VALID(g_boot)) {
CW_RB_INIT(g_boot); // cold boot, brown-out, or bit-flip
}
CW_RB_PAYLOAD(g_boot)->reboot_count++;
CW_RB_COMMIT(g_boot);
}Example 2 — Pending-OTA handoff between firmware runs
The running firmware records that an OTA image is ready and triggers a reset; the bootloader / next firmware reads the flag and acts on it.
cpp
typedef struct {
uint32_t pending; // 1 = apply OTA on next boot
uint32_t slot; // which flash slot holds the new image
} ota_handoff_t;
CW_REBOOT_SAFE_BLOB(g_ota, ota_handoff_t);
// Old firmware, right before reset:
void request_ota_apply(uint32_t slot) {
if (!CW_RB_VALID(g_ota)) CW_RB_INIT(g_ota);
CW_RB_PAYLOAD(g_ota)->pending = 1;
CW_RB_PAYLOAD(g_ota)->slot = slot;
CW_RB_COMMIT(g_ota); // <-- MUST happen before NVIC_SystemReset
NVIC_SystemReset();
}
// Next firmware, very early in boot:
void check_ota_handoff(void) {
if (!CW_RB_VALID(g_ota)) return; // cold boot → nothing pending
ota_handoff_t* h = CW_RB_PAYLOAD(g_ota);
if (h->pending) {
apply_image_from_slot(h->slot);
h->pending = 0;
CW_RB_COMMIT(g_ota); // consume the flag
}
}Example 3 — Raw attribute for a fault-handler backtrace
No header, no checksum: the fault handler scribbles into the buffer on its way down, and a post-reset consumer prints whatever survived. The consumer treats garbage as "no usable trace" and moves on.
cpp
typedef struct { uint32_t pc; uint32_t lr; } frame_t;
cw_reboot_safe static frame_t backtrace[16]; // raw — no CW_RB_* helpers
void HardFault_Handler(void) {
capture_frames_into(backtrace, 16); // writes are best-effort
NVIC_SystemReset();
}For anything you actually want to read back reliably, prefer the structured blob in Examples 1 and 2.
Functions Overview
| Name | |
|---|---|
| int | cw_reboot_safe_valid(const void * blob, unsigned long total_size) Checks whether a reboot-safe blob carries valid data. |
| void | cw_reboot_safe_init(void * blob, unsigned long total_size) Initializes a reboot-safe blob to a known-good empty state. |
| void | cw_reboot_safe_commit(void * blob, unsigned long total_size) Recomputes the checksum after the payload has been modified. |
Defines
| Name | |
|---|---|
| cw_reboot_safe | Section attribute that places a variable into the reboot-safe RAM region. |
| CW_REBOOT_SAFE_MAGIC | Magic cookie written into a reboot-safe blob's header when it is valid. |
| CW_REBOOT_SAFE_BLOB(name, payload_type) | Declares a typed reboot-safe blob with magic + checksum protection. |
| CW_RB_VALID(b) | Convenience wrapper around cw_reboot_safe_valid for a blob declared with CW_REBOOT_SAFE_BLOB. |
| CW_RB_INIT(b) | Convenience wrapper around cw_reboot_safe_init for a blob declared with CW_REBOOT_SAFE_BLOB. |
| CW_RB_COMMIT(b) | Convenience wrapper around cw_reboot_safe_commit for a blob declared with CW_REBOOT_SAFE_BLOB. |
| CW_RB_PAYLOAD(b) | Returns a typed pointer to the payload of a blob declared with CW_REBOOT_SAFE_BLOB. |
Function Details
function cw_reboot_safe_valid
cpp
int cw_reboot_safe_valid(
const void * blob,
unsigned long total_size
)Checks whether a reboot-safe blob carries valid data.
Parameters:
- blob Pointer to the blob (header first, payload after).
- total_size Total size of the blob in bytes, including the header.
Return: Non-zero if the blob is valid, 0 otherwise.
Verifies the header's magic cookie and recomputes the checksum over the payload bytes. Application code normally calls this through the CW_RB_VALID convenience macro.
function cw_reboot_safe_init
cpp
void cw_reboot_safe_init(
void * blob,
unsigned long total_size
)Initializes a reboot-safe blob to a known-good empty state.
Parameters:
- blob Pointer to the blob to initialize.
- total_size Total size of the blob in bytes, including the header.
Writes the magic cookie, zeroes the payload bytes and stores the matching checksum. Use this after cw_reboot_safe_valid reports the blob as invalid (cold boot or detected corruption). Normally invoked via the CW_RB_INIT macro.
function cw_reboot_safe_commit
cpp
void cw_reboot_safe_commit(
void * blob,
unsigned long total_size
)Recomputes the checksum after the payload has been modified.
Parameters:
- blob Pointer to the blob whose checksum should be refreshed.
- total_size Total size of the blob in bytes, including the header.
Must be called after every write to the payload so the next cw_reboot_safe_valid check still succeeds. Normally invoked via the CW_RB_COMMIT macro.
Skipping this call is the most common reboot-safe bug: the payload on disk is correct, but the stale checksum makes the next firmware run treat the blob as garbage and CW_RB_INIT it back to zero. In particular, if you mutate the payload right before triggering a reset, commit before calling NVIC_SystemReset.
Macros Documentation
define cw_reboot_safe
cpp
#define cw_reboot_safe __attribute__((section(".cw_reboot_safe")))Section attribute that places a variable into the reboot-safe RAM region.
Used directly for raw, unstructured storage where the caller does its own validity check. For structured payloads with magic + checksum protection, prefer CW_REBOOT_SAFE_BLOB.
Example:
cpp
cw_reboot_safe static uint32_t backtrace[16];define CW_REBOOT_SAFE_MAGIC
cpp
#define CW_REBOOT_SAFE_MAGIC (0xC01DC0DEU)Magic cookie written into a reboot-safe blob's header when it is valid.
define CW_REBOOT_SAFE_BLOB
cpp
#define CW_REBOOT_SAFE_BLOB(
name,
payload_type
)
typedef struct \
{ \
cw_reboot_safe_hdr_t _hdr; \
payload_type payload; \
} name##_blob_t; \
cw_reboot_safe static name##_blob_t nameDeclares a typed reboot-safe blob with magic + checksum protection.
Parameters:
- name Identifier of the resulting variable (file-scope
static). - payload_type Type of the user payload carried inside the blob.
Expands to a static variable in the .cw_reboot_safe section whose layout is { cw_reboot_safe_hdr_t _hdr; payload_type payload; }. Access the payload via CW_RB_PAYLOAD; validate / initialize / commit it via CW_RB_VALID, CW_RB_INIT and CW_RB_COMMIT.
define CW_RB_VALID
cpp
#define CW_RB_VALID(
b
)
cw_reboot_safe_valid (&(b), sizeof(b))Convenience wrapper around cw_reboot_safe_valid for a blob declared with CW_REBOOT_SAFE_BLOB.
define CW_RB_INIT
cpp
#define CW_RB_INIT(
b
)
cw_reboot_safe_init (&(b), sizeof(b))Convenience wrapper around cw_reboot_safe_init for a blob declared with CW_REBOOT_SAFE_BLOB.
define CW_RB_COMMIT
cpp
#define CW_RB_COMMIT(
b
)
cw_reboot_safe_commit(&(b), sizeof(b))Convenience wrapper around cw_reboot_safe_commit for a blob declared with CW_REBOOT_SAFE_BLOB.
define CW_RB_PAYLOAD
cpp
#define CW_RB_PAYLOAD(
b
)
(&(b).payload)Returns a typed pointer to the payload of a blob declared with CW_REBOOT_SAFE_BLOB.