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). 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}