Skip to main content

nros_platform/board/
rtic_entry.rs

1//! [`RticBoardEntry`] — Phase 216.B.1.
2//!
3//! Sibling to [`super::BoardEntry`] for **framework-owned-spin**
4//! boards. Where `BoardEntry::run` owns the boot lifecycle and
5//! drives the executor itself, `RticBoardEntry` hands the runtime
6//! over to RTIC: the `nros::main!()` proc-macro (216.B.3) generates a
7//! `#[rtic::app]` module that calls [`RticBoardEntry::init_hardware`]
8//! from inside the framework-generated `#[init]` body, stashes the
9//! returned `(Executor, Runtime)` pair in `#[local]` storage, and
10//! lets RTIC's interrupt-driven scheduler drive dispatch.
11//!
12//! ```ignore
13//! impl RticBoardEntry for RticStm32F4 {
14//!     type Pac = stm32f4xx_hal::pac::Peripherals;
15//!     type Core = cortex_m::Peripherals;
16//!     type Executor = nros::Executor;
17//!     type Runtime = RticRuntime;
18//!
19//!     const DISPATCHERS: &'static [&'static str] = &["USART1", "USART2"];
20//!
21//!     fn init_hardware(
22//!         device: Self::Pac,
23//!         core: Self::Core,
24//!     ) -> (Self::Executor, Self::Runtime) {
25//!         // … clock / pin / transport bringup, build Executor + Runtime …
26//!     }
27//! }
28//! ```
29//!
30//! ## Layering note
31//!
32//! `nros-platform` sits **below** `nros` in the dep graph (`nros`
33//! depends on `nros-platform`, not the other way around). That
34//! forces two abstractions here:
35//!
36//! 1. [`RticBoardEntry::Executor`] is an opaque assoc type — concrete
37//!    board impls plug in `nros::Executor`, but the trait surface
38//!    cannot name it without inverting the dep graph.
39//! 2. [`RticBoardEntry::Core`] is an opaque assoc type for the same
40//!    reason against `cortex_m`. Every Cortex-M chip board will
41//!    pick `cortex_m::Peripherals`, but pulling `cortex_m` into
42//!    `nros-platform` would force the dep on every consumer
43//!    (POSIX, Zephyr, FreeRTOS, …) that has no use for it.
44
45use super::{Board, DeployOverlay, runtime::NodeDispatchRuntime};
46
47/// Board-side hook for RTIC integration. The `nros::main!()`
48/// proc-macro (216.B.3) generates a `#[rtic::app]` module that calls
49/// [`Self::init_hardware`] from inside the framework-generated
50/// `#[init]` body and wires the returned pair into RTIC `#[local]`
51/// storage.
52///
53/// Distinct from [`super::BoardEntry`] (board-owns-spin) and
54/// [planned] `EmbassyBoardEntry` (216.C.1, executor-owns-spin via
55/// `embassy_executor::Spawner`).
56pub trait RticBoardEntry: Board {
57    /// Chip Peripheral Access Crate handle (e.g.
58    /// `stm32f4xx_hal::pac::Peripherals`). Whatever the RTIC
59    /// `#[rtic::app(device = …)]` attribute expects as the `device`
60    /// peripheral struct.
61    type Pac: 'static;
62
63    /// Core peripheral handle. Typically `cortex_m::Peripherals` on
64    /// Cortex-M chips but kept abstract so `nros-platform` doesn't
65    /// take a transitive `cortex_m` dep that every POSIX / Zephyr /
66    /// RTOS consumer would inherit.
67    type Core: 'static;
68
69    /// Executor type the board hands back. Concrete board impls plug
70    /// in `nros::Executor`; the assoc type keeps the layering clean
71    /// (`nros-platform` does not depend on `nros`). The proc-macro
72    /// stashes this value in RTIC `#[local]` storage.
73    type Executor: 'static;
74
75    /// Dispatch sink the proc-macro wires into RTIC `#[local]`
76    /// storage. Required to implement
77    /// [`NodeDispatchRuntime`] so signaled callbacks queued from
78    /// RTIC tasks reach the registered Node pkgs.
79    ///
80    /// Per Phase 216.A.2, `NodeDispatchRuntime` already carries
81    /// `signal_callback` + `dispatch_strategy`; the RTIC runtime
82    /// impl uses `DispatchStrategy::Deferred` and routes signals
83    /// through a `heapless::spsc::Producer` into an RTIC software
84    /// task (see Phase 216.B.2).
85    type Runtime: NodeDispatchRuntime + 'static;
86
87    /// RTIC `dispatchers = [...]` list, declared at the board layer
88    /// so each chip pins its own interrupt slots (e.g. `&["USART1",
89    /// "USART2"]`). The proc-macro splices this into the generated
90    /// `#[rtic::app(dispatchers = …)]` attribute.
91    const DISPATCHERS: &'static [&'static str];
92
93    /// Run from inside the proc-macro-generated `#[init]` body.
94    /// Returns the `(Executor, Runtime)` pair the macro stashes in
95    /// RTIC `#[local]` storage. The board impl owns clock / pin /
96    /// transport bringup before constructing the executor + runtime
97    /// pair.
98    fn init_hardware(device: Self::Pac, core: Self::Core) -> (Self::Executor, Self::Runtime);
99
100    /// Like [`init_hardware`](Self::init_hardware) but applies a deploy-metadata
101    /// overlay (Phase 244.D1) to the board's compiled-in net/locator `Config`
102    /// before opening the executor. `nros::main!()` calls THIS from the
103    /// generated `#[init]` body, passing the
104    /// `[package.metadata.nros.deploy.<board>]` block.
105    ///
106    /// The default ignores `deploy` and forwards to
107    /// [`init_hardware`](Self::init_hardware), so existing RTIC boards are
108    /// unchanged. Boards with a baked net `Config` (the bare-metal firmware
109    /// boards) override it so each Entry pkg can pin its own ip / locator /
110    /// gateway — required when two RTIC firmwares share one board on the same
111    /// QEMU network (e.g. the talker-rtic / listener-rtic pub/sub pair).
112    fn init_hardware_with_deploy(
113        device: Self::Pac,
114        core: Self::Core,
115        _deploy: &DeployOverlay,
116    ) -> (Self::Executor, Self::Runtime) {
117        <Self as RticBoardEntry>::init_hardware(device, core)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    //! Compile-time smoke test: a dummy `RticBoardEntry` impl wires
124    //! through every assoc type / const slot and the `Board`
125    //! super-trait chain (`BoardInit + BoardPrint + BoardExit`). The
126    //! impl is never invoked at runtime — the test is purely about
127    //! the trait surface accepting a real-shaped board type without
128    //! any extra bounds creep.
129    use super::*;
130    use crate::board::{BoardExit, BoardInit, BoardPrint, NodeDispatchRuntime};
131
132    struct DummyPac;
133    struct DummyCore;
134    struct DummyExecutor;
135    struct DummyRuntime;
136    struct DummyBoard;
137
138    impl BoardInit for DummyBoard {
139        fn init_hardware() {}
140    }
141    impl BoardPrint for DummyBoard {
142        fn println(_args: core::fmt::Arguments<'_>) {}
143    }
144    impl BoardExit for DummyBoard {
145        fn exit_success() -> ! {
146            // Test impl — never executed; the trait surface only
147            // requires the signature.
148            loop {}
149        }
150        fn exit_failure() -> ! {
151            loop {}
152        }
153    }
154
155    impl NodeDispatchRuntime for DummyRuntime {
156        fn spin_once(&mut self, _timeout_ms: u32) -> Result<(), ()> {
157            Err(())
158        }
159    }
160
161    impl RticBoardEntry for DummyBoard {
162        type Pac = DummyPac;
163        type Core = DummyCore;
164        type Executor = DummyExecutor;
165        type Runtime = DummyRuntime;
166
167        const DISPATCHERS: &'static [&'static str] = &["USART1", "USART2"];
168
169        fn init_hardware(_device: Self::Pac, _core: Self::Core) -> (Self::Executor, Self::Runtime) {
170            (DummyExecutor, DummyRuntime)
171        }
172    }
173
174    #[test]
175    fn dummy_board_satisfies_rtic_board_entry() {
176        // Trait-method call confirms the assoc types + const slot
177        // line up. We never spin the returned pair.
178        let (_exec, _rt) = <DummyBoard as RticBoardEntry>::init_hardware(DummyPac, DummyCore);
179        assert_eq!(<DummyBoard as RticBoardEntry>::DISPATCHERS.len(), 2);
180    }
181}