use core::{
cell::UnsafeCell,
fmt,
ops::{Deref, DerefMut},
sync::atomic::{AtomicBool, Ordering},
};
use r_efi::efi;
use crate::events::{raise_tpl, restore_tpl};
pub struct TplMutex<T: ?Sized> {
tpl_lock_level: efi::Tpl,
lock: AtomicBool,
name: &'static str,
data: UnsafeCell<T>,
}
pub struct TplGuard<'a, T: ?Sized + 'a> {
release_tpl: efi::Tpl,
mutex: &'a TplMutex<T>,
}
unsafe impl<T: ?Sized + Send> Sync for TplMutex<T> {}
unsafe impl<T: ?Sized + Send> Send for TplMutex<T> {}
unsafe impl<T: ?Sized + Sync> Sync for TplGuard<'_, T> {}
unsafe impl<T: ?Sized + Send> Send for TplGuard<'_, T> {}
impl<T> TplMutex<T> {
pub const fn new(tpl_lock_level: efi::Tpl, data: T, name: &'static str) -> Self {
Self { tpl_lock_level, lock: AtomicBool::new(false), data: UnsafeCell::new(data), name }
}
}
impl<T: ?Sized> TplMutex<T> {
pub fn lock(&self) -> TplGuard<'_, T> {
self.try_lock().unwrap_or_else(|| panic!("Re-entrant locks for {:?} not permitted.", self.name))
}
pub fn try_lock(&self) -> Option<TplGuard<'_, T>> {
let release_tpl = raise_tpl(self.tpl_lock_level);
if self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok() {
Some(TplGuard { release_tpl, mutex: self })
} else {
restore_tpl(release_tpl);
None
}
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for TplMutex<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.try_lock() {
Some(guard) => write!(
f,
"TplMutex {{ lock_tpl: {:x?}, release_tpl: {:x?}, data: ",
self.tpl_lock_level, guard.release_tpl
)
.and_then(|()| (*guard).fmt(f))
.and_then(|()| write!(f, " }}")),
None => write!(f, "TplMutex {{ lock_tpl: {:x?}, data: <locked> }}", self.tpl_lock_level),
}
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for TplGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<T: ?Sized + fmt::Display> fmt::Display for TplGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl<'a, T: ?Sized> Deref for TplGuard<'a, T> {
type Target = T;
fn deref(&self) -> &'a T {
unsafe { self.mutex.data.get().as_ref().expect("TplMutex data pointer should not be null") }
}
}
impl<'a, T: ?Sized> DerefMut for TplGuard<'a, T> {
fn deref_mut(&mut self) -> &'a mut T {
unsafe { self.mutex.data.get().as_mut().expect("TplMutex data pointer should not be null") }
}
}
impl<T: ?Sized> Drop for TplGuard<'_, T> {
fn drop(&mut self) {
self.mutex.lock.store(false, Ordering::Release);
restore_tpl(self.release_tpl);
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use crate::{
events::{raise_tpl, restore_tpl},
test_support,
};
use super::TplMutex;
use r_efi::efi;
fn with_reset_state<F: Fn() + std::panic::RefUnwindSafe>(f: F) {
let result = crate::test_support::with_global_lock(|| {
test_support::init_test_logger();
raise_tpl(efi::TPL_HIGH_LEVEL);
restore_tpl(efi::TPL_APPLICATION);
let _guard = test_support::StateGuard::new(|| {
raise_tpl(efi::TPL_HIGH_LEVEL);
restore_tpl(efi::TPL_APPLICATION);
});
f();
});
match result {
Ok(()) => {}
Err(e) => {
std::panic::resume_unwind(e);
}
}
}
#[test]
fn test_tpl_mutex_basic() {
with_reset_state(|| {
let lock = TplMutex::new(efi::TPL_NOTIFY, 42, "test_lock");
{
let guard = lock.lock();
assert_eq!(*guard, 42);
}
{
let mut guard = lock.lock();
*guard = 43;
}
{
let guard = lock.lock();
assert_eq!(*guard, 43);
}
});
}
#[test]
#[should_panic(expected = "Re-entrant locks for \"test_lock\" not permitted.")]
fn test_tpl_mutex_reentrant() {
with_reset_state(|| {
let lock = TplMutex::new(efi::TPL_NOTIFY, 42, "test_lock");
let _guard1 = lock.lock();
let _guard2 = lock.lock(); });
}
#[test]
fn test_tpl_mutex_try_lock() {
with_reset_state(|| {
let lock = TplMutex::new(efi::TPL_NOTIFY, 42, "test_lock");
{
let guard1 = lock.try_lock().expect("Failed to acquire lock");
assert_eq!(*guard1, 42);
let guard2 = lock.try_lock();
assert!(guard2.is_none(), "Should not be able to acquire lock while already held");
}
{
let guard3 = lock.try_lock().expect("Failed to acquire lock after release");
assert_eq!(*guard3, 42);
}
});
}
#[test]
fn test_tpl_mutex_debug() {
with_reset_state(|| {
let lock = TplMutex::new(efi::TPL_NOTIFY, 42, "test_lock");
let debug_str = format!("{:?}", lock);
assert_eq!(debug_str, "TplMutex { lock_tpl: 10, release_tpl: 4, data: 42 }");
let _guard = lock.lock();
let debug_str_locked = format!("{:?}", lock);
assert_eq!(debug_str_locked, "TplMutex { lock_tpl: 10, data: <locked> }");
});
}
#[test]
fn test_tpl_mutex_guard_debug_display() {
with_reset_state(|| {
let lock = TplMutex::new(efi::TPL_NOTIFY, 42, "test_lock");
{
let guard = lock.lock();
let debug_str = format!("{:?}", guard);
assert_eq!(debug_str, "42");
let display_str = format!("{}", guard);
assert_eq!(display_str, "42");
}
});
}
}