#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![cfg_attr(xtensa, feature(asm_experimental_arch))]
#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
#![cfg_attr(
semver_checks,
allow(rustdoc::private_intra_doc_links, rustdoc::broken_intra_doc_links)
)]
#![no_std]
mod fmt;
use core::{cell::UnsafeCell, marker::PhantomData};
pub mod raw;
use raw::{RawLock, SingleCoreInterruptLock};
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RestoreState(u32, PhantomData<*const ()>);
impl RestoreState {
const REENTRY_FLAG: u32 = 1 << 31;
pub const unsafe fn new(inner: u32) -> Self {
Self(inner, PhantomData)
}
pub const fn invalid() -> Self {
Self(0, PhantomData)
}
#[inline]
fn mark_reentry(&mut self) {
self.0 |= Self::REENTRY_FLAG;
}
#[inline]
fn is_reentry(self) -> bool {
self.0 & Self::REENTRY_FLAG != 0
}
#[inline]
pub fn inner(self) -> u32 {
self.0
}
}
#[cfg(single_core)]
mod single_core {
use core::cell::Cell;
#[repr(transparent)]
pub(super) struct LockedState {
locked: Cell<bool>,
}
impl LockedState {
pub const fn new() -> Self {
Self {
locked: Cell::new(false),
}
}
#[inline]
pub fn lock(&self, lock: &impl crate::RawLock) -> crate::RestoreState {
let mut tkn = unsafe { lock.enter() };
let was_locked = self.locked.replace(true);
if was_locked {
tkn.mark_reentry();
}
tkn
}
#[inline]
pub unsafe fn unlock(&self) {
self.locked.set(false)
}
}
}
#[cfg(multi_core)]
mod multi_core {
use core::sync::atomic::{AtomicUsize, Ordering};
const UNUSED_THREAD_ID_VALUE: usize = 0x100;
#[inline]
fn thread_id() -> usize {
cfg_if::cfg_if! {
if #[cfg(all(multi_core, riscv))] {
riscv::register::mhartid::read()
} else if #[cfg(all(multi_core, xtensa))] {
(xtensa_lx::get_processor_id() & 0x2000) as usize
} else {
0
}
}
}
#[repr(transparent)]
pub(super) struct LockedState {
owner: AtomicUsize,
}
impl LockedState {
#[inline]
pub const fn new() -> Self {
Self {
owner: AtomicUsize::new(UNUSED_THREAD_ID_VALUE),
}
}
#[inline]
pub fn lock(&self, lock: &impl crate::RawLock) -> crate::RestoreState {
let try_lock = || {
let mut tkn = unsafe { lock.enter() };
let current_thread_id = thread_id();
let try_lock_result = self
.owner
.compare_exchange(
UNUSED_THREAD_ID_VALUE,
current_thread_id,
Ordering::Acquire,
Ordering::Relaxed,
)
.map(|_| ());
match try_lock_result {
Ok(()) => Some(tkn),
Err(owner) if owner == current_thread_id => {
tkn.mark_reentry();
Some(tkn)
}
Err(_) => {
unsafe { lock.exit(tkn) };
None
}
}
};
loop {
if let Some(token) = try_lock() {
return token;
}
}
}
#[inline]
pub unsafe fn unlock(&self) {
#[cfg(debug_assertions)]
if self.owner.load(Ordering::Relaxed) != thread_id() {
panic_attempt_unlock_not_owned();
}
self.owner.store(UNUSED_THREAD_ID_VALUE, Ordering::Release);
}
}
#[cfg(debug_assertions)]
#[inline(never)]
#[cold]
fn panic_attempt_unlock_not_owned() -> ! {
panic!("tried to unlock a mutex locked on a different thread");
}
}
#[cfg(multi_core)]
use multi_core::LockedState;
#[cfg(single_core)]
use single_core::LockedState;
pub struct GenericRawMutex<L: RawLock> {
lock: L,
inner: LockedState,
}
unsafe impl<L: RawLock> Sync for GenericRawMutex<L> {}
impl<L: RawLock> GenericRawMutex<L> {
pub const fn new(lock: L) -> Self {
Self {
lock,
inner: LockedState::new(),
}
}
#[inline]
unsafe fn acquire(&self) -> RestoreState {
self.inner.lock(&self.lock)
}
#[inline]
unsafe fn release(&self, token: RestoreState) {
if !token.is_reentry() {
unsafe {
self.inner.unlock();
self.lock.exit(token)
}
}
}
#[inline]
pub fn lock_non_reentrant<R>(&self, f: impl FnOnce() -> R) -> R {
let _token = LockGuard::new_non_reentrant(self);
f()
}
#[inline]
pub fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
let _token = LockGuard::new_reentrant(self);
f()
}
}
#[cfg_attr(
multi_core,
doc = r#"It needs a bit of memory, but it does not take a global critical
section, making it preferrable for use in multi-core systems."#
)]
pub struct RawMutex {
inner: GenericRawMutex<SingleCoreInterruptLock>,
}
impl Default for RawMutex {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl RawMutex {
#[inline]
pub const fn new() -> Self {
Self {
inner: GenericRawMutex::new(SingleCoreInterruptLock),
}
}
#[inline]
pub unsafe fn acquire(&self) -> RestoreState {
unsafe { self.inner.acquire() }
}
#[inline]
pub unsafe fn release(&self, token: RestoreState) {
unsafe {
self.inner.release(token);
}
}
#[inline]
pub fn lock_non_reentrant<R>(&self, f: impl FnOnce() -> R) -> R {
self.inner.lock_non_reentrant(f)
}
#[inline]
pub fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
self.inner.lock(f)
}
}
unsafe impl embassy_sync_06::blocking_mutex::raw::RawMutex for RawMutex {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Self::new();
fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
self.inner.lock(f)
}
}
unsafe impl embassy_sync_07::blocking_mutex::raw::RawMutex for RawMutex {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Self::new();
fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
self.inner.lock(f)
}
}
unsafe impl embassy_sync_08::blocking_mutex::raw::RawMutex for RawMutex {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Self::new();
fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
self.inner.lock(f)
}
}
pub struct NonReentrantMutex<T> {
lock_state: RawMutex,
data: UnsafeCell<T>,
}
impl<T> NonReentrantMutex<T> {
pub const fn new(data: T) -> Self {
Self {
lock_state: RawMutex::new(),
data: UnsafeCell::new(data),
}
}
pub fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
self.lock_state
.lock_non_reentrant(|| f(unsafe { &mut *self.data.get() }))
}
}
unsafe impl<T: Send> Send for NonReentrantMutex<T> {}
unsafe impl<T: Send> Sync for NonReentrantMutex<T> {}
struct LockGuard<'a, L: RawLock> {
lock: &'a GenericRawMutex<L>,
token: RestoreState,
}
impl<'a, L: RawLock> LockGuard<'a, L> {
#[inline]
fn new_non_reentrant(lock: &'a GenericRawMutex<L>) -> Self {
let this = Self::new_reentrant(lock);
if this.token.is_reentry() {
panic_lock_not_reentrant();
}
this
}
#[inline]
fn new_reentrant(lock: &'a GenericRawMutex<L>) -> Self {
let token = unsafe {
lock.acquire()
};
Self { lock, token }
}
}
impl<L: RawLock> Drop for LockGuard<'_, L> {
fn drop(&mut self) {
unsafe { self.lock.release(self.token) };
}
}
#[inline(never)]
#[cold]
fn panic_lock_not_reentrant() -> ! {
panic!("lock is not reentrant");
}