aboutsummaryrefslogtreecommitdiff
path: root/embassy-sync/src
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-sync/src')
-rw-r--r--embassy-sync/src/blocking_mutex/raw_rwlock.rs86
-rw-r--r--embassy-sync/src/blocking_rwlock/mod.rs221
-rw-r--r--embassy-sync/src/blocking_rwlock/raw.rs159
-rw-r--r--embassy-sync/src/lib.rs1
4 files changed, 381 insertions, 86 deletions
diff --git a/embassy-sync/src/blocking_mutex/raw_rwlock.rs b/embassy-sync/src/blocking_mutex/raw_rwlock.rs
deleted file mode 100644
index de4bd1dc5..000000000
--- a/embassy-sync/src/blocking_mutex/raw_rwlock.rs
+++ /dev/null
@@ -1,86 +0,0 @@
1use core::sync::atomic::{AtomicUsize, Ordering};
2use core::task::Waker;
3use core::cell::UnsafeCell;
4
5pub trait RawRwLock {
6 fn lock_read(&self);
7 fn try_lock_read(&self) -> bool;
8 fn unlock_read(&self);
9 fn lock_write(&self);
10 fn try_lock_write(&self) -> bool;
11 fn unlock_write(&self);
12}
13
14pub struct RawRwLockImpl {
15 state: AtomicUsize,
16 waker: UnsafeCell<Option<Waker>>,
17}
18
19impl RawRwLockImpl {
20 pub const fn new() -> Self {
21 Self {
22 state: AtomicUsize::new(0),
23 waker: UnsafeCell::new(None),
24 }
25 }
26}
27
28unsafe impl Send for RawRwLockImpl {}
29unsafe impl Sync for RawRwLockImpl {}
30
31impl RawRwLock for RawRwLockImpl {
32 fn lock_read(&self) {
33 loop {
34 let state = self.state.load(Ordering::Acquire);
35 if state & 1 == 0 {
36 if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state {
37 break;
38 }
39 }
40 }
41 }
42
43 fn try_lock_read(&self) -> bool {
44 let state = self.state.load(Ordering::Acquire);
45 if state & 1 == 0 {
46 if self.state.compare_and_swap(state, state + 2, Ordering::AcqRel) == state {
47 return true;
48 }
49 }
50 false
51 }
52
53 fn unlock_read(&self) {
54 self.state.fetch_sub(2, Ordering::Release);
55 if self.state.load(Ordering::Acquire) == 0 {
56 if let Some(waker) = unsafe { &*self.waker.get() } {
57 waker.wake_by_ref();
58 }
59 }
60 }
61
62 fn lock_write(&self) {
63 loop {
64 let state = self.state.load(Ordering::Acquire);
65 if state == 0 {
66 if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 {
67 break;
68 }
69 }
70 }
71 }
72
73 fn try_lock_write(&self) -> bool {
74 if self.state.compare_and_swap(0, 1, Ordering::AcqRel) == 0 {
75 return true;
76 }
77 false
78 }
79
80 fn unlock_write(&self) {
81 self.state.store(0, Ordering::Release);
82 if let Some(waker) = unsafe { &*self.waker.get() } {
83 waker.wake_by_ref();
84 }
85 }
86}
diff --git a/embassy-sync/src/blocking_rwlock/mod.rs b/embassy-sync/src/blocking_rwlock/mod.rs
new file mode 100644
index 000000000..a304b77d6
--- /dev/null
+++ b/embassy-sync/src/blocking_rwlock/mod.rs
@@ -0,0 +1,221 @@
1//! Blocking read-write lock.
2//!
3//! This module provides a blocking read-write lock that can be used to synchronize data.
4pub mod raw_rwlock;
5
6use core::cell::UnsafeCell;
7
8use self::raw_rwlock::RawRwLock;
9
10/// Blocking read-write lock (not async)
11///
12/// Provides a blocking read-write lock primitive backed by an implementation of [`raw_rwlock::RawRwLock`].
13///
14/// Which implementation you select depends on the context in which you're using the read-write lock, and you can choose which kind
15/// of interior mutability fits your use case.
16///
17/// Use [`CriticalSectionRwLock`] when data can be shared between threads and interrupts.
18///
19/// Use [`NoopRwLock`] when data is only shared between tasks running on the same executor.
20///
21/// Use [`ThreadModeRwLock`] when data is shared between tasks running on the same executor but you want a global singleton.
22///
23/// In all cases, the blocking read-write lock is intended to be short lived and not held across await points.
24/// Use the async [`RwLock`](crate::rwlock::RwLock) if you need a lock that is held across await points.
25pub struct RwLock<R, T: ?Sized> {
26 // NOTE: `raw` must be FIRST, so when using ThreadModeRwLock the "can't drop in non-thread-mode" gets
27 // to run BEFORE dropping `data`.
28 raw: R,
29 data: UnsafeCell<T>,
30}
31
32unsafe impl<R: RawRwLock + Send, T: ?Sized + Send> Send for RwLock<R, T> {}
33unsafe impl<R: RawRwLock + Sync, T: ?Sized + Send> Sync for RwLock<R, T> {}
34
35impl<R: RawRwLock, T> RwLock<R, T> {
36 /// Creates a new read-write lock in an unlocked state ready for use.
37 #[inline]
38 pub const fn new(val: T) -> RwLock<R, T> {
39 RwLock {
40 raw: R::INIT,
41 data: UnsafeCell::new(val),
42 }
43 }
44
45 /// Creates a critical section and grants temporary read access to the protected data.
46 pub fn read_lock<U>(&self, f: impl FnOnce(&T) -> U) -> U {
47 self.raw.read_lock(|| {
48 let ptr = self.data.get() as *const T;
49 let inner = unsafe { &*ptr };
50 f(inner)
51 })
52 }
53
54 /// Creates a critical section and grants temporary write access to the protected data.
55 pub fn write_lock<U>(&self, f: impl FnOnce(&mut T) -> U) -> U {
56 self.raw.write_lock(|| {
57 let ptr = self.data.get() as *mut T;
58 let inner = unsafe { &mut *ptr };
59 f(inner)
60 })
61 }
62}
63
64impl<R, T> RwLock<R, T> {
65 /// Creates a new read-write lock based on a pre-existing raw read-write lock.
66 ///
67 /// This allows creating a read-write lock in a constant context on stable Rust.
68 #[inline]
69 pub const fn const_new(raw_rwlock: R, val: T) -> RwLock<R, T> {
70 RwLock {
71 raw: raw_rwlock,
72 data: UnsafeCell::new(val),
73 }
74 }
75
76 /// Consumes this read-write lock, returning the underlying data.
77 #[inline]
78 pub fn into_inner(self) -> T {
79 self.data.into_inner()
80 }
81
82 /// Returns a mutable reference to the underlying data.
83 ///
84 /// Since this call borrows the `RwLock` mutably, no actual locking needs to
85 /// take place---the mutable borrow statically guarantees no locks exist.
86 #[inline]
87 pub fn get_mut(&mut self) -> &mut T {
88 unsafe { &mut *self.data.get() }
89 }
90}
91
92/// A read-write lock that allows borrowing data across executors and interrupts.
93///
94/// # Safety
95///
96/// This read-write lock is safe to share between different executors and interrupts.
97pub type CriticalSectionRwLock<T> = RwLock<raw_rwlock::CriticalSectionRawRwLock, T>;
98
99/// A read-write lock that allows borrowing data in the context of a single executor.
100///
101/// # Safety
102///
103/// **This Read-Write Lock is only safe within a single executor.**
104pub type NoopRwLock<T> = RwLock<raw_rwlock::NoopRawRwLock, T>;
105
106impl<T> RwLock<raw_rwlock::CriticalSectionRawRwLock, T> {
107 /// Borrows the data for the duration of the critical section
108 pub fn borrow<'cs>(&'cs self, _cs: critical_section::CriticalSection<'cs>) -> &'cs T {
109 let ptr = self.data.get() as *const T;
110 unsafe { &*ptr }
111 }
112}
113
114impl<T> RwLock<raw_rwlock::NoopRawRwLock, T> {
115 /// Borrows the data
116 #[allow(clippy::should_implement_trait)]
117 pub fn borrow(&self) -> &T {
118 let ptr = self.data.get() as *const T;
119 unsafe { &*ptr }
120 }
121}
122
123// ThreadModeRwLock does NOT use the generic read-write lock from above because it's special:
124// it's Send+Sync even if T: !Send. There's no way to do that without specialization (I think?).
125//
126// There's still a ThreadModeRawRwLock for use with the generic RwLock (handy with Channel, for example),
127// but that will require T: Send even though it shouldn't be needed.
128
129#[cfg(any(cortex_m, feature = "std"))]
130pub use thread_mode_rwlock::*;
131#[cfg(any(cortex_m, feature = "std"))]
132mod thread_mode_rwlock {
133 use super::*;
134
135 /// A "read-write lock" that only allows borrowing from thread mode.
136 ///
137 /// # Safety
138 ///
139 /// **This Read-Write Lock is only safe on single-core systems.**
140 ///
141 /// On multi-core systems, a `ThreadModeRwLock` **is not sufficient** to ensure exclusive access.
142 pub struct ThreadModeRwLock<T: ?Sized> {
143 inner: UnsafeCell<T>,
144 }
145
146 // NOTE: ThreadModeRwLock only allows borrowing from one execution context ever: thread mode.
147 // Therefore it cannot be used to send non-sendable stuff between execution contexts, so it can
148 // be Send+Sync even if T is not Send (unlike CriticalSectionRwLock)
149 unsafe impl<T: ?Sized> Sync for ThreadModeRwLock<T> {}
150 unsafe impl<T: ?Sized> Send for ThreadModeRwLock<T> {}
151
152 impl<T> ThreadModeRwLock<T> {
153 /// Creates a new read-write lock
154 pub const fn new(value: T) -> Self {
155 ThreadModeRwLock {
156 inner: UnsafeCell::new(value),
157 }
158 }
159 }
160
161 impl<T: ?Sized> ThreadModeRwLock<T> {
162 /// Lock the `ThreadModeRwLock` for reading, granting access to the data.
163 ///
164 /// # Panics
165 ///
166 /// This will panic if not currently running in thread mode.
167 pub fn read_lock<R>(&self, f: impl FnOnce(&T) -> R) -> R {
168 f(self.borrow())
169 }
170
171 /// Lock the `ThreadModeRwLock` for writing, granting access to the data.
172 ///
173 /// # Panics
174 ///
175 /// This will panic if not currently running in thread mode.
176 pub fn write_lock<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
177 f(self.borrow_mut())
178 }
179
180 /// Borrows the data
181 ///
182 /// # Panics
183 ///
184 /// This will panic if not currently running in thread mode.
185 pub fn borrow(&self) -> &T {
186 assert!(
187 raw_rwlock::in_thread_mode(),
188 "ThreadModeRwLock can only be borrowed from thread mode."
189 );
190 unsafe { &*self.inner.get() }
191 }
192
193 /// Mutably borrows the data
194 ///
195 /// # Panics
196 ///
197 /// This will panic if not currently running in thread mode.
198 pub fn borrow_mut(&self) -> &mut T {
199 assert!(
200 raw_rwlock::in_thread_mode(),
201 "ThreadModeRwLock can only be borrowed from thread mode."
202 );
203 unsafe { &mut *self.inner.get() }
204 }
205 }
206
207 impl<T: ?Sized> Drop for ThreadModeRwLock<T> {
208 fn drop(&mut self) {
209 // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so
210 // `drop` needs the same guarantees as `lock`. `ThreadModeRwLock<T>` is Send even if
211 // T isn't, so without this check a user could create a ThreadModeRwLock in thread mode,
212 // send it to interrupt context and drop it there, which would "send" a T even if T is not Send.
213 assert!(
214 raw_rwlock::in_thread_mode(),
215 "ThreadModeRwLock can only be dropped from thread mode."
216 );
217
218 // Drop of the inner `T` happens after this.
219 }
220 }
221} \ No newline at end of file
diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs
new file mode 100644
index 000000000..85e8374b5
--- /dev/null
+++ b/embassy-sync/src/blocking_rwlock/raw.rs
@@ -0,0 +1,159 @@
1//! Read-Write Lock primitives.
2//!
3//! This module provides a trait for read-write locks that can be used in different contexts.
4use core::marker::PhantomData;
5
6/// Raw read-write lock trait.
7///
8/// This read-write lock is "raw", which means it does not actually contain the protected data, it
9/// just implements the read-write lock mechanism. For most uses you should use [`super::RwLock`] instead,
10/// which is generic over a RawRwLock and contains the protected data.
11///
12/// Note that, unlike other read-write locks, implementations only guarantee no
13/// concurrent access from other threads: concurrent access from the current
14/// thread is allowed. For example, it's possible to lock the same read-write lock multiple times reentrantly.
15///
16/// Therefore, locking a `RawRwLock` is only enough to guarantee safe shared (`&`) access
17/// to the data, it is not enough to guarantee exclusive (`&mut`) access.
18///
19/// # Safety
20///
21/// RawRwLock implementations must ensure that, while locked, no other thread can lock
22/// the RawRwLock concurrently.
23///
24/// Unsafe code is allowed to rely on this fact, so incorrect implementations will cause undefined behavior.
25pub unsafe trait RawRwLock {
26 /// Create a new `RawRwLock` instance.
27 ///
28 /// This is a const instead of a method to allow creating instances in const context.
29 const INIT: Self;
30
31 /// Lock this `RawRwLock` for reading.
32 fn read_lock<R>(&self, f: impl FnOnce() -> R) -> R;
33
34 /// Lock this `RawRwLock` for writing.
35 fn write_lock<R>(&self, f: impl FnOnce() -> R) -> R;
36}
37
38/// A read-write lock that allows borrowing data across executors and interrupts.
39///
40/// # Safety
41///
42/// This read-write lock is safe to share between different executors and interrupts.
43pub struct CriticalSectionRawRwLock {
44 _phantom: PhantomData<()>,
45}
46unsafe impl Send for CriticalSectionRawRwLock {}
47unsafe impl Sync for CriticalSectionRawRwLock {}
48
49impl CriticalSectionRawRwLock {
50 /// Create a new `CriticalSectionRawRwLock`.
51 pub const fn new() -> Self {
52 Self { _phantom: PhantomData }
53 }
54}
55
56unsafe impl RawRwLock for CriticalSectionRawRwLock {
57 const INIT: Self = Self::new();
58
59 fn read_lock<R>(&self, f: impl FnOnce() -> R) -> R {
60 critical_section::with(|_| f())
61 }
62
63 fn write_lock<R>(&self, f: impl FnOnce() -> R) -> R {
64 critical_section::with(|_| f())
65 }
66}
67
68// ================
69
70/// A read-write lock that allows borrowing data in the context of a single executor.
71///
72/// # Safety
73///
74/// **This Read-Write Lock is only safe within a single executor.**
75pub struct NoopRawRwLock {
76 _phantom: PhantomData<*mut ()>,
77}
78
79unsafe impl Send for NoopRawRwLock {}
80
81impl NoopRawRwLock {
82 /// Create a new `NoopRawRwLock`.
83 pub const fn new() -> Self {
84 Self { _phantom: PhantomData }
85 }
86}
87
88unsafe impl RawRwLock for NoopRawRwLock {
89 const INIT: Self = Self::new();
90 fn read_lock<R>(&self, f: impl FnOnce() -> R) -> R {
91 f()
92 }
93
94 fn write_lock<R>(&self, f: impl FnOnce() -> R) -> R {
95 f()
96 }
97}
98
99// ================
100
101#[cfg(any(cortex_m, feature = "std"))]
102mod thread_mode {
103 use super::*;
104
105 /// A "read-write lock" that only allows borrowing from thread mode.
106 ///
107 /// # Safety
108 ///
109 /// **This Read-Write Lock is only safe on single-core systems.**
110 ///
111 /// On multi-core systems, a `ThreadModeRawRwLock` **is not sufficient** to ensure exclusive access.
112 pub struct ThreadModeRawRwLock {
113 _phantom: PhantomData<()>,
114 }
115
116 unsafe impl Send for ThreadModeRawRwLock {}
117 unsafe impl Sync for ThreadModeRawRwLock {}
118
119 impl ThreadModeRawRwLock {
120 /// Create a new `ThreadModeRawRwLock`.
121 pub const fn new() -> Self {
122 Self { _phantom: PhantomData }
123 }
124 }
125
126 unsafe impl RawRwLock for ThreadModeRawRwLock {
127 const INIT: Self = Self::new();
128 fn read_lock<R>(&self, f: impl FnOnce() -> R) -> R {
129 assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode.");
130
131 f()
132 }
133
134 fn write_lock<R>(&self, f: impl FnOnce() -> R) -> R {
135 assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode.");
136
137 f()
138 }
139 }
140
141 impl Drop for ThreadModeRawRwLock {
142 fn drop(&mut self) {
143 assert!(
144 in_thread_mode(),
145 "ThreadModeRwLock can only be dropped from thread mode."
146 );
147 }
148 }
149
150 pub(crate) fn in_thread_mode() -> bool {
151 #[cfg(feature = "std")]
152 return Some("main") == std::thread::current().name();
153
154 #[cfg(not(feature = "std"))]
155 return unsafe { (0xE000ED04 as *const u32).read_volatile() } & 0x1FF == 0;
156 }
157}
158#[cfg(any(cortex_m, feature = "std"))]
159pub use thread_mode::*; \ No newline at end of file
diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs
index 5d91b4d9c..b9ead5f79 100644
--- a/embassy-sync/src/lib.rs
+++ b/embassy-sync/src/lib.rs
@@ -11,6 +11,7 @@ pub(crate) mod fmt;
11mod ring_buffer; 11mod ring_buffer;
12 12
13pub mod blocking_mutex; 13pub mod blocking_mutex;
14pub mod blocking_rwlock;
14pub mod channel; 15pub mod channel;
15pub mod lazy_lock; 16pub mod lazy_lock;
16pub mod mutex; 17pub mod mutex;