nros_platform/board/dispatch.rs
1//! Phase 216.A.1 — `DispatchStrategy` enum.
2//!
3//! Declares how a Node pkg expects its callbacks to fire. The board
4//! crate's `NodeDispatchRuntime::dispatch_strategy()` (Phase 216.A.2,
5//! lives at `super::runtime`) tells the codegen + check layers which
6//! strategies it can serve; `Node::DISPATCH` (Phase 216.A.3, lives at
7//! `nros::Node`) tells which strategy a Node requires.
8//!
9//! `nros check` (Phase 216.D.1) cross-validates Node `DISPATCH` against
10//! the Entry pkg's board framework on a `(framework, strategy)` matrix
11//! — see the doc body in `docs/roadmap/phase-216-baremetal-framework-
12//! integration.md`.
13//!
14//! `#[repr(u8)]` for FFI stability: the `nros::node!()` macro (Phase
15//! 216.A.5) emits a per-Node `__nros_node_<pkg>_dispatch_strategy()
16//! -> u8` ABI symbol so `nros check` can read the strategy without
17//! linking the Node crate.
18
19/// How a Node expects its callbacks to fire.
20#[repr(u8)]
21#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
22pub enum DispatchStrategy {
23 /// Callbacks fire from the executor's spin loop (current default).
24 /// Served by every runtime: POSIX, RTOS (FreeRTOS/NuttX/Zephyr/
25 /// ThreadX), bare-metal, RTIC (proxied via `__nros_dispatch` task
26 /// when the board demands it). The default for
27 /// every existing Node pkg — preserves backward compat.
28 Inline = 0,
29
30 /// Callbacks fire from a framework-owned task (the RTIC dispatcher). The board-side `NodeDispatchRuntime` enqueues
31 /// signaled callbacks; the framework's scheduler dequeues + drives
32 /// `ExecutableNode::on_callback` from its own task context. Needed
33 /// for Nodes whose callbacks must not run from the spin task (e.g.
34 /// callbacks that take RTIC locks or share priority with custom
35 /// tasks).
36 Deferred = 1,
37
38 /// Callbacks fire directly from an ISR handler. Design slot only —
39 /// impl deferred to Phase 216.E.1. Requires a reentrancy audit of
40 /// the dispatch path + a lock-free SPSC variant tolerant of
41 /// ISR-priority producers + a per-Node `#[isr_safe]` proof
42 /// contract. Reserved here so the matrix in Phase 216.D.1 has a
43 /// stable discriminant to reject against.
44 FromIsr = 2,
45}
46
47impl DispatchStrategy {
48 /// Compile-time default for `Node::DISPATCH`. Inline preserves
49 /// every existing Node pkg unchanged.
50 pub const DEFAULT: Self = Self::Inline;
51
52 /// FFI round-trip discriminant. Mirrors `as u8` but expressible in
53 /// `const` contexts where `as` casts on enums currently require an
54 /// `#[allow(non_upper_case_globals)]` dance.
55 #[inline]
56 pub const fn to_u8(self) -> u8 {
57 self as u8
58 }
59
60 /// Inverse of `to_u8`. Returns `None` for unknown discriminants —
61 /// `nros check` surfaces the rejection with a clear diagnostic
62 /// rather than silently treating a future strategy as `Inline`.
63 #[inline]
64 pub const fn from_u8(value: u8) -> Option<Self> {
65 match value {
66 0 => Some(Self::Inline),
67 1 => Some(Self::Deferred),
68 2 => Some(Self::FromIsr),
69 _ => None,
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn default_is_inline() {
80 assert_eq!(DispatchStrategy::DEFAULT, DispatchStrategy::Inline);
81 }
82
83 #[test]
84 fn u8_round_trip() {
85 for s in [
86 DispatchStrategy::Inline,
87 DispatchStrategy::Deferred,
88 DispatchStrategy::FromIsr,
89 ] {
90 assert_eq!(DispatchStrategy::from_u8(s.to_u8()), Some(s));
91 }
92 }
93
94 #[test]
95 fn from_u8_rejects_unknown() {
96 assert_eq!(DispatchStrategy::from_u8(3), None);
97 assert_eq!(DispatchStrategy::from_u8(255), None);
98 }
99}