Skip to content

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 time

Forgetting 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
intcw_reboot_safe_valid(const void * blob, unsigned long total_size)
Checks whether a reboot-safe blob carries valid data.
voidcw_reboot_safe_init(void * blob, unsigned long total_size)
Initializes a reboot-safe blob to a known-good empty state.
voidcw_reboot_safe_commit(void * blob, unsigned long total_size)
Recomputes the checksum after the payload has been modified.

Defines

Name
cw_reboot_safeSection attribute that places a variable into the reboot-safe RAM region.
CW_REBOOT_SAFE_MAGICMagic 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 name

Declares 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.