Skip to main content

rivet/port/
arch.rs

1//! Group A of the port contract: the CPU port.
2//!
3//! Declares the symbols a `rivet-arch-*` crate must provide (as
4//! `#[no_mangle] extern "Rust" fn`s) — context switch, trap/exception
5//! entry, per-arch memory-protection programming, interrupt masking. The
6//! kernel calls only the safe wrappers below; nothing here reaches into
7//! any specific arch crate by name; linkage is by symbol name only; a
8//! missing implementation is a link error naming the exact symbol, not a
9//! type error.
10//!
11//! Signatures are restricted to primitives (`usize`, `u32`, `u64`, raw
12//! pointers, `!`) specifically so the `extern "Rust"` ABI — otherwise
13//! unspecified in general — is unambiguous between the crate that
14//! declares a symbol and the crate that defines it (the same convention
15//! the `critical-section` crate uses for its provider mechanism).
16
17extern "Rust" {
18    /// One-time arch bring-up: install the trap/exception vector, arm any
19    /// boot-time-static memory guards (e.g. RISC-V's locked PMP
20    /// catch-all), point the ISR stack register at its linker-provided
21    /// top. Must not touch board-specific hardware (clocks, timers,
22    /// console) — that is [`crate::port::board::init`]'s job, called
23    /// separately.
24    fn __rivet_arch_init();
25
26    /// Enter a low-power wait for the next interrupt (`wfi` or
27    /// equivalent). Called by the executor when every task is pending.
28    fn __rivet_arch_idle();
29
30    /// Request an immediate reschedule opportunity: the same trap/
31    /// exception path used by tick-driven preemption (software interrupt
32    /// on RISC-V, PendSV on Cortex-M) — there is exactly one context-
33    /// switch code path, not two. Safe to call from task or ISR context.
34    fn __rivet_arch_request_reschedule();
35
36    /// Disable interrupts, returning an opaque token that
37    /// [`__rivet_arch_irq_restore`] uses to decide whether to re-enable
38    /// them (nested critical sections must compose: an inner disable is a
39    /// no-op if interrupts were already off, and only the outermost
40    /// restore actually re-enables).
41    fn __rivet_arch_irq_save() -> usize;
42    fn __rivet_arch_irq_restore(token: usize);
43
44    /// Build the initial stack frame for a new preemptive task so that the
45    /// first context switch into it starts execution at `entry_fn(arg)`.
46    /// Returns the initial stack pointer.
47    ///
48    /// # Safety
49    /// `stack_ptr`/`stack_len` must describe a suitably aligned region at
50    /// least [`__rivet_arch_min_task_stack`] bytes long; `entry_fn` must be
51    /// a valid function pointer taking one `usize` argument and never
52    /// returning.
53    fn __rivet_arch_init_task_stack(
54        stack_ptr: *mut u8,
55        stack_len: usize,
56        entry_fn: usize,
57        arg: usize,
58    ) -> usize;
59
60    /// Transfer control to the first preemptive task. Never returns.
61    ///
62    /// # Safety
63    /// `sp` must be a stack pointer previously produced by
64    /// [`__rivet_arch_init_task_stack`].
65    fn __rivet_arch_start_first_task(sp: usize) -> !;
66
67    /// Called on every actual context switch with the newly-dispatched
68    /// task's stack range, so an arch with a reprogrammable MPU (Cortex-M)
69    /// can grant exactly that range. No-op on arches whose memory guards
70    /// are boot-time-static (RISC-V PMP).
71    fn __rivet_arch_on_switch_to(stack_base: usize, stack_size: usize);
72
73    /// Register a locked stack-overflow guard band for allocation `slot`
74    /// (RISC-V PMP; no-op on arches — Cortex-M — whose two-region MPU
75    /// design already gives full isolation without per-task entries).
76    fn __rivet_arch_guard_register(guard_base: usize, slot: usize);
77
78    /// Temporarily grant kernel access to a stack range inside an
79    /// otherwise memory-guard-denied pool (used while filling/
80    /// initializing a newly allocated stack). No-op on arches without a
81    /// whole-pool deny region.
82    fn __rivet_arch_scratch_open(base: usize, size: usize);
83    /// Close the window opened by [`__rivet_arch_scratch_open`].
84    fn __rivet_arch_scratch_close();
85
86    /// Minimum byte size a preemptive task stack must have: the
87    /// context-switch frame plus slack for the entry trampoline.
88    fn __rivet_arch_min_task_stack() -> usize;
89
90    /// Minimum guard-band size (bytes) `rivet::preempt::stack_pool` must
91    /// reserve below each stack before calling [`__rivet_arch_guard_register`]
92    /// on it. `64` on every arch with no hardware-specific minimum larger
93    /// than that (RISC-V PMP's usual `G == 0` case, and both arches with
94    /// no hardware guard mechanism at all, where the value is otherwise
95    /// unused but must still be a real power of two for the pool's own
96    /// layout math). Real RISC-V hardware can require more (plan.md
97    /// Phase 26: the ESP32-C6's PMP grain forces a larger minimum NAPOT
98    /// region than the reference platforms' `G == 0`) — reserving less
99    /// than what the guard will actually deny would deny part of the
100    /// stack itself, not just the intended dead band below it.
101    fn __rivet_arch_min_guard_size() -> usize;
102
103    /// Free-running cycle counter (plan.md Phase 10), used for
104    /// execution-time accounting and latency histograms. Not required to
105    /// start at zero, only to be monotonic (mod 2^64) and to advance at a
106    /// fixed, arch-documented rate. Implementations without a hardware
107    /// cycle counter may derive one from another monotonic source (e.g. a
108    /// SysTick-driven tick count) rather than failing — callers only ever
109    /// take deltas, so a coarser-than-ideal but still monotonic source is
110    /// still correct, just less precise.
111    fn __rivet_arch_cycle_count() -> u64;
112
113    /// Enable/disable/prioritize an external interrupt at the arch's
114    /// controller (plan.md Phase 13: NVIC on Cortex-M, PLIC on RISC-V).
115    /// The IRQ *number* is board-defined (each `rivet-bsp-*` publishes its
116    /// own map); the controller itself is architectural, so these live in
117    /// Group A, not Group B/C.
118    fn __rivet_arch_irq_enable(irq_num: u32);
119    fn __rivet_arch_irq_disable(irq_num: u32);
120    fn __rivet_arch_irq_set_priority(irq_num: u32, priority: u8);
121
122    /// This hart's index (plan.md Phase 19): `0` on every single-hart
123    /// arch/board (host, Cortex-M — QEMU's `lm3s6965evb`/`mps2-an385`
124    /// machine models are strictly single-core, confirmed empirically,
125    /// not a kernel limitation), the real `mhartid` on RISC-V. Used to
126    /// index per-hart scheduler/critical-section state; every subsystem
127    /// that was single-hart-only before this phase still behaves
128    /// identically when this always returns `0`.
129    fn __rivet_arch_hart_id() -> usize;
130
131    /// Request an immediate reschedule opportunity on a **specific**
132    /// (possibly different) hart (plan.md Phase 19). Needed because a task
133    /// becoming ready doesn't necessarily do so on the hart that should
134    /// run it next: with a global run queue, the hart that should
135    /// reconsider its schedule might be idling on a completely different
136    /// call stack (e.g. parked in `__rivet_arch_idle` with no timer tick
137    /// of its own — RISC-V `virt`'s SMP design keeps a single tick owner,
138    /// see `rivet-arch-riscv::clint`'s docs). RISC-V implements this via
139    /// CLINT's per-hart `MSIP` register. Single-hart arches (host,
140    /// Cortex-M) implement it as either a no-op or an alias for
141    /// [`__rivet_arch_request_reschedule`] when `hart == 0`, since it is
142    /// never called with any other value there.
143    fn __rivet_arch_request_reschedule_on(hart: usize);
144}
145
146pub fn init() {
147    // SAFETY: implemented by exactly one `rivet-arch-*` crate linked into
148    // the final binary; called once, before any task can run.
149    unsafe { __rivet_arch_init() }
150}
151
152pub fn idle() {
153    // SAFETY: see `init`.
154    unsafe { __rivet_arch_idle() }
155}
156
157pub fn request_reschedule() {
158    // SAFETY: see `init`.
159    unsafe { __rivet_arch_request_reschedule() }
160}
161
162/// Run `f` with interrupts disabled. Nested calls compose: an inner call
163/// observes interrupts already disabled and its restore is a no-op,
164/// leaving the outermost call to actually re-enable.
165#[inline]
166pub fn critical_section<R>(f: impl FnOnce() -> R) -> R {
167    // SAFETY: see `init`; save/restore is paired within this function.
168    let token = unsafe { __rivet_arch_irq_save() };
169    let r = f();
170    // SAFETY: `token` came from the `__rivet_arch_irq_save` call directly
171    // above, in this same function — a matched pair.
172    unsafe { __rivet_arch_irq_restore(token) };
173    r
174}
175
176/// # Safety
177/// `stack` must be suitably aligned and at least [`min_task_stack`] bytes
178/// long; `entry_fn` must be a valid function pointer taking one
179/// `usize`-sized argument and never returning.
180pub unsafe fn init_task_stack(stack: &mut [u8], entry_fn: usize, arg: usize) -> usize {
181    // SAFETY: forwarded to the arch crate under the same contract.
182    unsafe { __rivet_arch_init_task_stack(stack.as_mut_ptr(), stack.len(), entry_fn, arg) }
183}
184
185/// # Safety
186/// `sp` must be a stack pointer previously produced by [`init_task_stack`].
187pub unsafe fn start_first_task(sp: usize) -> ! {
188    // SAFETY: forwarded to the arch crate under the same contract.
189    unsafe { __rivet_arch_start_first_task(sp) }
190}
191
192pub fn on_switch_to(stack_base: usize, stack_size: usize) {
193    // SAFETY: see `init`.
194    unsafe { __rivet_arch_on_switch_to(stack_base, stack_size) }
195}
196
197pub fn guard_register(guard_base: usize, slot: usize) {
198    // SAFETY: see `init`.
199    unsafe { __rivet_arch_guard_register(guard_base, slot) }
200}
201
202pub fn scratch_open(base: usize, size: usize) {
203    // SAFETY: see `init`.
204    unsafe { __rivet_arch_scratch_open(base, size) }
205}
206
207pub fn scratch_close() {
208    // SAFETY: see `init`.
209    unsafe { __rivet_arch_scratch_close() }
210}
211
212pub fn min_task_stack() -> usize {
213    // SAFETY: see `init`.
214    unsafe { __rivet_arch_min_task_stack() }
215}
216
217pub fn min_guard_size() -> usize {
218    // SAFETY: see `init`.
219    unsafe { __rivet_arch_min_guard_size() }
220}
221
222/// Read the free-running cycle counter. See
223/// [`__rivet_arch_cycle_count`] for the monotonicity contract.
224pub fn cycle_count() -> u64 {
225    // SAFETY: see `init`.
226    unsafe { __rivet_arch_cycle_count() }
227}
228
229pub fn irq_enable(irq_num: u32) {
230    // SAFETY: see `init`.
231    unsafe { __rivet_arch_irq_enable(irq_num) }
232}
233
234pub fn irq_disable(irq_num: u32) {
235    // SAFETY: see `init`.
236    unsafe { __rivet_arch_irq_disable(irq_num) }
237}
238
239pub fn irq_set_priority(irq_num: u32, priority: u8) {
240    // SAFETY: see `init`.
241    unsafe { __rivet_arch_irq_set_priority(irq_num, priority) }
242}
243
244pub fn hart_id() -> usize {
245    // SAFETY: see `init`.
246    unsafe { __rivet_arch_hart_id() }
247}
248
249/// Ask `hart` to take a reschedule trap. See
250/// [`__rivet_arch_request_reschedule_on`].
251pub fn request_reschedule_on(hart: usize) {
252    // SAFETY: see `init`.
253    unsafe { __rivet_arch_request_reschedule_on(hart) }
254}