Safe API Framework 0.1.0 build 384e6ae
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
Safe-State Module - Architecture

Design Overview

Safe-State is a handler registration system for critical events. Applications register handlers for each severity level (DEGRADED, SAFE, REBOOT), then call RTE_SAFESTATE() or RTE_REBOOT() macros to trigger transitions. The framework invokes the registered handler with context (file, line, message) and guarantees control never returns for SAFE/REBOOT levels.

No dynamic allocation; handler storage is fixed (3 slots, one per level).

Three Severity Levels

DEGRADED (0) - Can return to caller
  └─ Use for: single component failure, reduced redundancy
  └─ Example: backup channel failed, primary still working
  └─ Behavior: handler runs, can return, system continues

SAFE (1) - Cannot return
  └─ Use for: critical fail-safe state, critical functions disabled
  └─ Example: both channels down, or voting disagreement
  └─ Behavior: handler runs, MUST NOT return, infinite loop if it does

REBOOT (2) - Cannot return
  └─ Use for: emergency restart required
  └─ Example: unrecoverable software error, corruption detected
  └─ Behavior: handler runs, MUST NOT return, infinite loop if it does

Core API

Registration

rte_safestate_level_t
Safe-state severity levels (ADR-004 section 2.1).
rte_status_t rte_safestate_register_handler(rte_safestate_level_t level, rte_safestate_handler_t handler)
Registers the reaction handler for one safe-state level. This is how an integrator supplies their own...
void(*) rte_safestate_handler_t(rte_safestate_level_t level, rte_safestate_reason_t reason, const char *file, int32_t line, const char *message)
Application-supplied reaction for one safe-state level.
rte_status_t
Common result/status codes.
Definition rte_status.h:27

Registers handler for one level. Replaces any previous handler for that level. Called at startup before any safe-state transitions are expected.

Entering a Level

const char *file,
int32_t line,
const char *message);
uint16_t rte_safestate_reason_t
Diagnostic reason code accompanying a safe-state transition.
void rte_safestate_enter(rte_safestate_level_t level, rte_safestate_reason_t reason, const char *file, int32_t line, const char *message)
Enters a safe-state level: invokes the registered handler (if any), then, for RTE_SAFESTATE_LEVEL_SAF...

Invokes registered handler for level, passing context. For SAFE/REBOOT, never returns (even if handler returns, framework enters infinite loop).

Normally called via macros, not directly.

Convenience Macros

RTE_ASSERT(condition) // If false, SAFE with REASON_ASSERT_FAILED
RTE_SAFESTATE(level, reason) // Enter level with reason
RTE_REBOOT(reason) // Enter REBOOT with reason
#define RTE_ASSERT(cond)
Checked assertion. Always active in every build configuration, including production (ADR-004 section ...
#define RTE_SAFESTATE(level, reason)
Explicitly enters the given safe-state level with a reason code, capturing the call site automaticall...
#define RTE_REBOOT(reason)
Enters RTE_SAFESTATE_LEVEL_REBOOT with the given reason code. Equivalent to RTE_SAFESTATE(RTE_SAFESTA...

Macros automatically capture FILE and LINE.

Handler Signature

typedef void (*rte_safestate_handler_t)(
rte_safestate_level_t level, // Level being entered
rte_safestate_reason_t reason, // Application-supplied reason code
const char *file, // Source file (__FILE__), may be NULL
int32_t line, // Source line (__LINE__)
const char *message // Optional detail (e.g., assertion text)
);

Handler receives full context about what triggered the transition. For DEGRADED: can return normally. For SAFE/REBOOT: must not return; if it does, framework prevents it.

Reason Code Strategy

Framework Reasons (0-4095)

RTE_SAFESTATE_REASON_UNSPECIFIED (0)    - No specific reason
RTE_SAFESTATE_REASON_ASSERT_FAILED (1)  - RTE_ASSERT() condition false
RTE_SAFESTATE_REASON_APPLICATION_BASE   - 4096, start of app-specific codes

Application Reasons (4096+)

Applications define their own reason codes starting at 4096:

#define APP_REASON_VITAL_CHANNEL_ERROR 4096
#define APP_REASON_VOTING_MISMATCH 4097
#define APP_REASON_CHECKSUM_FAILED 4098
// ... etc

Reason codes appear in handler and handler can log/persist them for diagnostics.

Transition Flow

Application calls RTE_SAFESTATE(level, reason)
    │
    ├─ Captures __FILE__, __LINE__
    └─ Calls rte_safestate_enter(level, reason, file, line, NULL)
            │
            ├─ Retrieves registered handler for level
            ├─ If handler != NULL: invokes it with context
            └─ Handler returns (DEGRADED only) or doesn't (SAFE/REBOOT)

If DEGRADED:
    └─ Handler returns normally
    └─ rte_safestate_enter() returns to caller
    └─ Application continues execution

If SAFE or REBOOT:
    ├─ If handler returns:
    │   └─ Framework enters infinite loop (infinite while(1))
    └─ Handler never returns:
        └─ Caller never regains control

Storage and Limits

Fixed storage for 3 handlers (one per level). No dynamic allocation. Handlers must be registered before use (typically at startup).

Level 0 (DEGRADED)  ──> Handler A
Level 1 (SAFE)      ──> Handler B
Level 2 (REBOOT)    ──> Handler C

RTE_ASSERT Implementation

RTE_ASSERT() is a macro that:

  1. Evaluates its condition
  2. If true: does nothing
  3. If false: calls rte_safestate_enter(SAFE, ASSERT_FAILED, file, line, expr_text)
#define RTE_ASSERT(cond) \
do { \
if (!(cond)) \
rte_safestate_enter(RTE_SAFESTATE_LEVEL_SAFE, \
RTE_SAFESTATE_REASON_ASSERT_FAILED, \
__FILE__, (int32_t)__LINE__, #cond); \
} while (0)

Unlike standard assert(), RTE_ASSERT() is always active, never disabled. This is intentional for safety-critical systems where assertions must catch bugs in production.

MISRA C:2012 Compliance

Rule 2.1   │ ✓ │ No unreachable code
Rule 5.1   │ ✓ │ External identifiers unique
Rule 7.2   │ ✓ │ Correct signedness
Rule 8.4   │ ✓ │ Consistent declarations
Rule 14.4  │ ✓ │ Boolean operators correct
Rule 17.1  │ ✓ │ Pointer validity checked
Rule 20.6  │ ✓ │ No malloc/free

No dynamic allocation; no undefined behavior on entry/exit.

Integration Points

With Watchdog Module

Watchdog timeouts can trigger safe-state via handler:

void watchdog_fired(void) {
APP_REASON_WATCHDOG_TIMEOUT);
}
@ RTE_SAFESTATE_LEVEL_SAFE

With Reboot Module

RTE_REBOOT() enters REBOOT level; handler can call rte_reboot_request() to actually restart the system.

Unit Tests

Test 1: Handler Registration and Invocation

static int handler_called = 0;
static rte_safestate_level_t handler_level = 0;
void test_handler(rte_safestate_level_t level, ...) {
handler_called = 1;
handler_level = level;
}
void test_degraded_handler_invoked(void) {
test_handler);
assert(handler_called == 1);
assert(handler_level == RTE_SAFESTATE_LEVEL_DEGRADED);
}
@ RTE_SAFESTATE_LEVEL_DEGRADED

Test 2: RTE_ASSERT Behavior

void test_assert_on_failure(void) {
// RTE_ASSERT(false) should enter SAFE state
// Verify via mock handler that was triggered
RTE_ASSERT(false); // Never reaches here (enters SAFE)
}

See Also