Skip to main content

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}