freertos-in-rust 0.3.0

Pure-Rust no_std FreeRTOS kernel translation with safe Rust APIs
Documentation

FreeRTOS-in-Rust

CI

FreeRTOS-in-Rust is a monolithic, no_std, pure-Rust translation of the FreeRTOS kernel. It links no FreeRTOS C code; only the small amount of architecture assembly needed for context switching remains non-Rust.

The goal of this project is to provide a "simple", but fully functional, RTOS in Rust for embedded systems. Embedded developers should be able to follow standard FreeRTOS design patterns with 100% of the supported feature surface available, while also gaining Rust syntax sugar.

The 0.3.x line combines two interfaces in one crate:

  • freertos_in_rust::kernel is the close, C-shaped translation. It preserves FreeRTOS names, handles, storage rules, and control flow, and marks contracts that Rust cannot verify as unsafe.
  • freertos_in_rust::sync is the safe Rust veneer. It adds owned handles, typed queues, stable static-storage borrows, checked parameters, and an explicit task-context capability without redesigning the kernel.

See RUST_WRAPPER.md for details on the veneer API and its safety model. The crate remains under active development, so 0.3.x APIs may still evolve.

Translation baseline

This is a source translation and derivative of the MIT-licensed FreeRTOS kernel, not a wrapper or a clean-room reimplementation. The translation target is pinned to upstream commit 3ace38969bd05558918f815ae75528cb656c64b0 (git describe: V10.4.3-744-g3ace38969). Upstream notices and amendment comments are retained in the Rust sources. The reference FreeRTOS-Kernel/ tree is excluded from crate packages and is not compiled.

Version 0.3.x is single-core. Separate SMP work remains experimental on the smp branch. Its early boot scaffolding has run in QEMU, but it has not passed the two-core scheduler gate and is not part of the 0.3.0 crate API or the qualification below.

Feature support

The supported single-core FreeRTOS feature surface is implemented. The concise table below is backed by the symbol-by-symbol source audit in PARITY.md; the later qualification table distinguishes compilation coverage from code actually exercised in QEMU or on hardware.

Feature Status Notes
Task management
xTaskCreate (dynamic) Requires alloc, heap-4, or heap-5
xTaskCreateStatic No allocator required
vTaskDelete Requires task-delete
vTaskDelay / xTaskDelayUntil
vTaskSuspend / vTaskResume Requires task-suspend
vTaskPrioritySet / priority queries Mutation requires task-priority-set
xTaskAbortDelay Requires abort-delay
eTaskGetState
Indexed task notifications Task and FromISR variants
Thread-local storage Requires thread-local-storage
Application task tags Requires application-task-tag
Stack high-water marks Requires stack-high-water-mark
Stack-overflow checking Requires stack-overflow-check and an application hook
vTaskGetInfo / uxTaskGetSystemState Requires trace-facility
Task-list and run-time formatting Requires stats-formatting
Queues and semaphores
Dynamic and static queues Typed safe queues are also available
Send / receive / peek Front, back, overwrite, blocking, and FromISR variants
Binary and counting semaphores Dynamic and static creation
Mutexes and recursive mutexes Requires use-mutexes
Priority inheritance and disinheritance Including timeout paths
Queue sets Requires queue-sets
Queue registry Requires queue-registry
Queue trace metadata Requires trace-facility
Software timers
Timer daemon task Requires timers
Dynamic and static timers One-shot and auto-reload
Start / stop / reset / period changes Task and FromISR command variants
Pended function calls Requires pend-function-call
Event groups
Create / wait / set / clear / sync Dynamic and static creation
Deferred FromISR set and clear Uses the timer-daemon command queue
Stream and message buffers
Stream buffers Requires stream-buffers
Message buffers Message-boundary semantics preserved
Static and dynamic construction Includes callback variants
Memory management
Static-only operation No allocator feature required
Rust global-allocator bridge alloc feature
heap_4 First-fit allocator with coalescing
heap_5 Multiple non-contiguous regions
heap_1, heap_2, heap_3 Intentionally omitted; use static allocation, heap-4/heap-5, or a Rust allocator
Diagnostics and scheduling options
Tickless idle QEMU-tested on every registered single-core port
Run-time statistics Requires generate-run-time-stats
Idle yielding Requires idle-yield
List integrity check bytes Requires list-data-integrity-check
User-replaceable trace macros ⚠️ Trace metadata exists; macro hooks currently use their default no-op behavior
Ports
Cortex-M0/M0+ QEMU-tested and run on LP-MSPM0G5187 hardware
Cortex-M3 QEMU mps2-an385
Cortex-M4F QEMU mps2-an386, including FPU context
Cortex-M7 QEMU mps2-an500, including FPU context
RISC-V RV32IMAC QEMU sifive_e
Cortex-A9 QEMU vexpress-a9
Cortex-A53 / AArch64 QEMU virt
Deliberate scope boundaries
SMP / multi-core scheduler ⚠️ Experimental smp branch; two-core scheduler gate does not yet pass
MPU ports Out of scope for the current translation
Co-routines Deprecated upstream and intentionally omitted

