- See also
- vital_channel_topologies for common redundancy patterns (2oo2, 2oo3, hot standby, mirroring)
-
ipc_guide for transport selection, configuration, and implementation
Overview
Vital Channels** provide redundancy voting for safety-critical inter-process communication. A vital channel wraps multiple underlying transport channels (IPC, shared memory, TCP, etc.) and applies voting logic to ensure data integrity across distributed processes.
The key design principle: Transport Independence. The vital_channel module does not depend on any specific IPC implementation—it uses transport-agnostic callbacks to invoke send/receive operations on the underlying channels.
Communication Flow (2oo2 Example)
In a typical railway system using 2-out-of-2 voting:
WEST Process A (Vital Compute)
|
v
[Vital Channel Layer]
| (send via callbacks)
+---> Channel 0 (IPC or shared memory to Process B)
|
+---> Channel 1 (IPC or shared memory to Process C)
WEST Process B (Vital Verify) WEST Process C (Non-Vital Service)
| |
v v
[Receive from Channel 0] [Receive from Channel 1]
|
v
[Vital Channel Voting]
|
+-- If Ch0 == Ch1 -> AGREE, return data
|
+-- If Ch0 != Ch1 -> DISAGREE, trigger SAFE-STATE
Transport OSAdapters
A vital channel operates on opaque void* channel handles. The application provides two callback functions to specify HOW to communicate:
- backend_send(): Invoked by vital_channel_send() for each redundant channel
- backend_recv(): Invoked by vital_channel_receive() for each redundant channel
These callbacks abstract away the transport mechanism:
| Transport | OSAdapter Implementation |
| IPC Request-Reply | Call rte_ipc_rr_send() / rte_ipc_rr_receive() |
| IPC Pub-Sub | Call rte_ipc_pubsub_publish() / rte_ipc_pubsub_receive() |
| Shared Memory | Direct memcpy to/from shared memory region |
| TCP | Call socket send()/recv() via wrapper |
| Custom Transport | User-defined send/recv functions |
This design enables a single vital_channel implementation to work with ANY transport.
Send Semantics (Atomic Broadcast)
When rte_channel_send() is called:
- Broadcast Phase: Invoke backend_send() for each redundant channel
- All channels receive the same data
- Ordering is deterministic (channel 0, then 1, then 2, etc.)
- Atomicity: ALL-or-NOTHING guarantee
- If ANY channel fails or times out, return error (partial send never visible)
- If ALL channels succeed, return RTE_STATUS_OK
- This prevents inconsistent system state (one receiver gets data, another doesn't)
- Health Tracking: Update per-channel send counts and error counters
Receive Semantics (Voting)
When rte_channel_receive() is called:
- Collection Phase: Invoke backend_recv() for each redundant channel
- Store results in static voting buffers (one per channel)
- Track timeouts and errors per-channel
- Voting Phase: Apply quorum logic
- 2oo2 (both must agree): Compare channels 0 and 1. If equal -> AGREED. If different -> DISAGREED.
- 2oo3 (majority wins): Find majority among 3 channels. If tie or no majority -> INSUFFICIENT_QUORUM.
- NMR (N out of M): Generalized voting using quorum_size threshold.
- Disagreement Handling
- Log disagreement (if log_disagreements=true)
- Call optional on_disagreement callback
- CRITICAL: Trigger SAFE-STATE automatically via RTE_SAFESTATE macro
- Return RTE_STATUS_HARDWARE_FAULT to caller
- Agreement Handling
- Copy agreed data to output buffer
- Return RTE_STATUS_OK
Quorum Requirements
A vital channel requires sufficient healthy channels to achieve quorum:
- 2oo2: Need >= 2 healthy channels (both must participate)
- 2oo3: Need >= 2 healthy channels (tolerate 1 fault)
- NMR: Need >= quorum_size healthy channels (tolerate M - quorum_size faults)
If quorum is lost (e.g., both channels in 2oo2 become unhealthy), all operations return RTE_STATUS_HARDWARE_FAULT with voting result = RTE_VOTING_INSUFFICIENT_QUORUM.
Health Monitoring
The vital_channel module tracks per-channel metrics:
typedef struct {
uint32_t send_count;
uint32_t send_error_count;
uint32_t receive_count;
uint32_t receive_error_count;
uint32_t disagreement_count;
bool is_healthy;
rte_status_t
Common result/status codes.
Health statistics for one vital channel.
Applications can query this via rte_channel_get_health() to:
- Detect early signs of channel degradation (rising error counters)
- Isolate faulty channels (high disagreement_count)
- Implement predictive fault detection (before safe-state is triggered)
Application Integration
Typical application flow:
void *channels[2] = { &ch0, &ch1 };
rte_status_t backend_send(
void *ch,
const void *data,
size_t size) {
}
rte_status_t backend_recv(
void *ch,
void *data,
size_t size, uint32_t timeout) {
}
.channel_timeout_ms = 1000,
.log_disagreements = true,
.backend_send = backend_send,
.backend_recv = backend_recv,
};
if (health.disagreement_count > THRESHOLD) {
}
rte_status_t rte_channel_receive(rte_channel_t *handle, void *data, size_t data_size, uint32_t timeout_ms)
Receives data on this channel via its OSAdapter receive callback.
rte_status_t rte_channel_get_health(const rte_channel_t *handle, rte_channel_health_t *out_health)
Queries this channel's health statistics.
rte_status_t rte_channel_init(rte_channel_storage_t *storage, const rte_channel_config_t *config)
Initializes one vital channel over a caller-supplied transport.
rte_status_t rte_channel_send(rte_channel_t *handle, const void *data, size_t data_size)
Sends data on this channel via its OSAdapter send callback.
rte_status_t rte_ipc_rr_server_create(rte_ipc_rr_server_t *handle_out, const rte_ipc_rr_server_config_t *config)
Create a request-reply server channel.
struct rte_ipc_rr_server_s * rte_ipc_rr_server_t
Request-Reply server channel handle.
Configuration for rte_channel_init().
Storage for one vital channel instance (opaque to caller).
Safety Properties
The vital_channel module guarantees (for CENELEC EN 50128 SIL 3/4 contexts):
- Atomicity: Sends are all-or-nothing; no partial broadcast state
- Voting Integrity: Disagreements are ALWAYS detected and escalated to safe-state
- Silent Failure Prevention: Timeouts and errors are tracked; no silent data corruption
- Deterministic: No dynamic allocation, bounded buffers, time-bounded operations
- MISRA Compliant: No recursion, no uninitialized variables, explicit casts only
Message Size Limits
The vital_channel implementation uses static voting buffers:
#define RTE_VITAL_CHANNEL_MAX_MESSAGE_SIZE 256
Messages larger than 256 bytes return RTE_STATUS_RESOURCE_EXHAUSTED. Applications needing larger messages should:
- Use chunked transfer over multiple messages
- Or reduce message size by refactoring data structures
- Do NOT use dynamic buffering (MISRA violation)
Usage Examples
2-out-of-2 Voting (Perfect Agreement Required)
Use case: Critical signal transmission where BOTH redundant processes must agree. Example: Train speed command from central interlocking to on-board computer.
2-out-of-3 Voting (Tolerate 1 Fault)
Use case: Three redundant processes; majority vote wins. Tolerate 1 Byzantine fault. Example: Odometer reading from 3 CCTV-based vision systems (different sensors may drift).
N-out-of-M Voting
Use case: Generalized voter for N processes, requires M of N to agree. Example: 5 GNSS receivers; require 3 to agree within epsilon range.
Known Limitations
- Static Message Size: 256 byte limit (MISRA-driven, no dynamic allocation)
- No Byzantine Fault Tolerance: Designed for benign failures (timeout, corruption); does not defend against malicious/Byzantine channels
- Simple Voting Only: Exact byte-for-byte comparison; no approximate/fuzzy voting
- No Automatic Channel Isolation: Health metrics are tracked but automatic isolation is left to the application/watchdog layer