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), Embassy (likewise). The default for
27 /// every existing Node pkg — preserves backward compat.
28 Inline = 0,
29
30 /// Callbacks fire from a framework-owned task (RTIC dispatcher /
31 /// Embassy task). The board-side `NodeDispatchRuntime` enqueues
32 /// signaled callbacks; the framework's scheduler dequeues + drives
33 /// `ExecutableNode::on_callback` from its own task context. Needed
34 /// for Nodes whose callbacks must not run from the spin task (e.g.
35 /// callbacks that take RTIC locks or share priority with custom
36 /// tasks).
37 Deferred = 1,
38
39 /// Callbacks fire directly from an ISR handler. Design slot only —
40 /// impl deferred to Phase 216.E.1. Requires a reentrancy audit of
41 /// the dispatch path + a lock-free SPSC variant tolerant of
42 /// ISR-priority producers + a per-Node `#[isr_safe]` proof
43 /// contract. Reserved here so the matrix in Phase 216.D.1 has a
44 /// stable discriminant to reject against.
45 FromIsr = 2,
46}
47
48impl DispatchStrategy {
49 /// Compile-time default for `Node::DISPATCH`. Inline preserves
50 /// every existing Node pkg unchanged.
51 pub const DEFAULT: Self = Self::Inline;
52
53 /// FFI round-trip discriminant. Mirrors `as u8` but expressible in
54 /// `const` contexts where `as` casts on enums currently require an
55 /// `#[allow(non_upper_case_globals)]` dance.
56 #[inline]
57 pub const fn to_u8(self) -> u8 {
58 self as u8
59 }
60
61 /// Inverse of `to_u8`. Returns `None` for unknown discriminants —
62 /// `nros check` surfaces the rejection with a clear diagnostic
63 /// rather than silently treating a future strategy as `Inline`.
64 #[inline]
65 pub const fn from_u8(value: u8) -> Option<Self> {
66 match value {
67 0 => Some(Self::Inline),
68 1 => Some(Self::Deferred),
69 2 => Some(Self::FromIsr),
70 _ => None,
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn default_is_inline() {
81 assert_eq!(DispatchStrategy::DEFAULT, DispatchStrategy::Inline);
82 }
83
84 #[test]
85 fn u8_round_trip() {
86 for s in [
87 DispatchStrategy::Inline,
88 DispatchStrategy::Deferred,
89 DispatchStrategy::FromIsr,
90 ] {
91 assert_eq!(DispatchStrategy::from_u8(s.to_u8()), Some(s));
92 }
93 }
94
95 #[test]
96 fn from_u8_rejects_unknown() {
97 assert_eq!(DispatchStrategy::from_u8(3), None);
98 assert_eq!(DispatchStrategy::from_u8(255), None);
99 }
100}