Skip to main content

ove/
sync.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Synchronization primitives for oveRTOS.
8//!
9//! Provides RAII wrappers for mutexes, recursive mutexes, semaphores, events,
10//! and condition variables. All types implement `Send + Sync` and work in both
11//! heap and zero-heap modes.
12
13use core::cell::UnsafeCell;
14use core::fmt;
15use core::marker::PhantomData;
16use core::mem::ManuallyDrop;
17use core::mem::MaybeUninit;
18use core::ops::{Deref, DerefMut};
19
20use crate::bindings;
21use crate::error::{Error, Result};
22
23// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
24// calls below): any handle passed to the C API is non-null and refers to a
25// live RTOS object — wrapper constructors establish validity via
26// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
27// a handle is released. Pointer and slice arguments reference caller-owned
28// memory valid for the duration of the call; the C side copies whatever it
29// retains and does not alias them past return (verified against the
30// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
31// pointer casts from user data, slice reconstruction via `from_raw_parts`,
32// or storing a callback across the FFI boundary — carry their own
33// `// SAFETY:` comment.
34
35// ---------------------------------------------------------------------------
36// Guard !Send sentinel
37// ---------------------------------------------------------------------------
38//
39// `MutexGuard` / `RecursiveMutexGuard` must be `!Send` so a guard cannot be
40// sent to another thread and unlocked there — backend-defined UB (POSIX
41// returns `EPERM`, FreeRTOS asserts in debug / silently releases in release).
42//
43// `std::sync::MutexGuard` declares `impl !Send for MutexGuard {}` using the
44// nightly-only `negative_impls` feature.  On stable, the canonical workaround
45// is to embed a `!Send` marker type via `PhantomData`.  This is what
46// `lock_api`, `parking_lot`, and `tokio::sync` all do.  Raw pointers are
47// `!Send + !Sync`, so this newtype propagates both.
48#[allow(dead_code)] // marker type — field is never read by design
49struct GuardNoSend(*mut ());
50
51// ---------------------------------------------------------------------------
52// Mutex<T>
53// ---------------------------------------------------------------------------
54
55/// Mutex protecting a value of type `T`.  `std::sync::Mutex<T>` /
56/// `parking_lot::Mutex<T>` analog.
57///
58/// The lock and the data live together in one type.  Acquiring the
59/// lock via [`Mutex::lock`] returns a [`MutexGuard<T>`] that
60/// [`Deref`]s to `T`, so the only way to touch the data is to hold
61/// the lock first — the borrow checker enforces the contract that
62/// `lock` then `*g = …` is the only legal access pattern.
63///
64/// # Differences from `std::sync::Mutex`
65///
66/// - No [poisoning](https://doc.rust-lang.org/std/sync/struct.Mutex.html#poisoning).
67///   `lock()` returns `Result<MutexGuard<T>, Error>`, not std's
68///   `LockResult<MutexGuard<T>>`.  Mirrors parking_lot's choice
69///   — forcing callers to unwrap a never-firing `PoisonError` is
70///   worse ergonomics than reporting only the backend errors that
71///   can actually occur.
72/// - [`try_lock_for`](Self::try_lock_for) /
73///   [`try_lock_until`](Self::try_lock_until) variants for bounded
74///   waits.  Parking_lot has these; std does not.  Return
75///   `Result<MutexGuard, Error>` (with [`Error::Timeout`] on
76///   deadline elapsed), not parking_lot's `Option<MutexGuard>` —
77///   we have backend errors that aren't simply "could not acquire".
78pub struct Mutex<T: ?Sized> {
79    handle: bindings::ove_mutex_t,
80    data: UnsafeCell<T>,
81}
82
83// SAFETY: `Mutex<T>` synchronises access to `T`; `T: Send` is enough
84// (`Send` is what is needed to ship across thread boundaries, and the
85// mutex provides the cross-thread mutual exclusion).  `T: Sync` is NOT
86// required — the mutex itself provides the synchronisation that would
87// otherwise be `T`'s responsibility.  Matches std / parking_lot.
88unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
89unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
90
91/// Caller-owned backing storage for a [`Mutex`] in zero-heap mode.  Declare
92/// it in a `static` and hand `&STORAGE` to [`Mutex::create`]; in heap mode the
93/// storage is ignored.  Mirrors [`crate::ThreadStorage`] — `const fn new()`
94/// makes it usable from a `static`, and the bytes are only ever handed to C as
95/// a raw pointer (the kernel owns the object's synchronisation).
96// The field is only addressed via a raw pointer handed to C, so it reads as
97// "never read" in heap mode — same as ClientStorage.
98#[allow(dead_code)]
99pub struct MutexStorage {
100    storage: UnsafeCell<MaybeUninit<bindings::ove_mutex_storage_t>>,
101}
102
103impl MutexStorage {
104    /// Zero-initialised storage.  `const` so it can initialise a `static`.
105    #[inline]
106    pub const fn new() -> Self {
107        Self {
108            storage: UnsafeCell::new(MaybeUninit::zeroed()),
109        }
110    }
111}
112
113impl Default for MutexStorage {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119// SAFETY: the storage is only ever addressed as a raw pointer passed to C;
120// the kernel owns the mutex's synchronisation.  Mirrors ThreadStorage's Sync.
121unsafe impl Sync for MutexStorage {}
122
123impl<T> Mutex<T> {
124    /// Create a new mutex around `val` via heap allocation (heap mode only).
125    #[cfg(not(zero_heap))]
126    pub fn new(val: T) -> Result<Self> {
127        let mut handle: bindings::ove_mutex_t = core::ptr::null_mut();
128        let rc = unsafe { bindings::ove_mutex_create(&mut handle) };
129        Error::from_code(rc)?;
130        Ok(Self {
131            handle,
132            data: UnsafeCell::new(val),
133        })
134    }
135
136    /// Create a mutex around `val` using caller-provided static storage
137    /// for the handle.  Available in zero-heap mode.
138    ///
139    /// # Safety
140    /// Caller must ensure `storage` outlives the `Mutex` and is not
141    /// shared with another primitive.
142    #[cfg(zero_heap)]
143    pub unsafe fn from_static(storage: *mut bindings::ove_mutex_storage_t, val: T) -> Result<Self> {
144        let mut handle: bindings::ove_mutex_t = core::ptr::null_mut();
145        let rc = unsafe { bindings::ove_mutex_init(&mut handle, storage) };
146        Error::from_code(rc)?;
147        Ok(Self {
148            handle,
149            data: UnsafeCell::new(val),
150        })
151    }
152
153    /// Create a mutex around `val` that works in **both** heap and zero-heap
154    /// modes: heap mode ignores `storage` and allocates; zero-heap mode backs
155    /// the handle with the caller-provided `storage` (which must outlive the
156    /// `Mutex`).  This is the mode-agnostic constructor downstream crates use
157    /// — `new`/`from_static` are gated to one mode and `from_static` needs the
158    /// crate-private storage type, so neither is callable portably.
159    pub fn create(storage: &'static MutexStorage, val: T) -> Result<Self> {
160        #[cfg(not(zero_heap))]
161        {
162            let _ = storage;
163            Self::new(val)
164        }
165        #[cfg(zero_heap)]
166        {
167            // SAFETY: `storage` is 'static and, by this `&'static` borrow of a
168            // dedicated `static`, uniquely backs this one Mutex.
169            let ptr = UnsafeCell::raw_get(&storage.storage).cast();
170            unsafe { Self::from_static(ptr, val) }
171        }
172    }
173
174    /// Consume the mutex and return the protected value.
175    ///
176    /// Available in heap mode only (zero-heap mode lacks a destroy path
177    /// for the handle — the handle's storage outlives the `Mutex`
178    /// anyway).
179    #[cfg(not(zero_heap))]
180    pub fn into_inner(self) -> Result<T> {
181        // ManuallyDrop prevents the regular Drop from running (which
182        // would both destroy the handle AND drop the data).  We then
183        // extract T and destroy the handle ourselves.
184        let this = ManuallyDrop::new(self);
185        // SAFETY: `this` is consumed; nothing else accesses `data`.
186        let data = unsafe { core::ptr::read(&this.data) };
187        unsafe { bindings::ove_mutex_destroy(this.handle) };
188        Ok(data.into_inner())
189    }
190}
191
192impl<T: ?Sized> Mutex<T> {
193    /// Get a mutable reference to the protected value without locking.
194    ///
195    /// Safe because `&mut self` proves the caller has exclusive access
196    /// — no other thread can hold a guard at this moment.  Matches
197    /// `std::sync::Mutex::get_mut`.
198    pub fn get_mut(&mut self) -> &mut T {
199        // SAFETY: `&mut self` is exclusive; no aliasing.
200        unsafe { &mut *self.data.get() }
201    }
202
203    /// Acquire the mutex, blocking indefinitely.  Returns an RAII guard
204    /// that releases the lock on drop.
205    ///
206    /// # Errors
207    /// Returns the substrate's error code if the mutex handle is invalid
208    /// (programming error — same failure mode as in C/C++).
209    #[inline]
210    pub fn lock(&self) -> Result<MutexGuard<'_, T>> {
211        let rc = unsafe { bindings::ove_mutex_lock(self.handle, u64::MAX) };
212        Error::from_code(rc)?;
213        Ok(MutexGuard {
214            mutex: self,
215            _no_send: PhantomData,
216        })
217    }
218
219    /// Attempt to acquire the mutex without blocking.
220    /// `std::sync::Mutex::try_lock` analog.
221    ///
222    /// # Errors
223    /// Returns [`Error::WouldBlock`] if the mutex is currently held by
224    /// another thread.
225    #[inline]
226    pub fn try_lock(&self) -> Result<MutexGuard<'_, T>> {
227        let rc = unsafe { bindings::ove_mutex_lock(self.handle, 0) };
228        Error::from_code(rc)?;
229        Ok(MutexGuard {
230            mutex: self,
231            _no_send: PhantomData,
232        })
233    }
234
235    /// Attempt to acquire the mutex, waiting up to `d`.
236    /// `parking_lot::Mutex::try_lock_for` analog.
237    ///
238    /// # Errors
239    /// Returns [`Error::Timeout`] if the lock cannot be acquired within
240    /// the duration.
241    #[inline]
242    pub fn try_lock_for(&self, d: core::time::Duration) -> Result<MutexGuard<'_, T>> {
243        let rc = unsafe { bindings::ove_mutex_lock(self.handle, crate::time::dur_to_ns(d)) };
244        Error::from_code(rc)?;
245        Ok(MutexGuard {
246            mutex: self,
247            _no_send: PhantomData,
248        })
249    }
250
251    /// Attempt to acquire the mutex by the given deadline.
252    /// `parking_lot::Mutex::try_lock_until` analog.  Use
253    /// [`Instant::FOREVER`](crate::time::Instant::FOREVER) for an
254    /// indefinite wait.
255    ///
256    /// # Errors
257    /// Returns [`Error::Timeout`] if the deadline elapses before the lock
258    /// is acquired.
259    #[inline]
260    pub fn try_lock_until(&self, deadline: crate::time::Instant) -> Result<MutexGuard<'_, T>> {
261        let timeout = crate::time::deadline_to_timeout_ns(deadline);
262        let rc = unsafe { bindings::ove_mutex_lock(self.handle, timeout) };
263        Error::from_code(rc)?;
264        Ok(MutexGuard {
265            mutex: self,
266            _no_send: PhantomData,
267        })
268    }
269}
270
271impl<T: ?Sized> Drop for Mutex<T> {
272    fn drop(&mut self) {
273        if self.handle.is_null() {
274            return;
275        }
276        // `UnsafeCell<T>`'s Drop runs automatically and drops `T`.
277        #[cfg(not(zero_heap))]
278        unsafe {
279            bindings::ove_mutex_destroy(self.handle);
280        }
281        #[cfg(zero_heap)]
282        unsafe {
283            bindings::ove_mutex_deinit(self.handle);
284        }
285    }
286}
287
288impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        // Don't try to lock — Debug shouldn't deadlock.  Show only the
291        // handle; matches std's "Mutex { data: <locked> }" shape minus
292        // the racy data probe.
293        f.debug_struct("Mutex")
294            .field("handle", &format_args!("{:p}", self.handle))
295            .finish_non_exhaustive()
296    }
297}
298
299/// RAII guard that unlocks a `Mutex<T>` when dropped.
300///
301/// Deref / DerefMut expose the protected value.  `MutexGuard` is
302/// `!Send`: the locking thread must release the lock.  Sending a guard
303/// to another thread would cause that thread to issue
304/// `ove_mutex_unlock` with no matching `ove_mutex_lock`, which is
305/// backend-defined UB.  The `_no_send` field carries a `GuardNoSend`
306/// `PhantomData` to propagate `!Send` at compile time (the same trick
307/// `lock_api` / `parking_lot` use on stable Rust).
308///
309/// `Sync` is reinstated for `T: Sync` — a `&MutexGuard<T>` is the same
310/// as a `&T`, which is `Send` if `T: Sync`.  Matches std and
311/// parking_lot.
312pub struct MutexGuard<'a, T: ?Sized> {
313    mutex: &'a Mutex<T>,
314    _no_send: PhantomData<GuardNoSend>,
315}
316
317// SAFETY: `&MutexGuard<T>` is `&T` once `Deref`-ed; if `T: Sync` then
318// sharing `&T` across threads is sound.  std / parking_lot do the same.
319unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
320
321impl<T: ?Sized> Deref for MutexGuard<'_, T> {
322    type Target = T;
323
324    #[inline]
325    fn deref(&self) -> &T {
326        // SAFETY: holding the guard implies the lock is held; we have
327        // exclusive read access.
328        unsafe { &*self.mutex.data.get() }
329    }
330}
331
332impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
333    #[inline]
334    fn deref_mut(&mut self) -> &mut T {
335        // SAFETY: holding the guard implies the lock is held; `&mut self`
336        // ensures no other guard exists, so this is the only `&mut T`.
337        unsafe { &mut *self.mutex.data.get() }
338    }
339}
340
341impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        // `&&**self` reaches through Deref to `&T`, then a second
344        // `&` makes the result Sized so `field(...)` can erase it
345        // through `&dyn Debug`.  Mirrors std::sync::MutexGuard's
346        // Debug impl.
347        f.debug_struct("MutexGuard")
348            .field("data", &&**self)
349            .finish()
350    }
351}
352
353impl<T: ?Sized + fmt::Display> fmt::Display for MutexGuard<'_, T> {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        (**self).fmt(f)
356    }
357}
358
359impl<T: ?Sized> Drop for MutexGuard<'_, T> {
360    fn drop(&mut self) {
361        // SAFETY: handle is alive as long as the `&Mutex<T>` is borrowed,
362        // which the `'a` lifetime guarantees.
363        unsafe {
364            bindings::ove_mutex_unlock(self.mutex.handle);
365        }
366    }
367}
368
369// ---------------------------------------------------------------------------
370// RecursiveMutex
371// ---------------------------------------------------------------------------
372
373/// Caller-owned storage for a [`RecursiveMutex`] in zero-heap mode (see
374/// [`MutexStorage`]).
375#[allow(dead_code)]
376pub struct RecursiveMutexStorage {
377    storage: UnsafeCell<MaybeUninit<bindings::ove_mutex_storage_t>>,
378}
379
380impl RecursiveMutexStorage {
381    /// Zero-initialised storage.  `const` so it can initialise a `static`.
382    #[inline]
383    pub const fn new() -> Self {
384        Self {
385            storage: UnsafeCell::new(MaybeUninit::zeroed()),
386        }
387    }
388}
389
390impl Default for RecursiveMutexStorage {
391    fn default() -> Self {
392        Self::new()
393    }
394}
395
396// SAFETY: see MutexStorage.
397unsafe impl Sync for RecursiveMutexStorage {}
398
399/// RAII wrapper around a recursive mutex.
400pub struct RecursiveMutex {
401    handle: bindings::ove_mutex_t,
402}
403
404impl RecursiveMutex {
405    /// Create a new recursive mutex via heap allocation (only in heap mode).
406    #[cfg(not(zero_heap))]
407    pub fn new() -> Result<Self> {
408        let mut handle: bindings::ove_mutex_t = core::ptr::null_mut();
409        let rc = unsafe { bindings::ove_recursive_mutex_create(&mut handle) };
410        Error::from_code(rc)?;
411        Ok(Self { handle })
412    }
413
414    /// Create from caller-provided static storage.
415    ///
416    /// # Safety
417    /// Caller must ensure `storage` outlives the `RecursiveMutex` and is not
418    /// shared with another primitive.
419    #[cfg(zero_heap)]
420    pub unsafe fn from_static(storage: *mut bindings::ove_mutex_storage_t) -> Result<Self> {
421        let mut handle: bindings::ove_mutex_t = core::ptr::null_mut();
422        let rc = unsafe { bindings::ove_recursive_mutex_init(&mut handle, storage) };
423        Error::from_code(rc)?;
424        Ok(Self { handle })
425    }
426
427    /// Mode-agnostic constructor (see [`Mutex::create`]).
428    pub fn create(storage: &'static RecursiveMutexStorage) -> Result<Self> {
429        #[cfg(not(zero_heap))]
430        {
431            let _ = storage;
432            Self::new()
433        }
434        #[cfg(zero_heap)]
435        {
436            let ptr = UnsafeCell::raw_get(&storage.storage).cast();
437            unsafe { Self::from_static(ptr) }
438        }
439    }
440
441    /// Acquire the recursive mutex, blocking indefinitely.  Returns an
442    /// RAII guard that releases one level of the lock on drop.  Same
443    /// thread may call this multiple times — each guard releases one
444    /// level when dropped.
445    #[inline]
446    pub fn lock(&self) -> Result<RecursiveMutexGuard<'_>> {
447        let rc = unsafe { bindings::ove_recursive_mutex_lock(self.handle, u64::MAX) };
448        Error::from_code(rc)?;
449        Ok(RecursiveMutexGuard {
450            mutex: self,
451            _no_send: PhantomData,
452        })
453    }
454
455    /// Attempt to acquire the recursive mutex without blocking.
456    ///
457    /// # Errors
458    /// Returns [`Error::WouldBlock`] if held by a different thread.
459    #[inline]
460    pub fn try_lock(&self) -> Result<RecursiveMutexGuard<'_>> {
461        let rc = unsafe { bindings::ove_recursive_mutex_lock(self.handle, 0) };
462        Error::from_code(rc)?;
463        Ok(RecursiveMutexGuard {
464            mutex: self,
465            _no_send: PhantomData,
466        })
467    }
468
469    /// Attempt to acquire the recursive mutex, waiting up to `d`.
470    #[inline]
471    pub fn try_lock_for(&self, d: core::time::Duration) -> Result<RecursiveMutexGuard<'_>> {
472        let rc =
473            unsafe { bindings::ove_recursive_mutex_lock(self.handle, crate::time::dur_to_ns(d)) };
474        Error::from_code(rc)?;
475        Ok(RecursiveMutexGuard {
476            mutex: self,
477            _no_send: PhantomData,
478        })
479    }
480
481    /// Attempt to acquire the recursive mutex by the given deadline.
482    /// Use [`Instant::FOREVER`](crate::time::Instant::FOREVER) for an
483    /// indefinite wait.
484    #[inline]
485    pub fn try_lock_until(
486        &self,
487        deadline: crate::time::Instant,
488    ) -> Result<RecursiveMutexGuard<'_>> {
489        let timeout = crate::time::deadline_to_timeout_ns(deadline);
490        let rc = unsafe { bindings::ove_recursive_mutex_lock(self.handle, timeout) };
491        Error::from_code(rc)?;
492        Ok(RecursiveMutexGuard {
493            mutex: self,
494            _no_send: PhantomData,
495        })
496    }
497
498    /// Release one level of the recursive lock.
499    ///
500    /// Prefer letting the RAII guard ([`RecursiveMutexGuard`]) drop
501    /// release the lock; this is kept `pub` for parity with the C API.
502    #[inline]
503    pub fn unlock(&self) {
504        unsafe { bindings::ove_recursive_mutex_unlock(self.handle) }
505    }
506}
507
508crate::ove_handle_impl!(
509    RecursiveMutex,
510    ove_recursive_mutex_destroy,
511    ove_mutex_deinit
512);
513
514/// RAII guard that unlocks a `RecursiveMutex` when dropped.
515///
516/// Same `!Send` constraint as [`MutexGuard`]: the locking thread must
517/// issue the matching unlock.
518pub struct RecursiveMutexGuard<'a> {
519    mutex: &'a RecursiveMutex,
520    _no_send: PhantomData<GuardNoSend>,
521}
522
523impl fmt::Debug for RecursiveMutexGuard<'_> {
524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
525        f.debug_struct("RecursiveMutexGuard")
526            .field("mutex", &format_args!("{:p}", self.mutex.handle))
527            .finish()
528    }
529}
530
531impl Drop for RecursiveMutexGuard<'_> {
532    fn drop(&mut self) {
533        self.mutex.unlock();
534    }
535}
536
537// ---------------------------------------------------------------------------
538// Semaphore
539// ---------------------------------------------------------------------------
540
541/// Caller-owned backing storage for a [`Semaphore`] in zero-heap mode.  See
542/// [`MutexStorage`] for the pattern; pass `&STORAGE` to [`Semaphore::create`].
543#[allow(dead_code)]
544pub struct SemaphoreStorage {
545    storage: UnsafeCell<MaybeUninit<bindings::ove_sem_storage_t>>,
546}
547
548impl SemaphoreStorage {
549    /// Zero-initialised storage.  `const` so it can initialise a `static`.
550    #[inline]
551    pub const fn new() -> Self {
552        Self {
553            storage: UnsafeCell::new(MaybeUninit::zeroed()),
554        }
555    }
556}
557
558impl Default for SemaphoreStorage {
559    fn default() -> Self {
560        Self::new()
561    }
562}
563
564// SAFETY: see MutexStorage.
565unsafe impl Sync for SemaphoreStorage {}
566
567/// Counting semaphore.
568pub struct Semaphore {
569    handle: bindings::ove_sem_t,
570}
571
572impl Semaphore {
573    /// Create a counting semaphore via heap allocation (only in heap mode).
574    #[cfg(not(zero_heap))]
575    pub fn new(initial: u32, max: u32) -> Result<Self> {
576        let mut handle: bindings::ove_sem_t = core::ptr::null_mut();
577        let rc = unsafe { bindings::ove_sem_create(&mut handle, initial, max) };
578        Error::from_code(rc)?;
579        Ok(Self { handle })
580    }
581
582    /// Create from caller-provided static storage.
583    ///
584    /// # Safety
585    /// Caller must ensure `storage` outlives the `Semaphore`.
586    #[cfg(zero_heap)]
587    pub unsafe fn from_static(
588        storage: *mut bindings::ove_sem_storage_t,
589        initial: u32,
590        max: u32,
591    ) -> Result<Self> {
592        let mut handle: bindings::ove_sem_t = core::ptr::null_mut();
593        let rc = unsafe { bindings::ove_sem_init(&mut handle, storage, initial, max) };
594        Error::from_code(rc)?;
595        Ok(Self { handle })
596    }
597
598    /// Create a counting semaphore that works in **both** heap and zero-heap
599    /// modes (see [`Mutex::create`]).  Heap mode ignores `storage`; zero-heap
600    /// mode backs the handle with the caller-provided `storage`.
601    pub fn create(storage: &'static SemaphoreStorage, initial: u32, max: u32) -> Result<Self> {
602        #[cfg(not(zero_heap))]
603        {
604            let _ = storage;
605            Self::new(initial, max)
606        }
607        #[cfg(zero_heap)]
608        {
609            // SAFETY: see Mutex::create.
610            let ptr = UnsafeCell::raw_get(&storage.storage).cast();
611            unsafe { Self::from_static(ptr, initial, max) }
612        }
613    }
614
615    /// Acquire one permit, blocking indefinitely.  `tokio::sync::Semaphore::acquire`
616    /// analog (sans `.await`).
617    #[inline]
618    pub fn acquire(&self) -> Result<()> {
619        let rc = unsafe { bindings::ove_sem_take(self.handle, u64::MAX) };
620        Error::from_code(rc)
621    }
622
623    /// Attempt to acquire one permit without blocking.
624    ///
625    /// # Errors
626    /// Returns [`Error::WouldBlock`] if no permit is available.
627    #[inline]
628    pub fn try_acquire(&self) -> Result<()> {
629        let rc = unsafe { bindings::ove_sem_take(self.handle, 0) };
630        Error::from_code(rc)
631    }
632
633    /// Attempt to acquire one permit, waiting up to `d`.
634    /// `parking_lot::Semaphore` doesn't exist; this matches the
635    /// `try_lock_for` convention.
636    ///
637    /// # Errors
638    /// Returns [`Error::Timeout`] if the duration elapses with no
639    /// permit available.
640    #[inline]
641    pub fn try_acquire_for(&self, d: core::time::Duration) -> Result<()> {
642        let rc = unsafe { bindings::ove_sem_take(self.handle, crate::time::dur_to_ns(d)) };
643        Error::from_code(rc)
644    }
645
646    /// Attempt to acquire one permit by the given deadline.
647    /// Use [`Instant::FOREVER`](crate::time::Instant::FOREVER) for an
648    /// indefinite wait.
649    #[inline]
650    pub fn try_acquire_until(&self, deadline: crate::time::Instant) -> Result<()> {
651        let timeout = crate::time::deadline_to_timeout_ns(deadline);
652        let rc = unsafe { bindings::ove_sem_take(self.handle, timeout) };
653        Error::from_code(rc)
654    }
655
656    /// Release one permit.  `tokio::sync::Semaphore::add_permits(1)` /
657    /// `embassy_sync::Semaphore::release(1)` analog.
658    #[inline]
659    pub fn release(&self) {
660        unsafe { bindings::ove_sem_give(self.handle) }
661    }
662
663    /// Release `n` permits.  Binding-side loop — substrate currently
664    /// has no `ove_sem_give_n`, so this calls `ove_sem_give` `n` times.
665    #[inline]
666    pub fn release_n(&self, n: u32) {
667        for _ in 0..n {
668            unsafe { bindings::ove_sem_give(self.handle) }
669        }
670    }
671
672    /// Register a notify callback fired after every successful release.
673    /// Wraps the C-level `ove_sem_set_notify`.
674    ///
675    /// # Safety
676    /// Same as [`crate::Stream::set_notify`]: `user_data` must remain
677    /// valid for as long as the callback may fire, and `cb` must be
678    /// ISR-safe.
679    #[cfg(has_async)]
680    #[inline]
681    pub unsafe fn set_notify(
682        &self,
683        cb: Option<unsafe extern "C" fn(*mut core::ffi::c_void)>,
684        user_data: *mut core::ffi::c_void,
685    ) -> Result<()> {
686        let rc = unsafe { bindings::ove_sem_set_notify(self.handle, cb, user_data) };
687        Error::from_code(rc)
688    }
689}
690
691crate::ove_handle_impl!(Semaphore, ove_sem_destroy, ove_sem_deinit);
692
693// ---------------------------------------------------------------------------
694// Event
695// ---------------------------------------------------------------------------
696
697/// Caller-owned storage for an [`Event`] in zero-heap mode (see [`MutexStorage`]).
698#[allow(dead_code)]
699pub struct EventStorage {
700    storage: UnsafeCell<MaybeUninit<bindings::ove_event_storage_t>>,
701}
702
703impl EventStorage {
704    /// Zero-initialised storage.  `const` so it can initialise a `static`.
705    #[inline]
706    pub const fn new() -> Self {
707        Self {
708            storage: UnsafeCell::new(MaybeUninit::zeroed()),
709        }
710    }
711}
712
713impl Default for EventStorage {
714    fn default() -> Self {
715        Self::new()
716    }
717}
718
719// SAFETY: see MutexStorage.
720unsafe impl Sync for EventStorage {}
721
722/// Binary event (signal/wait).
723pub struct Event {
724    handle: bindings::ove_event_t,
725}
726
727impl Event {
728    /// Create a new event via heap allocation (only in heap mode).
729    #[cfg(not(zero_heap))]
730    pub fn new() -> Result<Self> {
731        let mut handle: bindings::ove_event_t = core::ptr::null_mut();
732        let rc = unsafe { bindings::ove_event_create(&mut handle) };
733        Error::from_code(rc)?;
734        Ok(Self { handle })
735    }
736
737    /// Create from caller-provided static storage.
738    ///
739    /// # Safety
740    /// Caller must ensure `storage` outlives the `Event`.
741    #[cfg(zero_heap)]
742    pub unsafe fn from_static(storage: *mut bindings::ove_event_storage_t) -> Result<Self> {
743        let mut handle: bindings::ove_event_t = core::ptr::null_mut();
744        let rc = unsafe { bindings::ove_event_init(&mut handle, storage) };
745        Error::from_code(rc)?;
746        Ok(Self { handle })
747    }
748
749    /// Mode-agnostic constructor (see [`Mutex::create`]).
750    pub fn create(storage: &'static EventStorage) -> Result<Self> {
751        #[cfg(not(zero_heap))]
752        {
753            let _ = storage;
754            Self::new()
755        }
756        #[cfg(zero_heap)]
757        {
758            let ptr = UnsafeCell::raw_get(&storage.storage).cast();
759            unsafe { Self::from_static(ptr) }
760        }
761    }
762
763    /// Block indefinitely until the event is signalled.
764    #[inline]
765    pub fn wait(&self) -> Result<()> {
766        let rc = unsafe { bindings::ove_event_wait(self.handle, u64::MAX) };
767        Error::from_code(rc)
768    }
769
770    /// Non-blocking check.
771    ///
772    /// # Errors
773    /// Returns [`Error::WouldBlock`] if the event is not currently
774    /// signalled.
775    #[inline]
776    pub fn try_wait(&self) -> Result<()> {
777        let rc = unsafe { bindings::ove_event_wait(self.handle, 0) };
778        Error::from_code(rc)
779    }
780
781    /// Wait for the event up to `d`.  `parking_lot::Condvar::wait_for`
782    /// naming convention (waiting primitives don't use the `try_`
783    /// prefix in parking_lot).
784    ///
785    /// # Errors
786    /// Returns [`Error::Timeout`] if the event is not signalled within
787    /// `d`.
788    #[inline]
789    pub fn wait_for(&self, d: core::time::Duration) -> Result<()> {
790        let rc = unsafe { bindings::ove_event_wait(self.handle, crate::time::dur_to_ns(d)) };
791        Error::from_code(rc)
792    }
793
794    /// Wait for the event by the given deadline.  Use
795    /// [`Instant::FOREVER`](crate::time::Instant::FOREVER) for an
796    /// indefinite wait.
797    ///
798    /// # Errors
799    /// Returns [`Error::Timeout`] if the deadline elapses before the event
800    /// is signalled.
801    #[inline]
802    pub fn wait_until(&self, deadline: crate::time::Instant) -> Result<()> {
803        let timeout = crate::time::deadline_to_timeout_ns(deadline);
804        let rc = unsafe { bindings::ove_event_wait(self.handle, timeout) };
805        Error::from_code(rc)
806    }
807
808    /// Signal the event.
809    #[inline]
810    pub fn signal(&self) {
811        unsafe { bindings::ove_event_signal(self.handle) }
812    }
813
814    /// Signal the event from an ISR context.
815    #[inline]
816    pub fn signal_from_isr(&self) {
817        unsafe { bindings::ove_event_signal_from_isr(self.handle) }
818    }
819}
820
821crate::ove_handle_impl!(Event, ove_event_destroy, ove_event_deinit);
822
823// ---------------------------------------------------------------------------
824// CondVar + WaitTimeoutResult
825// ---------------------------------------------------------------------------
826
827/// Result of a `Condvar::wait_for` / `wait_until` call indicating
828/// whether the wait elapsed without being signalled.
829///
830/// Matches `std::sync::WaitTimeoutResult` exactly — same type name,
831/// same `timed_out()` accessor.  Returned wrapped in a tuple alongside
832/// the re-acquired guard.
833#[derive(Debug, Clone, Copy, PartialEq, Eq)]
834pub struct WaitTimeoutResult {
835    timed_out: bool,
836}
837
838impl WaitTimeoutResult {
839    /// Returns `true` if the wait elapsed without being signalled.
840    #[inline]
841    pub fn timed_out(&self) -> bool {
842        self.timed_out
843    }
844}
845
846/// Condition variable.  Pair with [`Mutex<T>`] to wait on state changes.
847///
848/// API matches `parking_lot::Condvar` shape — `wait` consumes the
849/// guard, returns it re-acquired.  Predicate variants
850/// (`wait_while*`) loop internally so callers can't accidentally write
851/// the buggy `if cv.wait_for(...) ... && ready` pattern.
852/// Caller-owned storage for a [`CondVar`] in zero-heap mode (see [`MutexStorage`]).
853#[allow(dead_code)]
854pub struct CondVarStorage {
855    storage: UnsafeCell<MaybeUninit<bindings::ove_condvar_storage_t>>,
856}
857
858impl CondVarStorage {
859    /// Zero-initialised storage.  `const` so it can initialise a `static`.
860    #[inline]
861    pub const fn new() -> Self {
862        Self {
863            storage: UnsafeCell::new(MaybeUninit::zeroed()),
864        }
865    }
866}
867
868impl Default for CondVarStorage {
869    fn default() -> Self {
870        Self::new()
871    }
872}
873
874// SAFETY: see MutexStorage.
875unsafe impl Sync for CondVarStorage {}
876
877pub struct CondVar {
878    handle: bindings::ove_condvar_t,
879}
880
881impl CondVar {
882    /// Create a new condition variable via heap allocation (only in heap mode).
883    #[cfg(not(zero_heap))]
884    pub fn new() -> Result<Self> {
885        let mut handle: bindings::ove_condvar_t = core::ptr::null_mut();
886        let rc = unsafe { bindings::ove_condvar_create(&mut handle) };
887        Error::from_code(rc)?;
888        Ok(Self { handle })
889    }
890
891    /// Create from caller-provided static storage.
892    ///
893    /// # Safety
894    /// Caller must ensure `storage` outlives the `CondVar`.
895    #[cfg(zero_heap)]
896    pub unsafe fn from_static(storage: *mut bindings::ove_condvar_storage_t) -> Result<Self> {
897        let mut handle: bindings::ove_condvar_t = core::ptr::null_mut();
898        let rc = unsafe { bindings::ove_condvar_init(&mut handle, storage) };
899        Error::from_code(rc)?;
900        Ok(Self { handle })
901    }
902
903    /// Mode-agnostic constructor (see [`Mutex::create`]).
904    pub fn create(storage: &'static CondVarStorage) -> Result<Self> {
905        #[cfg(not(zero_heap))]
906        {
907            let _ = storage;
908            Self::new()
909        }
910        #[cfg(zero_heap)]
911        {
912            let ptr = UnsafeCell::raw_get(&storage.storage).cast();
913            unsafe { Self::from_static(ptr) }
914        }
915    }
916
917    /// Atomically release the guarded mutex and block indefinitely
918    /// until signalled.  On return, the mutex is re-acquired and the
919    /// guard handed back.  `std::sync::Condvar::wait` analog.
920    ///
921    /// Always re-check the predicate in a loop after this returns —
922    /// spurious wake-ups are permitted by the substrate.  Or use
923    /// [`wait_while`](Self::wait_while) which does the loop for you.
924    #[inline]
925    pub fn wait<'a, T: ?Sized>(&self, guard: MutexGuard<'a, T>) -> Result<MutexGuard<'a, T>> {
926        let mutex = guard.mutex;
927        // Skip the guard's Drop — substrate atomically releases and
928        // re-acquires the mutex internally.
929        let _suppress = ManuallyDrop::new(guard);
930        let rc = unsafe { bindings::ove_condvar_wait(self.handle, mutex.handle, u64::MAX) };
931        Error::from_code(rc)?;
932        Ok(MutexGuard {
933            mutex,
934            _no_send: PhantomData,
935        })
936    }
937
938    /// Atomically release the guarded mutex and wait up to `d`.  On
939    /// return, the mutex is re-acquired.  `parking_lot::Condvar::wait_for`
940    /// analog.
941    ///
942    /// The returned [`WaitTimeoutResult`] distinguishes "signalled in
943    /// time" (`timed_out() == false`) from "elapsed without signal"
944    /// (`timed_out() == true`).
945    ///
946    /// # Errors
947    /// Returns an error for backend failures other than a clean timeout
948    /// — a clean timeout returns `Ok((guard, WaitTimeoutResult { ..true }))`.
949    #[inline]
950    pub fn wait_for<'a, T: ?Sized>(
951        &self,
952        guard: MutexGuard<'a, T>,
953        d: core::time::Duration,
954    ) -> Result<(MutexGuard<'a, T>, WaitTimeoutResult)> {
955        self.wait_with_timeout(guard, crate::time::dur_to_ns(d))
956    }
957
958    /// Atomically release the guarded mutex and wait by the given
959    /// deadline.  `parking_lot::Condvar::wait_until` analog.  Use
960    /// [`Instant::FOREVER`](crate::time::Instant::FOREVER) for an
961    /// indefinite wait.
962    #[inline]
963    pub fn wait_until<'a, T: ?Sized>(
964        &self,
965        guard: MutexGuard<'a, T>,
966        deadline: crate::time::Instant,
967    ) -> Result<(MutexGuard<'a, T>, WaitTimeoutResult)> {
968        self.wait_with_timeout(guard, crate::time::deadline_to_timeout_ns(deadline))
969    }
970
971    #[inline]
972    fn wait_with_timeout<'a, T: ?Sized>(
973        &self,
974        guard: MutexGuard<'a, T>,
975        timeout_ns: u64,
976    ) -> Result<(MutexGuard<'a, T>, WaitTimeoutResult)> {
977        let mutex = guard.mutex;
978        let _suppress = ManuallyDrop::new(guard);
979        let rc = unsafe { bindings::ove_condvar_wait(self.handle, mutex.handle, timeout_ns) };
980        // OVE_ERR_TIMEOUT is the "clean timeout, mutex re-acquired"
981        // case — re-package as `Ok((guard, timed_out: true))`.  Other
982        // negative codes are real errors.
983        let timed_out = match Error::from_code(rc) {
984            Ok(()) => false,
985            Err(Error::Timeout) => true,
986            Err(e) => return Err(e),
987        };
988        Ok((
989            MutexGuard {
990                mutex,
991                _no_send: PhantomData,
992            },
993            WaitTimeoutResult { timed_out },
994        ))
995    }
996
997    /// Loop until `cond(&mut T)` returns `false`, releasing the lock
998    /// while waiting.  `std::sync::Condvar::wait_while` analog.
999    ///
1000    /// Internally handles spurious wake-ups by re-checking the
1001    /// predicate after every wake.
1002    pub fn wait_while<'a, T: ?Sized, F>(
1003        &self,
1004        mut guard: MutexGuard<'a, T>,
1005        mut cond: F,
1006    ) -> Result<MutexGuard<'a, T>>
1007    where
1008        F: FnMut(&mut T) -> bool,
1009    {
1010        while cond(&mut *guard) {
1011            guard = self.wait(guard)?;
1012        }
1013        Ok(guard)
1014    }
1015
1016    /// `wait_while` with a duration bound.
1017    ///
1018    /// Returns `(guard, WaitTimeoutResult)` — `timed_out()` is `true`
1019    /// iff the predicate is still `true` at deadline.
1020    pub fn wait_while_for<'a, T: ?Sized, F>(
1021        &self,
1022        guard: MutexGuard<'a, T>,
1023        d: core::time::Duration,
1024        cond: F,
1025    ) -> Result<(MutexGuard<'a, T>, WaitTimeoutResult)>
1026    where
1027        F: FnMut(&mut T) -> bool,
1028    {
1029        let deadline = crate::time::Instant::now() + d;
1030        self.wait_while_until(guard, deadline, cond)
1031    }
1032
1033    /// `wait_while` with an absolute deadline.
1034    pub fn wait_while_until<'a, T: ?Sized, F>(
1035        &self,
1036        mut guard: MutexGuard<'a, T>,
1037        deadline: crate::time::Instant,
1038        mut cond: F,
1039    ) -> Result<(MutexGuard<'a, T>, WaitTimeoutResult)>
1040    where
1041        F: FnMut(&mut T) -> bool,
1042    {
1043        loop {
1044            if !cond(&mut *guard) {
1045                return Ok((guard, WaitTimeoutResult { timed_out: false }));
1046            }
1047            let (g, wtr) = self.wait_until(guard, deadline)?;
1048            guard = g;
1049            if wtr.timed_out() {
1050                // Re-evaluate one final time — the substrate may have
1051                // released the lock right at the deadline; cond may now
1052                // be false.  Matches std's semantics.
1053                let timed_out = cond(&mut *guard);
1054                return Ok((guard, WaitTimeoutResult { timed_out }));
1055            }
1056        }
1057    }
1058
1059    /// Wake one waiter.
1060    #[inline]
1061    pub fn signal(&self) {
1062        unsafe { bindings::ove_condvar_signal(self.handle) }
1063    }
1064
1065    /// Wake all waiters.
1066    #[inline]
1067    pub fn broadcast(&self) {
1068        unsafe { bindings::ove_condvar_broadcast(self.handle) }
1069    }
1070}
1071
1072crate::ove_handle_impl!(CondVar, ove_condvar_destroy, ove_condvar_deinit);
1073
1074// ── SpinMutex (G5) ───────────────────────────────────────────────────
1075//
1076// IRQ-locking mutex for very short critical sections. Distinct from
1077// the sleeping `Mutex<T>` above:
1078//
1079// - `Mutex::lock` blocks the calling thread on a kernel mutex; legal
1080//   from thread context only.
1081// - `SpinMutex::lock` disables interrupts globally and holds them
1082//   disabled for the body of the closure / lifetime of the guard. Use
1083//   for sub-microsecond updates to shared state that must be readable
1084//   from ISR context, or to bridge `&` data into an async task that
1085//   can't hold a real mutex across an `.await`.
1086//
1087// Requires the async substrate (`CONFIG_OVE_ASYNC=y`) — `ove_irq_lock`
1088// lives in `include/ove/irq.h` under that gate. Not available on WASM
1089// (no interrupt model). Consumers that need to be portable across
1090// builds with `CONFIG_OVE_ASYNC=n` should gate with
1091// `#[cfg(all(has_async, not(board_wasm)))]`.
1092
1093#[cfg(all(has_async, not(board_wasm)))]
1094pub use spin_mutex::{SpinMutex, SpinMutexGuard};
1095
1096#[cfg(all(has_async, not(board_wasm)))]
1097mod spin_mutex {
1098    use ::core::cell::UnsafeCell;
1099    use ::core::ops::{Deref, DerefMut};
1100
1101    use crate::bindings;
1102
1103    /// IRQ-locking mutex for very short critical sections.
1104    ///
1105    /// Acquiring the lock disables interrupts globally for the lifetime
1106    /// of the [`SpinMutexGuard`]; releasing it restores the previous
1107    /// interrupt state. **Do not hold across blocking calls or `.await`
1108    /// points** — the system will deadlock if a higher-priority
1109    /// interrupt is needed to make progress.
1110    ///
1111    /// Maps to `ove_irq_lock` / `ove_irq_unlock` on the C side, which
1112    /// is itself a thin wrapper over the host RTOS's
1113    /// interrupt-disable primitive (`taskENTER_CRITICAL` on FreeRTOS,
1114    /// `irq_lock()` on Zephyr, `enter_critical_section()` on NuttX,
1115    /// `pthread_sigmask` on POSIX).
1116    pub struct SpinMutex<T: ?Sized> {
1117        // UnsafeCell — `lock()` hands out `&mut T` while interrupts
1118        // are disabled, which is sound because the disable masks out
1119        // any other code path that could observe the same `&mut`.
1120        inner: UnsafeCell<T>,
1121    }
1122
1123    // SAFETY: any `T: Send` may be shared between threads because the
1124    // mutex enforces mutual exclusion via the interrupt-disable.
1125    unsafe impl<T: ?Sized + Send> Send for SpinMutex<T> {}
1126    unsafe impl<T: ?Sized + Send> Sync for SpinMutex<T> {}
1127
1128    impl<T> SpinMutex<T> {
1129        /// Wrap `value` in a new `SpinMutex`.
1130        #[inline]
1131        pub const fn new(value: T) -> Self {
1132            Self {
1133                inner: UnsafeCell::new(value),
1134            }
1135        }
1136
1137        /// Consume the mutex and return the wrapped value.
1138        #[inline]
1139        pub fn into_inner(self) -> T {
1140            self.inner.into_inner()
1141        }
1142    }
1143
1144    impl<T: ?Sized> SpinMutex<T> {
1145        /// Acquire the lock by disabling interrupts. The returned
1146        /// guard restores them when dropped.
1147        #[inline]
1148        pub fn lock(&self) -> SpinMutexGuard<'_, T> {
1149            // SAFETY: ove_irq_lock is safe from any context.
1150            let key = unsafe { bindings::ove_irq_lock() };
1151            SpinMutexGuard {
1152                mtx: self,
1153                key,
1154                _not_send: ::core::marker::PhantomData,
1155            }
1156        }
1157
1158        /// Run `f` with the value, restoring the IRQ state afterwards.
1159        /// Sugar around [`SpinMutex::lock`] that scopes the lock to
1160        /// the closure body.
1161        #[inline]
1162        pub fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
1163            let mut g = self.lock();
1164            f(&mut *g)
1165        }
1166
1167        /// Get a mutable reference without taking the lock. Sound
1168        /// because `&mut self` proves there are no other handles.
1169        #[inline]
1170        pub fn get_mut(&mut self) -> &mut T {
1171            // SAFETY: `&mut self` is exclusive.
1172            unsafe { &mut *self.inner.get() }
1173        }
1174    }
1175
1176    /// RAII guard that releases the [`SpinMutex`] when dropped.
1177    pub struct SpinMutexGuard<'a, T: ?Sized> {
1178        mtx: &'a SpinMutex<T>,
1179        key: bindings::ove_irq_key_t,
1180        // `*const ()` is !Send + !Sync by default; ensures the guard
1181        // can't migrate to another thread and call ove_irq_unlock from
1182        // the wrong context.
1183        _not_send: ::core::marker::PhantomData<*const ()>,
1184    }
1185
1186    impl<T: ?Sized> Deref for SpinMutexGuard<'_, T> {
1187        type Target = T;
1188        #[inline]
1189        fn deref(&self) -> &T {
1190            // SAFETY: the lock guarantees exclusive access.
1191            unsafe { &*self.mtx.inner.get() }
1192        }
1193    }
1194
1195    impl<T: ?Sized> DerefMut for SpinMutexGuard<'_, T> {
1196        #[inline]
1197        fn deref_mut(&mut self) -> &mut T {
1198            unsafe { &mut *self.mtx.inner.get() }
1199        }
1200    }
1201
1202    impl<T: ?Sized> Drop for SpinMutexGuard<'_, T> {
1203        #[inline]
1204        fn drop(&mut self) {
1205            // SAFETY: `key` was returned by the matching ove_irq_lock.
1206            unsafe { bindings::ove_irq_unlock(self.key) }
1207        }
1208    }
1209}