Skip to main content

nros_platform_api/
wake.rs

1//! Phase 130 — ergonomic Rust wrapper around
2//! [`PlatformThreading`]'s wake primitive.
3//!
4//! Wraps a fixed-size, aligned scratch buffer so the executor can
5//! drop a `PlatformWake<P>` into a struct field without having to
6//! call `nros_platform_wake_storage_size()` at compile time.
7//! `WAKE_STORAGE_BYTES` is sized to cover every supported
8//! platform's binary semaphore (POSIX `sem_t` ~32 B, Zephyr
9//! `k_sem` ~16 B, FreeRTOS `xSemaphoreHandle` ~ptr, NuttX `sem_t`,
10//! ThreadX `tx_semaphore` ~56 B, macOS pthread cond+mutex+flag
11//! ~72 B). The probe-vs-buffer invariant is asserted at runtime
12//! during construction.
13//!
14//! Allocation-free: `Wake<P>` lives inline. Suitable for `no_std`
15//! consumers that can't depend on `alloc`.
16//!
17//! `Wake<P>` is `Sync` (the underlying primitive is the wake
18//! contract) but not `Send` after construction — the storage must
19//! stay put because the platform impl stores backing-primitive
20//! pointers that reference it.
21
22use core::{cell::UnsafeCell, ffi::c_void, marker::PhantomData, mem::MaybeUninit};
23
24use crate::PlatformThreading;
25
26/// Maximum bytes the wake primitive may occupy on any supported
27/// platform. Sized generously so we never have to bump for a new
28/// RTOS port. Asserted against the runtime probe in [`Wake::new`].
29pub const WAKE_STORAGE_BYTES: usize = 128;
30
31/// Alignment of the inline storage buffer. Wide enough for every
32/// pointer-aligned primitive on 64-bit hosts.
33pub const WAKE_STORAGE_ALIGN: usize = 16;
34
35#[repr(C, align(16))]
36struct WakeStorage {
37    bytes: UnsafeCell<[MaybeUninit<u8>; WAKE_STORAGE_BYTES]>,
38}
39
40/// Reason `wait_ms` returned.
41#[derive(Debug, Copy, Clone, PartialEq, Eq)]
42pub enum WakeReason {
43    /// `wake_signal` (or `wake_signal_from_isr`) fired and the
44    /// pending signal was consumed.
45    Signaled,
46    /// The timeout deadline expired before any signal.
47    Timeout,
48    /// Backend error (e.g. ISR-unsafe call on a platform that
49    /// rejects it). Treated as fatal in the executor's wake loop.
50    Error,
51}
52
53/// Errors at construction time.
54#[derive(Debug, Copy, Clone, PartialEq, Eq)]
55pub enum WakeInitError {
56    /// `P::wake_storage_size()` returned more bytes than the
57    /// inline buffer can hold. The platform impl needs more room
58    /// than the API contract reserved — bump
59    /// [`WAKE_STORAGE_BYTES`] or switch the consumer to a heap
60    /// path.
61    StorageTooSmall { needed: usize, available: usize },
62    /// `P::wake_storage_align()` returned a stricter alignment
63    /// than [`WAKE_STORAGE_ALIGN`].
64    AlignmentTooStrict { needed: usize, available: usize },
65    /// `P::wake_init` returned `-1`. Platform doesn't implement a
66    /// wake primitive (single-thread bare-metal).
67    Unsupported,
68}
69
70/// RAII wrapper around the platform's wake primitive.
71pub struct Wake<P: PlatformThreading> {
72    storage: WakeStorage,
73    _marker: PhantomData<P>,
74}
75
76// SAFETY: the wake contract is explicitly cross-thread. Backends
77// store either inline kernel structures (Zephyr k_sem, POSIX sem_t)
78// or pointers to handles owned by the kernel — both are safe to
79// access concurrently per their platform spec.
80unsafe impl<P: PlatformThreading> Sync for Wake<P> {}
81
82impl<P: PlatformThreading> Wake<P> {
83    /// Construct a new wake primitive in the inline buffer.
84    pub fn new() -> Result<Self, WakeInitError> {
85        let needed = P::wake_storage_size();
86        if needed == 0 {
87            return Err(WakeInitError::Unsupported);
88        }
89        if needed > WAKE_STORAGE_BYTES {
90            return Err(WakeInitError::StorageTooSmall {
91                needed,
92                available: WAKE_STORAGE_BYTES,
93            });
94        }
95        let align = P::wake_storage_align();
96        if align > WAKE_STORAGE_ALIGN {
97            return Err(WakeInitError::AlignmentTooStrict {
98                needed: align,
99                available: WAKE_STORAGE_ALIGN,
100            });
101        }
102
103        let storage = WakeStorage {
104            bytes: UnsafeCell::new([MaybeUninit::uninit(); WAKE_STORAGE_BYTES]),
105        };
106        let ptr = storage.bytes.get() as *mut c_void;
107        // `ptr` points at `WAKE_STORAGE_BYTES` aligned to 16 —
108        // both invariants verified above. `wake_init` either
109        // initialises the primitive in place or returns non-zero
110        // (in which case we drop `storage` without calling
111        // `wake_drop`, matching the "init failed, no teardown
112        // needed" contract).
113        let rc = P::wake_init(ptr);
114        if rc != 0 {
115            return Err(WakeInitError::Unsupported);
116        }
117        Ok(Self {
118            storage,
119            _marker: PhantomData,
120        })
121    }
122
123    /// Block until signaled or `timeout_ms` elapses.
124    pub fn wait_ms(&self, timeout_ms: u32) -> WakeReason {
125        let ptr = self.storage.bytes.get() as *mut c_void;
126        match P::wake_wait_ms(ptr, timeout_ms) {
127            0 => WakeReason::Signaled,
128            1 => WakeReason::Timeout,
129            _ => WakeReason::Error,
130        }
131    }
132
133    /// Wake one waiter. Safe to call from any thread.
134    pub fn signal(&self) {
135        let ptr = self.storage.bytes.get() as *mut c_void;
136        let _ = P::wake_signal(ptr);
137    }
138
139    /// ISR-safe variant. Returns `false` when the backend has no
140    /// ISR path and the caller should fall back to
141    /// [`Self::signal`].
142    pub fn signal_from_isr(&self) -> bool {
143        let ptr = self.storage.bytes.get() as *mut c_void;
144        P::wake_signal_from_isr(ptr) == 0
145    }
146}
147
148impl<P: PlatformThreading> Drop for Wake<P> {
149    fn drop(&mut self) {
150        let ptr = self.storage.bytes.get() as *mut c_void;
151        // ptr was init'd in `new`; this is the matching teardown
152        // call. Any in-flight `wait_ms` is the caller's bug —
153        // `Wake` is consumed by-value so callers cannot legally
154        // hold a `&self` past Drop.
155        let _ = P::wake_drop(ptr);
156    }
157}