Legend: ✅ implemented · ⚠️ partial/experimental · ➖ deliberately out of scope

Configure a target

The default feature is only port-dummy, a compile-only scaffold that cannot start a scheduler. Every production dependency must disable defaults and select exactly one hardware port:

[dependencies]
freertos-in-rust = { version = "0.3", default-features = false, features = [
    "port-cortex-m0",
    "heap-4",
    "user-config",
    "task-delete",
    "task-suspend",
    "use-mutexes",
    "timers",
    "stream-buffers",
] }

The port features are:

  • port-cortex-m0, port-cortex-m3, port-cortex-m4f, port-cortex-m7
  • port-riscv32
  • port-cortex-a9, port-cortex-a53

Port features are mutually exclusive and automatically select both the native integer width and tick width. Applications should not add arch-* or tick-* features themselves.

All 32-bit ports use u32 TickType_t. Cortex-A53 uses 64-bit architecture types and u64 TickType_t. This policy avoids the unsupported 16-bit tick combinations and lets event-group reserved bits and timer arithmetic follow the selected tick type consistently.

Optional kernel capabilities map the corresponding FreeRTOS configuration switches. The available names are defined in Cargo.toml, including task-delete, task-suspend, abort-delay, task-priority-set, use-mutexes, timers, stream-buffers, queue-sets, queue-registry, pend-function-call, thread-local-storage, tickless-idle, and generate-run-time-stats. all-kernel-features is a test aggregate; it does not select a port, width, allocator, or SMP mode.

Hardware configuration

Enable user-config for production hardware and define its clock symbols in the final application:

#[no_mangle]
pub static FREERTOS_CONFIG_CPU_CLOCK_HZ: u32 = 32_000_000;

// Required as well for port-riscv32.
#[no_mangle]
pub static FREERTOS_CONFIG_MTIME_HZ: u32 = 10_000_000;

Without user-config, the crate uses the repository's test-oriented defaults: 80 MHz for the CPU clock and, on RV32, 32.768 kHz for MTIME. Those values must not be assumed correct for a board. Other numeric FreeRTOS configuration values currently live in src/config.rs, including a 1 kHz kernel tick.

Raw kernel and safe veneer

The raw layer is useful for direct FreeRTOS ports and for comparing behavior with the C kernel:

use freertos_in_rust::kernel::tasks::{vTaskDelay, xTaskGetTickCount};

let before = xTaskGetTickCount();
// SAFETY: this runs in a live task with the scheduler running and unsuspended.
unsafe { vTaskDelay(10) };

Safe task-only operations require &TaskContext. Constructing the capability is the explicit assertion that execution is in a task, or in exclusive startup before interrupts and the scheduler can race it:

use freertos_in_rust::sync::{Queue, TaskContext};

// SAFETY: single-threaded startup; kernel interrupts are still masked.
let context = unsafe { TaskContext::assume() };
let queue = Queue::<u32>::new(&context, 8).expect("queue allocation failed");
assert!(queue.send(&context, &42));

freertos_in_rust::start_scheduler(context)

Create a fresh TaskContext at each task entry. It is neither Send nor Sync. ISR APIs remain a separate, mostly unsafe boundary because Rust types cannot prove interrupt priority, wake flags, or platform dispatch contracts.

Allocation policy

Select at most one allocator feature:

Selection Behavior Application responsibility
none Static-only kernel objects Use the *Static APIs and initialized Static*_t::new() storage. Dynamic allocation support is disabled.
heap-4 Ported first-fit/coalescing allocator over one fixed region Size is currently configTOTAL_HEAP_SIZE; install memory::FreeRtosAllocator as the Rust global allocator if Rust allocations should use it too.
heap-5 Ported allocator over multiple non-contiguous regions Call vPortDefineHeapRegions before any allocation; regions must remain valid and correctly ordered.
alloc pvPortMalloc/vPortFree wrap Rust's allocator The application must install and initialize a suitable #[global_allocator].

For heap-4 or heap-5, the provided adapter can back Rust allocation:

#[global_allocator]
static ALLOCATOR: freertos_in_rust::memory::FreeRtosAllocator =
    freertos_in_rust::memory::FreeRtosAllocator;

Static veneer constructors borrow their stack, data, and control storage for the complete object lifetime. Do not form references to uninitialized control blocks; initialize them with their const fn new() constructors.

Port qualification

“Build” and “QEMU” below are deliberately separate. A target check proves that a configuration cross-compiles; a bounded QEMU guest additionally exercises the stated runtime behavior. Neither is a substitute for qualification on a particular board, clock tree, interrupt controller, and compiler profile.

