blob: e6b7c81358168666ce5af23e0d1a949e62b08efd [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};
use pw_status::Result;
use pw_time_core::Instant;
use crate::Kernel;
use crate::scheduler::{Thread, WaitQueueLock, WaitType};
const MUTEX_DEBUG: bool = false;
macro_rules! mutex_debug {
($($args:expr),*) => {{
log_if::debug_if!(MUTEX_DEBUG, $($args),*)
}}
}
struct MutexState {
count: usize,
holder_thread_id: usize,
}
pub struct Mutex<K: Kernel, T> {
// An future optimization can be made by keeping an atomic count outside of
// the spinlock. However, not all architectures support atomics so a pure
// SchedLock based approach will always be needed.
state: WaitQueueLock<K, MutexState>,
inner: UnsafeCell<T>,
}
unsafe impl<K: Kernel, T> Sync for Mutex<K, T> {}
unsafe impl<K: Kernel, T> Send for Mutex<K, T> {}
pub struct MutexGuard<'lock, K: Kernel, T> {
lock: &'lock Mutex<K, T>,
}
impl<K: Kernel, T> Deref for MutexGuard<'_, K, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.inner.get() }
}
}
impl<K: Kernel, T> DerefMut for MutexGuard<'_, K, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.inner.get() }
}
}
impl<K: Kernel, T> Drop for MutexGuard<'_, K, T> {
fn drop(&mut self) {
self.lock.unlock();
}
}
impl<K: Kernel, T> Mutex<K, T> {
pub const fn new(kernel: K, initial_value: T) -> Self {
Self {
state: WaitQueueLock::new(
kernel,
MutexState {
count: 0,
holder_thread_id: Thread::<K>::null_id(),
},
),
inner: UnsafeCell::new(initial_value),
}
}
pub fn lock(&self) -> MutexGuard<'_, K, T> {
let mut state = self.state.lock();
pw_assert::ne!(
state.holder_thread_id as usize,
state.sched().current_thread_id() as usize,
"Mutex attempted to be locked by holding thread"
);
#[allow(clippy::needless_else)]
if let Some(val) = state.count.checked_add(1) {
state.count = val;
} else {
pw_assert::debug_assert!(false);
}
// TODO - konkers: investigate using core::intrinsics::unlikely() or
// core::hint::unlikely()
if state.count > 1 {
mutex_debug!(
"Mutex {:#010x}: lock wait by thread '{}' ({:#010x})",
&raw const *self as usize,
state.sched().current_thread_name() as &str,
state.sched().current_thread_id() as usize
);
// Mutexes use uninterruptible waits because cleaning up a terminating
// thread may involve aquisiation of mutex protected resources.
let res;
(state, res) = state.wait(WaitType::NonInterruptible);
pw_assert::debug_assert!(res.is_ok());
}
mutex_debug!(
"Mutex {:#010x}: lock acquired by thread '{}' ({:#010x})",
&raw const *self as usize,
state.sched().current_thread_name() as &str,
state.sched().current_thread_id() as usize
);
state.holder_thread_id = state.sched().current_thread_id();
// At this point we have exclusive access to `self.inner`.
MutexGuard { lock: self }
}
// TODO - konkers: Investigate combining with lock().
pub fn lock_until(&self, deadline: Instant<K::Clock>) -> Result<MutexGuard<'_, K, T>> {
let mut state = self.state.lock();
#[allow(clippy::needless_else)]
if let Some(val) = state.count.checked_add(1) {
state.count = val;
} else {
pw_assert::debug_assert!(false);
}
// TODO - konkers: investigate using core::intrinsics::unlikely() or
// core::hint::unlikely()
if state.count > 1 {
let result;
mutex_debug!(
"Mutex {:#010x}: lock_until({}) wait by thread '{}' ({:#010x})",
&raw const *self as usize,
deadline.ticks() as u64,
state.sched().current_thread_name() as &str,
state.sched().current_thread_id() as usize
);
// Mutexes use uninterruptible waits because cleaning up a terminating
// thread may involve aquisiation of mutex protected resources.
(state, result) = state.wait_until(WaitType::NonInterruptible, deadline);
if let Err(e) = result {
mutex_debug!(
"Mutex {:#010x}: lock_until error: {} for thread '{}' ({:#010x})",
&raw const *self as usize,
e as u32,
state.sched().current_thread_name() as &str,
state.sched().current_thread_id() as usize
);
if let Some(val) = state.count.checked_sub(1) {
state.count = val;
} else {
// use assert not debug_assert, as it's possible a
// real bug could trigger this assert,
pw_assert::assert!(false)
}
return Err(e);
}
}
mutex_debug!(
"Mutex {:#010x}: lock_until acquired by thread '{}' ({:#010x})",
&raw const *self as usize,
state.sched().current_thread_name() as &str,
state.sched().current_thread_id() as usize
);
state.holder_thread_id = state.sched().current_thread_id();
// At this point we have exclusive access to `self.inner`.
Ok(MutexGuard { lock: self })
}
fn unlock(&self) {
let mut state = self.state.lock();
pw_assert::assert!(state.count > 0);
pw_assert::eq!(
state.holder_thread_id as usize,
state.sched().current_thread_id() as usize
);
state.holder_thread_id = Thread::<K>::null_id();
state.count -= 1;
// TODO - konkers: investigate using core::intrinsics::unlikely() or
// core::hint::unlikely()
if state.count > 0 {
let _ = state.wake_one();
}
}
}