Skip to main content

nros_rmw/
sync.rs

1//! Synchronization primitives abstraction
2//!
3//! This module provides a unified interface for different mutex implementations,
4//! allowing the transport layer to work with various executor backends.
5//!
6//! All backends expose a single API: [`Mutex::with()`], which executes a
7//! closure while holding the lock. This closure-based API guarantees correct
8//! lock release across all backends (spin, critical-section, no-sync).
9//!
10//! # Feature Flags
11//!
12//! - `sync-spin` (default for zenoh): Uses `spin::Mutex` - works everywhere but not RTIC-compatible
13//! - `sync-critical-section`: Uses critical sections - RTIC/Embassy compatible
14//! - `sync-portable-atomic`: Uses portable-atomic for broader platform support
15//!
16//! # RTIC Compatibility
17//!
18//! For RTIC applications, use `sync-critical-section` feature. This ensures that
19//! mutex operations use `critical_section::with()` which is compatible with RTIC's
20//! Stack Resource Policy (SRP) scheduling.
21
22// ============================================================================
23// spin::Mutex implementation (default)
24// ============================================================================
25
26#[cfg(feature = "sync-spin")]
27mod spin_impl {
28    /// Mutex using spin lock (default implementation)
29    pub struct Mutex<T> {
30        inner: spin::Mutex<T>,
31    }
32
33    impl<T> Mutex<T> {
34        /// Create a new mutex with the given value
35        pub const fn new(value: T) -> Self {
36            Self {
37                inner: spin::Mutex::new(value),
38            }
39        }
40
41        /// Lock the mutex and execute the closure with access to the data.
42        pub fn with<F, R>(&self, f: F) -> R
43        where
44            F: FnOnce(&mut T) -> R,
45        {
46            f(&mut self.inner.lock())
47        }
48    }
49
50    // SAFETY: Mutex is Send if T is Send
51    unsafe impl<T: Send> Send for Mutex<T> {}
52    // SAFETY: Mutex is Sync if T is Send
53    unsafe impl<T: Send> Sync for Mutex<T> {}
54}
55
56#[cfg(feature = "sync-spin")]
57pub use spin_impl::Mutex;
58
59// ============================================================================
60// critical-section implementation (RTIC/Embassy compatible)
61// ============================================================================
62
63#[cfg(all(feature = "sync-critical-section", not(feature = "sync-spin")))]
64mod cs_impl {
65    use core::cell::UnsafeCell;
66
67    /// Mutex using critical sections (RTIC/Embassy compatible)
68    ///
69    /// This implementation uses `critical_section::with()` to protect data access,
70    /// which is compatible with RTIC's Stack Resource Policy (SRP) scheduling.
71    pub struct Mutex<T> {
72        data: UnsafeCell<T>,
73    }
74
75    impl<T> Mutex<T> {
76        /// Create a new mutex with the given value
77        pub const fn new(value: T) -> Self {
78            Self {
79                data: UnsafeCell::new(value),
80            }
81        }
82
83        /// Lock the mutex and execute the closure with access to the data.
84        pub fn with<F, R>(&self, f: F) -> R
85        where
86            F: FnOnce(&mut T) -> R,
87        {
88            critical_section::with(|_cs| {
89                // SAFETY: We're in a critical section, so no other code can access this
90                let data = unsafe { &mut *self.data.get() };
91                f(data)
92            })
93        }
94    }
95
96    // SAFETY: Mutex is Send if T is Send
97    unsafe impl<T: Send> Send for Mutex<T> {}
98    // SAFETY: Mutex is Sync if T is Send (access is protected by critical section)
99    unsafe impl<T: Send> Sync for Mutex<T> {}
100}
101
102#[cfg(all(feature = "sync-critical-section", not(feature = "sync-spin")))]
103pub use cs_impl::Mutex;
104
105// ============================================================================
106// Fallback: No sync feature enabled - use RefCell-like approach
107// This is only safe for single-threaded use (e.g., bare-metal without interrupts)
108// ============================================================================
109
110#[cfg(not(any(feature = "sync-spin", feature = "sync-critical-section")))]
111mod nosync_impl {
112    use core::cell::RefCell;
113
114    /// Mutex without synchronization (single-threaded only)
115    ///
116    /// WARNING: This is only safe for single-threaded use cases without interrupts.
117    /// For RTIC or any multi-priority system, use `sync-critical-section` feature.
118    pub struct Mutex<T> {
119        inner: RefCell<T>,
120    }
121
122    impl<T> Mutex<T> {
123        /// Create a new mutex with the given value
124        pub const fn new(value: T) -> Self {
125            Self {
126                inner: RefCell::new(value),
127            }
128        }
129
130        /// Lock the mutex and execute the closure with access to the data.
131        pub fn with<F, R>(&self, f: F) -> R
132        where
133            F: FnOnce(&mut T) -> R,
134        {
135            f(&mut self.inner.borrow_mut())
136        }
137    }
138
139    // Note: Without proper synchronization, this is NOT truly Send/Sync safe
140    // but we provide it for compilation in no_std single-threaded scenarios.
141    // This is ONLY safe when there is a single thread of execution and no
142    // interrupts that access the same data.
143    unsafe impl<T: Send> Send for Mutex<T> {}
144    // SAFETY: We implement Sync to allow Arc<Mutex<T>> to be Send, which is
145    // required for zenoh callbacks. This is safe ONLY in single-threaded
146    // environments without concurrent interrupt access.
147    unsafe impl<T: Send> Sync for Mutex<T> {}
148}
149
150#[cfg(not(any(feature = "sync-spin", feature = "sync-critical-section")))]
151pub use nosync_impl::Mutex;
152
153// ============================================================================
154// Helper trait for lock-free operations using atomics
155// ============================================================================
156
157/// Helper functions for atomic operations used in buffers
158pub mod atomic {
159    use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
160
161    /// Atomically load a boolean value
162    #[inline]
163    pub fn load_bool(val: &AtomicBool) -> bool {
164        val.load(Ordering::Acquire)
165    }
166
167    /// Atomically store a boolean value
168    #[inline]
169    pub fn store_bool(val: &AtomicBool, new: bool) {
170        val.store(new, Ordering::Release);
171    }
172
173    /// Atomically load a usize value
174    #[inline]
175    pub fn load_usize(val: &AtomicUsize) -> usize {
176        val.load(Ordering::Acquire)
177    }
178
179    /// Atomically store a usize value
180    #[inline]
181    pub fn store_usize(val: &AtomicUsize, new: usize) {
182        val.store(new, Ordering::Release);
183    }
184
185    // AtomicI64 functions are only available on platforms with 64-bit atomics
186    // (e.g., x86_64, aarch64). On 32-bit platforms like thumbv7em, these are
187    // not available. The zenoh module uses these but requires alloc/std anyway.
188    #[cfg(target_has_atomic = "64")]
189    use core::sync::atomic::AtomicI64;
190
191    /// Atomically load an i64 value
192    #[cfg(target_has_atomic = "64")]
193    #[inline]
194    pub fn load_i64(val: &AtomicI64) -> i64 {
195        val.load(Ordering::Acquire)
196    }
197
198    /// Atomically store an i64 value
199    #[cfg(target_has_atomic = "64")]
200    #[inline]
201    pub fn store_i64(val: &AtomicI64, new: i64) {
202        val.store(new, Ordering::Release);
203    }
204
205    /// Atomically increment i64 and return new value
206    #[cfg(target_has_atomic = "64")]
207    #[inline]
208    pub fn fetch_add_i64(val: &AtomicI64, add: i64) -> i64 {
209        val.fetch_add(add, Ordering::AcqRel)
210    }
211}