1#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
5#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
6#![cfg_attr(xtensa, feature(asm_experimental_arch))]
7#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
8#![cfg_attr(
10 semver_checks,
11 allow(rustdoc::private_intra_doc_links, rustdoc::broken_intra_doc_links)
12)]
13#![no_std]
14
15mod fmt;
17
18use core::{cell::UnsafeCell, marker::PhantomData};
19
20pub mod raw;
21
22use raw::{RawLock, SingleCoreInterruptLock};
23
24#[derive(Clone, Copy, Debug)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39pub struct RestoreState(u32, PhantomData<*const ()>);
40
41impl RestoreState {
42 const REENTRY_FLAG: u32 = 1 << 31;
43
44 pub const unsafe fn new(inner: u32) -> Self {
50 Self(inner, PhantomData)
51 }
52
53 pub const fn invalid() -> Self {
58 Self(0, PhantomData)
59 }
60
61 #[inline]
62 fn mark_reentry(&mut self) {
63 self.0 |= Self::REENTRY_FLAG;
64 }
65
66 #[inline]
67 fn is_reentry(self) -> bool {
68 self.0 & Self::REENTRY_FLAG != 0
69 }
70
71 #[inline]
73 pub fn inner(self) -> u32 {
74 self.0
75 }
76}
77
78#[cfg(single_core)]
79mod single_core {
80 use core::cell::Cell;
81
82 #[repr(transparent)]
83 pub(super) struct LockedState {
84 locked: Cell<bool>,
85 }
86
87 impl LockedState {
88 pub const fn new() -> Self {
89 Self {
90 locked: Cell::new(false),
91 }
92 }
93
94 #[inline]
95 pub fn lock(&self, lock: &impl crate::RawLock) -> crate::RestoreState {
96 let mut tkn = unsafe { lock.enter() };
97 let was_locked = self.locked.replace(true);
98 if was_locked {
99 tkn.mark_reentry();
100 }
101 tkn
102 }
103
104 #[inline]
109 pub unsafe fn unlock(&self) {
110 self.locked.set(false)
111 }
112 }
113}
114
115#[cfg(multi_core)]
116mod multi_core {
117 use core::sync::atomic::{AtomicUsize, Ordering};
118
119 const UNUSED_THREAD_ID_VALUE: usize = 0x100;
122
123 #[inline]
124 fn thread_id() -> usize {
125 cfg_select! {
127 all(multi_core, riscv) => riscv::register::mhartid::read(),
128 all(multi_core, xtensa) => (xtensa_lx::get_processor_id() & 0x2000) as usize,
129 _ => 0,
130 }
131 }
132
133 #[repr(transparent)]
134 pub(super) struct LockedState {
135 owner: AtomicUsize,
136 }
137
138 impl LockedState {
139 #[inline]
140 pub const fn new() -> Self {
141 Self {
142 owner: AtomicUsize::new(UNUSED_THREAD_ID_VALUE),
143 }
144 }
145
146 #[inline]
147 pub fn lock(&self, lock: &impl crate::RawLock) -> crate::RestoreState {
148 let try_lock = || {
157 let mut tkn = unsafe { lock.enter() };
158
159 let current_thread_id = thread_id();
160
161 let try_lock_result = self
162 .owner
163 .compare_exchange(
164 UNUSED_THREAD_ID_VALUE,
165 current_thread_id,
166 Ordering::Acquire,
167 Ordering::Relaxed,
168 )
169 .map(|_| ());
170
171 match try_lock_result {
172 Ok(()) => Some(tkn),
173 Err(owner) if owner == current_thread_id => {
174 tkn.mark_reentry();
175 Some(tkn)
176 }
177 Err(_) => {
178 unsafe { lock.exit(tkn) };
179 None
180 }
181 }
182 };
183
184 loop {
185 if let Some(token) = try_lock() {
186 return token;
187 }
188 }
189 }
190
191 #[inline]
196 pub unsafe fn unlock(&self) {
197 #[cfg(debug_assertions)]
198 if self.owner.load(Ordering::Relaxed) != thread_id() {
199 panic_attempt_unlock_not_owned();
200 }
201 self.owner.store(UNUSED_THREAD_ID_VALUE, Ordering::Release);
202 }
203 }
204
205 #[cfg(debug_assertions)]
206 #[inline(never)]
207 #[cold]
208 fn panic_attempt_unlock_not_owned() -> ! {
209 panic!("tried to unlock a mutex locked on a different thread");
210 }
211}
212
213#[cfg(multi_core)]
214use multi_core::LockedState;
215#[cfg(single_core)]
216use single_core::LockedState;
217
218pub struct GenericRawMutex<L: RawLock> {
221 lock: L,
222 inner: LockedState,
223}
224
225unsafe impl<L: RawLock> Sync for GenericRawMutex<L> {}
227
228impl<L: RawLock> GenericRawMutex<L> {
229 pub const fn new(lock: L) -> Self {
231 Self {
232 lock,
233 inner: LockedState::new(),
234 }
235 }
236
237 #[inline]
245 unsafe fn acquire(&self) -> RestoreState {
246 self.inner.lock(&self.lock)
247 }
248
249 #[inline]
257 unsafe fn release(&self, token: RestoreState) {
258 if !token.is_reentry() {
259 unsafe {
260 self.inner.unlock();
261
262 self.lock.exit(token)
263 }
264 }
265 }
266
267 #[inline]
272 pub fn lock_non_reentrant<R>(&self, f: impl FnOnce() -> R) -> R {
273 let _token = LockGuard::new_non_reentrant(self);
274 f()
275 }
276
277 #[inline]
279 pub fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
280 let _token = LockGuard::new_reentrant(self);
281 f()
282 }
283}
284
285#[cfg_attr(
289 multi_core,
290 doc = r#"It needs a bit of memory, but it does not take a global critical
291 section, making it preferrable for use in multi-core systems."#
292)]
293pub struct RawMutex {
294 inner: GenericRawMutex<SingleCoreInterruptLock>,
295}
296
297impl Default for RawMutex {
298 #[inline]
299 fn default() -> Self {
300 Self::new()
301 }
302}
303
304impl RawMutex {
305 #[inline]
307 pub const fn new() -> Self {
308 Self {
309 inner: GenericRawMutex::new(SingleCoreInterruptLock),
310 }
311 }
312
313 #[inline]
321 pub unsafe fn acquire(&self) -> RestoreState {
322 unsafe { self.inner.acquire() }
323 }
324
325 #[inline]
333 pub unsafe fn release(&self, token: RestoreState) {
334 unsafe {
335 self.inner.release(token);
336 }
337 }
338
339 #[inline]
344 pub fn lock_non_reentrant<R>(&self, f: impl FnOnce() -> R) -> R {
345 self.inner.lock_non_reentrant(f)
346 }
347
348 #[inline]
350 pub fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
351 self.inner.lock(f)
352 }
353}
354
355unsafe impl embassy_sync_06::blocking_mutex::raw::RawMutex for RawMutex {
356 #[allow(clippy::declare_interior_mutable_const)]
357 const INIT: Self = Self::new();
358
359 fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
360 self.inner.lock(f)
361 }
362}
363
364unsafe impl embassy_sync_07::blocking_mutex::raw::RawMutex for RawMutex {
365 #[allow(clippy::declare_interior_mutable_const)]
366 const INIT: Self = Self::new();
367
368 fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
369 self.inner.lock(f)
370 }
371}
372
373unsafe impl embassy_sync_08::blocking_mutex::raw::RawMutex for RawMutex {
374 #[allow(clippy::declare_interior_mutable_const)]
375 const INIT: Self = Self::new();
376
377 fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
378 self.inner.lock(f)
379 }
380}
381
382pub struct NonReentrantMutex<T> {
387 lock_state: RawMutex,
388 data: UnsafeCell<T>,
389}
390
391impl<T> NonReentrantMutex<T> {
392 pub const fn new(data: T) -> Self {
394 Self {
395 lock_state: RawMutex::new(),
396 data: UnsafeCell::new(data),
397 }
398 }
399
400 pub fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
404 self.lock_state
405 .lock_non_reentrant(|| f(unsafe { &mut *self.data.get() }))
406 }
407}
408
409unsafe impl<T: Send> Send for NonReentrantMutex<T> {}
410unsafe impl<T: Send> Sync for NonReentrantMutex<T> {}
411
412struct LockGuard<'a, L: RawLock> {
413 lock: &'a GenericRawMutex<L>,
414 token: RestoreState,
415}
416
417impl<'a, L: RawLock> LockGuard<'a, L> {
418 #[inline]
419 fn new_non_reentrant(lock: &'a GenericRawMutex<L>) -> Self {
420 let this = Self::new_reentrant(lock);
421 if this.token.is_reentry() {
422 panic_lock_not_reentrant();
423 }
424 this
425 }
426
427 #[inline]
428 fn new_reentrant(lock: &'a GenericRawMutex<L>) -> Self {
429 let token = unsafe {
430 lock.acquire()
434 };
435
436 Self { lock, token }
437 }
438}
439
440impl<L: RawLock> Drop for LockGuard<'_, L> {
441 fn drop(&mut self) {
442 unsafe { self.lock.release(self.token) };
443 }
444}
445
446#[inline(never)]
447#[cold]
448fn panic_lock_not_reentrant() -> ! {
449 panic!("lock is not reentrant");
450}