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
qnx-rtos-app Directory Reference
Directory dependency graph for qnx-rtos-app:

Files

 
main.c
 QNX RTOS example application demonstrating RteFramework.

Detailed Description

Railway Control Server (QNX RTOS)

A production-grade railway control server demonstrating RteFramework on QNX RTOS with deterministic real-time guarantees.

Overview

Application: Train control server with message passing
Platform: QNX Neutrino RTOS
Use case: Safety-critical railway signaling control
Purpose: Learn real-time OAL abstraction, message passing patterns, and safety-critical design

What It Does

  1. Receives train updates via QNX message passing:
    • Train position data
    • Signal state queries
    • Ping/health checks
  2. Processes with deterministic timing:
    • Validates train position bounds
    • Determines safe signal state (green/yellow/red)
    • Calculates speed limits for safe operation
  3. Sends control commands back to client:
    • Current signal state
    • Track speed limit
    • Status confirmation
  4. Logs safety-critical operations with structured logging
  5. Maintains statistics:
    • Messages received and processed
    • Error count
    • Server state

Architecture

Message-Based Communication

┌─────────────────────┐
│ Train Client │
│ (e.g., on-board │
│ computer) │
└──────────┬──────────┘
│
MsgSend
│
▼
┌──────────────────────────────┐
│ Railway Control Server │
│ (QNX Channel) │
│ │
│ receive() │
│ ↓ │
│ process_message() │
│ ↓ │
│ determine_signal_state() │
│ ↓ │
│ reply() │
└──────────┬───────────────────┘
│
MsgReply
│
▼
┌──────────────────────┐
│ Train Client │
│ (awaiting reply) │
└──────────────────────┘

Safety Model

  • Synchronous message exchange — No lost messages
  • Bounded response time — Deterministic processing
  • Position validation — Bounds checking before use
  • Signal determination — Safe defaults (red = stop)
  • Error tracking — Anomalies logged and counted

Building

Prerequisites

# Install QNX Momentics IDE or QNX SDP
export QNX_HOST=/opt/qnx7.0.0/host/linux/x86_64
export QNX_TARGET=/opt/qnx7.0.0/target/qnx7.0.0/x86_64
# Verify installation
$QNX_HOST/usr/bin/qcc --version

Quick Build

From the project root:

# Set QNX paths
export QNX_HOST=/opt/qnx7.0.0/host/linux/x86_64
export QNX_TARGET=/opt/qnx7.0.0/target/qnx7.0.0/x86_64
# Build script (easiest)
./examples/build-qnx.sh
# Or CMake preset
cmake --preset qnx
cmake --build --preset qnx

Manual Build

export QNX_HOST=/opt/qnx7.0.0/host/linux/x86_64
export QNX_TARGET=/opt/qnx7.0.0/target/qnx7.0.0/x86_64
# Build framework for QNX
mkdir -p build/qnx
cd build/qnx
cmake -S . -B . \
-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-QNX.cmake \
-DQNX_HOST=$QNX_HOST \
-DQNX_TARGET=$QNX_TARGET \
-DRTE_BUILD_TESTS=OFF
cmake --build .
cd ../..
# Build the example
cd examples/qnx-rtos-app
mkdir build && cd build
cmake -S ../../.. -B . \
-DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-QNX.cmake
cmake --build .

Deployment

On QNX Target

