Expand description
Rivet RTOS — zero-allocation, dual-tier RTOS for microcontrollers.
§Architecture
Two tiers of concurrency, unified under one priority scheduler:
- Preemptive tier (
#[rivet::ptask]/spawn_ptask!): each task gets its own stack. The timer tick can suspend a running task at any point (not just at a yield/await) and resume a higher-priority one instead — real priority preemption, with priority inheritance onpreempt::PriorityMutexto avoid priority inversion. - Cooperative tier (
#[rivet::task]):async fntasks compiled toFuturestate machines, polled on a single shared stack — zero per-task stack cost, ideal for I/O-bound/event-driven logic. This tier runs as an ordinary preemptive task at the lowest priority, so any real preemptive task immediately preempts it; it fills otherwise idle CPU time and only callsWFIwhen nothing anywhere is ready.
§Targets
- ARM Cortex-M (M0+/M3/M4/M7/M33) — via the
arch-cortex-mfeature - RISC-V (RV32) — via the
arch-riscvfeature
§Example
ⓘ
use rivet::sync::Semaphore;
static SEM: Semaphore<1> = Semaphore::new(0);
// Cooperative: fine for I/O-bound logic.
#[rivet::task(priority = 0)]
async fn background() {
loop {
SEM.acquire().await;
}
}
// Preemptive: genuinely can't be starved by a lower/equal priority
// task that never yields.
static CFG: u32 = 42;
fn critical_task(cfg: &'static u32) -> ! {
loop {
// real work, no .await required anywhere
}
}
fn main() -> ! {
rivet::init();
rivet::spawn_ptask!(stack = 2048, priority = 5, entry = critical_task, arg = CFG);
rivet::run();
}Re-exports§
pub use report::report;
Modules§
- config
- Compile-time kernel configuration (plan.md §4.1).
- console
- Debug console — the board’s UART/semihosting/whatever, reached through
crate::port::board. Replaces the oldrivet::arch::debug_print; application code should use this module (orcrate::print!/crate::println!) instead of talking to the port directly. - critical
- Critical section abstraction, built on the Group A
port::archinterrupt-mask primitives (local, per-hart) plus a genuine cross-hart spinlock (plan.md Phase 19). - deadlines
- Periods, drift-corrected periodic wake, and CPU-budget enforcement (plan.md Phase 11).
- exec_
time - Per-task execution-time accounting (plan.md Phase 10).
- executor
- Priority-aware async executor with single-stack task polling.
- fault
- Fault policy (plan.md §3.4).
- irq
- IRQ dispatch (plan.md Phase 13).
- latency
- Latency histograms (plan.md Phase 12).
- log
- Deferred-formatting logging:
log!is safe to call from ISR context (it does no formatting, no allocation, and never blocks — it just pushes a{level, task_id, timestamp, message}frame into a ring buffer), and a drain task formats and writes frames to the console at its own pace, off the hot path. - port
- The RTOS/board port contract.
- preempt
- Preemptive tier: tasks with dedicated stacks, real priority preemption.
- report
rivet::report()— a single call that dumps kernel-wide state to the console: every live task’s priority (base/effective), state, stack watermark, and%busyexecution-time share, plus registry-wide timer/task slot usage.- sync
- Async synchronization primitives.
- task
- Task types, generic future storage, and the task registry.
- time
- Static timing: const-generic durations and async sleep futures.
- timer
- Fixed-size timer queue backing
crate::time::Sleep. - trace
- Rivet Debugger wire protocol encoder — emits the binary trace frames
rivet-debugger-app(a separate, sibling project — see its own SRS/ PLAN at../rivet-debugger) decodes live over a UART. - waker
- Zero-allocation waker using atomic priority bitmaps.
- watchdog
- Watchdog policy — arch/board-independent.
Macros§
- log
- Log a message at the given level. ISR-safe: pushing a frame does no
formatting and never blocks (see the module docs for why arguments are
a small closed set —
u32/i32/f32/&'static str— rather than a fullformat_args!-style template). Up to two{}placeholders in$msgare substituted, in order, at drain time: - Write formatted text to the debug console. See [
println!] for a version that appends a newline. - println
- Write formatted text to the debug console, followed by a newline.
- register_
task - Convenience macro to declare a task registration by hand (used
internally by
#[rivet::task], and available for advanced manual use). - spawn_
ptask - Declare and spawn a preemptive task in one step.
Constants§
- VERSION
- Crate version.
Functions§
- exit_
failure - Terminate with a distinguishable non-zero failure code. Never returns.
- exit_
success - Terminate successfully. Never returns. Under QEMU this reduces to the
board’s exit device / semihosting path (the
xtasktest harness asserts on the resulting exit code); on real hardware, boards typically map this to a reset or halt. - init
- Initialize the kernel: set up the arch layer, discover
#[rivet::task](cooperative) tasks, and spawn the async executor as the lowest-priority preemptive task. Callspawn_ptask!for any additional preemptive tasks after this, then callrun. - kernel_
ready - Whether hart 0 has finished boot and is running the scheduler (plan.md
Phase 19).
rivet-rt’s secondary-hart bring-up spins on this before callingrun_secondary_hart. Alwaysfalse(and irrelevant) on a single-hart board — nothing there ever calls the secondary-hart path. - run
- run_
secondary_ hart - Bring up the preemptive scheduler on a secondary hart (plan.md
Phase 19, RISC-V
virtunder-smp > 1only): per-hart arch bring-up (trap vector, ISR stack slice, PMP catch-all — everythingport::arch::initdoes, all genuinely per-hart CSR/register state) followed bypreempt::start_secondary_hart. Deliberately does not repeatinit’s other steps (console/board/log setup, the async-idle-task spawn) — those are global, one-time facts owned by hart 0. Call only afterkernel_readyis true, from a hart other than the one that calledrun. Never returns. - system_
reset - Trigger a system reset (watchdog / fault-policy recovery). Never returns.
- yield_
now - Voluntarily give up the CPU: request an immediate reschedule opportunity, same as a mutex unlock waking a higher-priority waiter. Safe to call from task or ISR context.
Attribute Macros§
- main
- Declare the application entry point. See
rivet_macros::mainfor the full docs and an example. Declare the application entry point. Expands to the#[no_mangle] extern "C" fn rivet_main() -> !thatrivet-rt’s boot code (_starton RISC-V,Reseton Cortex-M) calls after bss/data init, withrivet::initinserted automatically before the function body runs. - task
- Declare a static async (cooperative-tier) task. See the [
task] module docs and the crate-level example. Lives in the macro namespace, so it coexists with thetaskmodule (rivet::task::TaskCelletc.) at the same path. Declare a static async task.