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 - User Guide

What is Safe-State?

Safe-State provides fail-safe transitions for safety-critical systems. When any component detects a critical condition, it can transition the system to one of three levels:

  • DEGRADED: Reduced functionality but still operating
  • SAFE: Critical functions disabled, fail-safe state active
  • REBOOT: Controlled restart required

Key principle: If correctness cannot be guaranteed, enter fail-safe mode.

Safe-State Levels

The module defines three severity levels (in order of escalation):

DEGRADED (Level 0)

Reduced capability but system can continue operating. Example: Redundant channel failed, but primary still working. Handler CAN return to caller (system continues execution).

SAFE (Level 1)

Fail-safe state: critical functions disabled, system in safe configuration. Example: Both channels failed, both must be considered unavailable. Handler MUST NOT return; if it does, framework enters infinite loop.

REBOOT (Level 2)

Emergency restart required. Example: Critical error detected, recovery only via fresh start. Handler MUST NOT return; if it does, framework enters infinite loop.

Quick Start

1. Register Handler at Startup

void my_safe_state_handler(rte_safestate_level_t level,
const char *file,
int32_t line,
const char *message) {
// Log the event
printf("Safe-state [%d]: reason=%u at %s:%ld msg=%s\n",
level, reason, file, line, message);
// Can return - system continues
activate_fallback_mode();
return;
}
// For SAFE and REBOOT: do not return
// Perform final shutdown, then let framework handle it
close_all_connections();
flush_logs();
// Handler does not return
}
// At application startup:
my_safe_state_handler);
my_safe_state_handler);
my_safe_state_handler);
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...
uint16_t rte_safestate_reason_t
Diagnostic reason code accompanying a safe-state transition.
@ RTE_SAFESTATE_LEVEL_REBOOT
@ RTE_SAFESTATE_LEVEL_DEGRADED
@ RTE_SAFESTATE_LEVEL_SAFE

2. Use RTE_ASSERT for Checked Assertions

Replace standard assert() with RTE_ASSERT() - always active, even in production.

RTE_ASSERT(buffer != NULL); // Always checked
RTE_ASSERT(count > 0 && count < 256); // Condition must be true
#define RTE_ASSERT(cond)
Checked assertion. Always active in every build configuration, including production (ADR-004 section ...

On failure: enters SAFE state with RTE_SAFESTATE_REASON_ASSERT_FAILED.

3. Trigger Safe-State Explicitly

// Critical channel failed
if (vital_channel_status == CHANNEL_ERROR) {
CUSTOM_REASON_VITAL_CHANNEL_FAILED);
}
// Need restart
if (recovery_failed) {
RTE_REBOOT(CUSTOM_REASON_RECOVERY_IMPOSSIBLE);
// Does not return
}
#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...

Practical Examples

Example 1: Redundant Channel Voting Failure

rte_status_t process_vital_command(const vital_msg_t *msg) {
// Both channels must deliver identical message
// If either fails or values differ -> critical failure
if (msg->checksum_mismatch) {
log_error("Vital message checksum failed");
CUSTOM_REASON_CHECKSUM_MISMATCH);
}
if (!msg->channel_a_valid || !msg->channel_b_valid) {
log_error("Channel unavailable: a=%d b=%d",
msg->channel_a_valid, msg->channel_b_valid);
CUSTOM_REASON_CHANNEL_UNAVAILABLE);
}
// Both channels valid and checksums match - safe to proceed
execute_command(msg);
return RTE_STATUS_OK;
}
rte_status_t
Common result/status codes.
Definition rte_status.h:27
@ RTE_STATUS_OK
Definition rte_status.h:28
@ RTE_STATUS_INVALID_PARAM
Definition rte_status.h:29

Example 2: Graceful Degradation

void check_system_health(void) {
rte_system_health_t health;
get_system_health(&health);
uint32_t failures = 0;
if (!health.primary_ok) failures++;
if (!health.backup_ok) failures++;
if (!health.tertiary_ok) failures++;
if (failures == 0) {
// All systems OK - normal operation
CUSTOM_REASON_RECOVERED);
} else if (failures == 1) {
// One system down - degraded but operational
log_warning("System degraded: %u failures", failures);
CUSTOM_REASON_REDUCED_REDUNDANCY);
} else if (failures >= 2) {
// Multiple failures - must enter safe state
log_critical("System failed: %u failures", failures);
CUSTOM_REASON_INSUFFICIENT_REDUNDANCY);
}
}

Example 3: Assertion-Based Fault Detection

void process_train_command(const train_cmd_t *cmd) {
// These assertions are ALWAYS active, even in production builds
RTE_ASSERT(cmd != NULL);
RTE_ASSERT(cmd->speed <= MAX_TRAIN_SPEED);
RTE_ASSERT(cmd->brake_level <= MAX_BRAKE);
// If any assertion fails -> SAFE state with REASON_ASSERT_FAILED
// No exception, no recovery - just fail-safe
execute_train_command(cmd);
}

Common Patterns

Pattern 1: Health Monitoring Loop

Periodically check system health and transition accordingly:

void health_check_loop(void) {
while (1) {
rte_system_status_t status = get_system_status();
if (status.all_healthy) {
// Try to recover to normal if previously degraded
if (current_level != RTE_SAFESTATE_LEVEL_DEGRADED) {
}
} else if (status.critical_fault) {
// Critical system fault - enter SAFE
CUSTOM_REASON_CRITICAL_FAULT);
}
sleep_ms(100); // Check every 100ms
}
}

Pattern 2: Fail-Safe-by-Default

Assume unsafe unless proven otherwise:

// BAD: Assumes safe unless proven unsafe
rte_status_t rc = critical_operation();
if (rc == RTE_STATUS_OK) {
proceed_with_operation();
}
// What if rc indicates error? Continues anyway!
// GOOD: Assumes unsafe, must prove safe
rte_status_t rc = critical_operation();
if (rc != RTE_STATUS_OK) {
return;
}
proceed_with_operation(); // Proven safe

Application-Specific Reason Codes

Framework reserves reason codes 0-4095. Applications use 4096 and above:

#define CUSTOM_REASON_VITAL_CHANNEL_FAILED 4096
#define CUSTOM_REASON_CHECKSUM_MISMATCH 4097
#define CUSTOM_REASON_REDUCED_REDUNDANCY 4098
#define CUSTOM_REASON_INSUFFICIENT_REDUNDANCY 4099
#define CUSTOM_REASON_CRITICAL_FAULT 4100
#define CUSTOM_REASON_RECOVERY_IMPOSSIBLE 4101
#define CUSTOM_REASON_CHANNEL_UNAVAILABLE 4102
#define CUSTOM_REASON_RECOVERED 4103

Best Practices

  1. Register all three handlers at startup
    • Even if not all levels used, register handlers for each
    • Prevents undefined behavior if level is entered without handler
  2. Handlers should not block
    • Keep handler execution time minimal
    • Log events, close resources quickly, then exit
    • Framework guarantees no further code execution after SAFE/REBOOT
  3. Use meaningful reason codes
    • Not just UNSPECIFIED
    • Helps with diagnostics and post-incident analysis
    • Reserve ranges: framework 0-4095, application 4096+
  4. RTE_ASSERT for programmer errors
    • Use for assumptions that should always be true
    • Example: pointer not NULL, array index in range
    • Failure = bug in code, must stop immediately
  5. RTE_SAFESTATE for runtime faults
    • Use for conditions that can legitimately fail at runtime
    • Example: communication failure, sensor error
    • Failure = system state invalid, must enter safe mode

See Also