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}