Skip to main content

Crate rivet

Crate rivet 

Source
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 on preempt::PriorityMutex to avoid priority inversion.
  • Cooperative tier (#[rivet::task]): async fn tasks compiled to Future state 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 calls WFI when nothing anywhere is ready.

§Targets

  • ARM Cortex-M (M0+/M3/M4/M7/M33) — via the arch-cortex-m feature
  • RISC-V (RV32) — via the arch-riscv feature

§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 old rivet::arch::debug_print; application code should use this module (or crate::print! / crate::println!) instead of talking to the port directly.
critical
Critical section abstraction, built on the Group A port::arch interrupt-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 %busy execution-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 full format_args!-style template). Up to two {} placeholders in $msg are substituted, in order, at drain time:
print
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 xtask test 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. Call spawn_ptask! for any additional preemptive tasks after this, then call run.
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 calling run_secondary_hart. Always false (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 virt under -smp > 1 only): per-hart arch bring-up (trap vector, ISR stack slice, PMP catch-all — everything port::arch::init does, all genuinely per-hart CSR/register state) followed by preempt::start_secondary_hart. Deliberately does not repeat init’s other steps (console/board/log setup, the async-idle-task spawn) — those are global, one-time facts owned by hart 0. Call only after kernel_ready is true, from a hart other than the one that called run. 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::main for the full docs and an example. Declare the application entry point. Expands to the #[no_mangle] extern "C" fn rivet_main() -> ! that rivet-rt’s boot code (_start on RISC-V, Reset on Cortex-M) calls after bss/data init, with rivet::init inserted 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 the task module (rivet::task::TaskCell etc.) at the same path. Declare a static async task.