Port / Rust target Qualification in the 0.3 repair Runtime evidence actually run Important limits
port-dummy / host Build-only None; scheduler startup intentionally fails Development and documentation scaffold only.
Cortex-M0/M0+ / thumbv6m-none-eabi Target cargo check and Clippy with heap-4 plus all kernel capabilities; QEMU- and hardware-tested microbit: smoke 3/3, kernel-core 5/5, ISR 4/4, allocator 4/4, stream/event/timer 6/6, port/context 7/7, tickless 3/3; LP-MSPM0G5187 dynamic task/queue/event-group regression QEMU does not qualify a particular board clock tree or peripheral setup. The linked hardware regression records the exact board and flash flow.
Cortex-M3 / thumbv7m-none-eabi Target check and Clippy with heap-4 plus all capabilities; QEMU-tested mps2-an385: smoke 3/3, kernel-core 5/5, ISR 4/4, allocator 4/4, stream/event/timer 6/6, port/context 7/7, tickless 3/3 Runtime evidence is for QEMU's MPS2 model, not a specific production MCU.
Cortex-M4F / thumbv7em-none-eabihf Target check and Clippy with heap-4 plus all capabilities; QEMU-tested mps2-an386: smoke 3/3, kernel-core 5/5, ISR 4/4, allocator 4/4, stream/event/timer 6/6, safe API 10/10, port/FPU context 8/8, tickless 3/3; static-only 8/8 The static-only test uses a separate no-allocator firmware. Runtime evidence is for QEMU's MPS2 model.
Cortex-M7 / thumbv7em-none-eabihf Target check and Clippy with heap-4 plus all capabilities; QEMU-tested mps2-an500: smoke 3/3, kernel-core 5/5, ISR 4/4, allocator 4/4, stream/event/timer 6/6, port/FPU context 8/8, tickless 3/3 ARM erratum 837070 handling for affected early M7 revisions is not implemented.
RV32IMAC / riscv32imac-unknown-none-elf Minimal and maximal target checks; QEMU-tested sifive_e composite smoke: 10/10, covering traps/ISR yield, PLIC dispatch, MTIME rollover/fractional cadence, and tickless accounting Single-hart, machine mode, and SiFive E only. The application callback must claim and complete external interrupt-controller sources.
Cortex-A9 / armv7a-none-eabi Focused target check with tickless/stats; QEMU-tested vexpress-a9 smoke: 7/7, including a non-tick early wake, repeated suppressed sleeps, periodic reload restoration, and WFI runtime accounting The port assumes the vexpress-style MPCore private-peripheral/GIC mapping and SP804 integration. Validate addresses and clocks on real hardware.
Cortex-A53 / aarch64-unknown-none Focused target check with tickless/stats; QEMU-tested virt: smoke 3/3, port/context 10/10, tickless 4/4, event/timer 3/3 Runtime stats read CNTPCT_EL0; platform firmware or a higher exception level must permit EL1 access.

The recovered LP-MSPM0G5187 application is preserved under hardware-tests/mspm0g5187 and is cross-compiled in CI. The 0.3 repair was programmed, byte-for-byte verified, and run through the board's onboard XDS110 with TI UniFlash 9.4. Its four-task RGB pattern exercised dynamic allocation, queue blocking, timed delays, and an event-group rendezvous on the MSPM0G5187.

Current QEMU qualification covers tickless idle on every registered single-core port. Each Cortex-M tickless guest suppresses 200 ticks, wakes at tick 180, and observes two SysTick interrupts across the test.

Test driver

Use the repository test driver instead of assembling ad-hoc QEMU command lines:

# Deterministic host tests in debug and release modes.
cargo run --manifest-path xtask/Cargo.toml -- test host

# Required feature/allocator/port matrix, docs, and expected-failure checks.
cargo run --manifest-path xtask/Cargo.toml -- check-features

# One bounded guest.
cargo run --manifest-path xtask/Cargo.toml -- \
  qemu-test --arch cortex-a53 --suite tickless

# The registered suites for one architecture.
cargo run --manifest-path xtask/Cargo.toml -- \
  qemu-test --arch cortex-a53 --suite all

# The smoke suite on every registered single-core architecture.
cargo run --manifest-path xtask/Cargo.toml -- \
  qemu-test --arch all --suite smoke

Each firmware run has a 25-second default timeout and writes a diagnostic log under target/xtask/qemu-logs/. Override the bound with FREERUSTOS_QEMU_TIMEOUT_SECS when necessary.

Install the Rust targets used by the ports before running the complete matrix:

rustup target add \
  thumbv6m-none-eabi \
  thumbv7m-none-eabi \
  thumbv7em-none-eabihf \
  riscv32imac-unknown-none-elf \
  armv7a-none-eabi \
  aarch64-unknown-none

The QEMU matrix requires qemu-system-arm, qemu-system-aarch64, and qemu-system-riscv32. The demo/ applications remain useful for manual inspection, but passing a demo build is not counted as runtime qualification in the table above.

Repository layout

src/
├── kernel/     # C-shaped translations of tasks, queues, lists, timers, etc.
├── sync/       # Checked Rust veneer and ownership/context types
├── port/       # Cortex-M, Cortex-A, RV32, test, and dummy ports
├── memory/     # Static stubs, heap_4, heap_5, and global-allocator bridge
├── config.rs   # FreeRTOSConfig.h equivalent
├── types.rs    # Port-selected FreeRTOS scalar and handle types
└── lib.rs
tests/qemu/     # Bounded bare-metal QEMU firmware suites
xtask/          # Host, feature-matrix, and QEMU test driver

License

MIT, matching the upstream FreeRTOS kernel. See LICENSE.