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}