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}