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}