# 1. Create deployment directories
ssh user@qnx-target "mkdir -p /opt/rbc/bin /opt/rbc/lib /opt/rbc/include"
# 2. Deploy framework
scp build/qnx/src/*/*.a user@qnx-target:/opt/rbc/lib/
scp -r ../../include/rte user@qnx-target:/opt/rbc/include/
# 3. Deploy server binary
scp examples/qnx-rtos-app/railway-server user@qnx-target:/opt/rbc/bin/
# 4. Run on target
ssh user@qnx-target /opt/rbc/bin/railway-server

Using QNX Emulator

For development without real QNX hardware:

# Build for QNX (cross-compile)
cmake --preset qnx
cmake --build --preset qnx
# Run in emulator (if available)
# qemu-system-x86_64 -kernel qnx-kernel ...

Running

On QNX System

# Terminal 1: Start server
/opt/rbc/bin/railway-server
# Terminal 2: Send messages (see Client Example below)
./railway-client

Expected Output

═══════════════════════════════════════════════════════════════
Railway Control Server (QNX RTOS)
═══════════════════════════════════════════════════════════════
PID: 12345
[INFO] Railway Control Server starting (QNX RTOS)
[INFO] QNX channel created: 0
[INFO] Message channel ready (PID=12345, CHID=0)
[INFO] Starting QNX server loop
[DEBUG] Processing TRAIN_UPDATE message
[DEBUG] Train update: ID=1, Position=500
[INFO] Train 1: signal=1, speed_limit=80
[INFO] Stats: received=10, processed=10, errors=0
[INFO] Stats: received=20, processed=20, errors=0
[INFO] Received SHUTDOWN request
[INFO] Shutting down railway server
═══════════════════════════════════════════════════════════════
Railway Server Summary
═══════════════════════════════════════════════════════════════
Messages received: 100
Messages processed: 100
Errors: 0
═══════════════════════════════════════════════════════════════

Client Example

Simple QNX client to communicate with the server:

#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>
typedef struct {
uint32_t type;
uint32_t train_id;
uint32_t position;
uint8_t data[244];
typedef struct {
int status;
uint32_t signal_state;
uint32_t track_speed_limit;
int main() {
int coid;
cmd_reply_t reply;
printf("Connecting to railway server...\n");
// Find the server's channel (in production, use nametag lookup)
coid = ConnectAttach(ND_LOCAL_NODE, 0, 1, _NTO_SIDE_CHANNEL, 0);
if (coid == -1) {
perror("ConnectAttach");
return 1;
}
printf("Connected (coid=%d)\n", coid);
// Send train position update
msg.type = 2; // MSG_TYPE_TRAIN_UPDATE
msg.train_id = 1;
msg.position = 500;
printf("Sending train update: ID=%u, Position=%u\n",
msg.train_id, msg.position);
if (MsgSend(coid, &msg, sizeof(msg), &reply, sizeof(reply)) == -1) {
perror("MsgSend");
return 1;
}
printf("Reply received:\n");
printf(" Signal state: %u (%s)\n",
reply.signal_state,
(reply.signal_state == 0 ? "RED" :
reply.signal_state == 1 ? "GREEN" : "YELLOW"));
printf(" Speed limit: %u km/h\n", reply.track_speed_limit);
ConnectDetach(coid);
return 0;
}

Code Structure

main.c
├── QNX Configuration
│ ├── Channel and connection IDs
│ ├── Message passing structs
│ └── Message types enum
│
├── Core Functions
│ ├── qnx_init_channel() — Create message channel
│ ├── process_train_update() — Validate & process train data
│ ├── handle_message() — Dispatch message handling
│ ├── server_loop() — Main message loop
│ └── app_shutdown() — Cleanup
│
└── Message Handling
├── MSG_TYPE_PING — Health check
├── MSG_TYPE_TRAIN_UPDATE — Process position
├── MSG_TYPE_SIGNAL_QUERY — Query current state
└── MSG_TYPE_SHUTDOWN — Graceful exit

Framework Features Demonstrated

1. Status Codes

rte_status_t status = process_train_update(&msg, &reply);
reply.status = status;
rte_status_t
Common result/status codes.
Definition rte_status.h:27

2. Structured Logging

RTE_LOG_INFO("Train %u: signal=%u, speed_limit=%u",
msg.train_id, reply.signal_state, reply.track_speed_limit);

3. Safe Assertions

RTE_ASSERT(msg != NULL);
RTE_ASSERT(reply != NULL);
#define RTE_ASSERT(cond)
Checked assertion. Always active in every build configuration, including production (ADR-004 section ...

4. Fixed-Width Types

uint32_t train_id; // Not 'unsigned int'
uint32_t position; // Not 'unsigned long'
uint8_t data[244]; // Exact size, no padding

5. QNX Native Integration

int chid = ChannelCreate(0); // Create channel
int rcvid = MsgReceive(chid, ...); // Block on message
MsgReply(rcvid, EOK, &reply, ...); // Send reply

Safety Critical Features

Position Validation

if (msg->position > 10000) {
RTE_LOG_WARN("Train position out of valid range: %u", msg->position);
reply->status = RTE_STATUS_ERROR;
reply->signal_state = 0; // Red = STOP (safe default)
return RTE_STATUS_ERROR;
}

Deterministic Signal Logic

Position < 1000m → GREEN (go ahead)
Position 1000-5000m → YELLOW (caution)
Position > 5000m → RED (stop)
Out of bounds → RED (fail-safe)

Error Tracking

Every error is:

  1. Logged
  2. Counted
  3. Reported in statistics
  4. Handled with safe default

Atomic Message Handling

rcvid = MsgReceive(...); // Block atomically
handle_message(...); // Process
MsgReply(...); // Reply atomically

No race conditions; message passing is kernel-atomic.

Performance

Real-Time Guarantees (QNX)

  • Message receipt: Bounded by kernel scheduler (typically <1ms)
  • Processing: Deterministic, no dynamic allocation
  • Response: Atomic reply via kernel
  • Jitter: Minimal, predictable

Throughput

  • Messages/sec: ~50k (on typical embedded QNX)
  • Latency: <1ms 99th percentile (real-time class)
  • **Memory:** ~2 MB footprint

Comparison with Linux Example

Aspect Linux QNX
Response time Variable, ~10-100ms Deterministic, <1ms
Timing guarantee Soft real-time Hard real-time
Memory overhead Larger Smaller
Development time Fast Slower (cross-compile)
Safety guarantee Best-effort Certified

Extending the Example

Add Persistent State

Store signal states in NVM:

rte_nvm_write(NVM_OFFSET_SIGNAL_STATE, &signal_state, sizeof(signal_state));
rte_status_t rte_nvm_write(rte_nvm_handle_t handle, size_t offset, const void *buffer, size_t buffer_size)
Writes data and its integrity metadata to an NVM region.
Definition rte_nvm.c:97

Add Periodic Tasks

Combine with timer for background monitoring:

void monitor_task(void *arg) {
while (running) {
sleep(1);
RTE_LOG_INFO("Heartbeat from monitor");
}
}

Add Event Notifications

Use QNX pulse to notify clients of events:

MsgSendPulse(coid, SIGEV_PULSE, 0, TRAIN_EVENT_PULSE);

Add Multiple Trains

Extend message struct to handle train fleet:

typedef struct {
uint32_t num_trains;
train_t trains[MAX_TRAINS];
} fleet_message_t;

Testing

Unit Test

# Build with tests (if applicable)
cmake --preset debug
cmake --build --preset debug
ctest --preset debug

Integration Test

  1. Start server on QNX
  2. Send messages from client
  3. Verify correct signal states
  4. Check log output

Stress Test

# Send rapid messages
for i in {1..1000}; do
railway-client train_id=$i position=$((i % 10000))
done

Troubleshooting

"QCC compiler not found"

  • Check QNX_HOST environment variable
  • Verify QNX installation path

"ChannelCreate failed"

  • Another process already owns the channel
  • Insufficient permissions
  • QNX resource limits exceeded

"MsgReceive timeout"

  • Server crashed or not running
  • Channel was destroyed
  • Connection was broken

"Out of memory"

  • Server has memory leak
  • Buffer overflow
  • Too many queued messages

Related Examples

References