use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};
use crate::kernel::queue::{
queueQUEUE_TYPE_MUTEX, queueSEND_TO_BACK, xQueueCreateMutexStatic, xQueueGenericSend,
xQueueSemaphoreTake, QueueHandle_t, StaticQueue_t,
};
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
use crate::kernel::queue::{vQueueDelete, xQueueCreateMutex};
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
use crate::sync::is_in_isr;
use crate::sync::task::TaskContext;
use crate::sync::{assert_can_block, assert_task_context};
use crate::types::*;
pub struct Mutex<T> {
handle: QueueHandle_t,
data: UnsafeCell<T>,
owns_handle: bool,
}
unsafe impl<T: Send> Sync for Mutex<T> {}
unsafe impl<T: Send> Send for Mutex<T> {}
impl<T> Mutex<T> {
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
pub fn new(_context: &TaskContext, value: T) -> Option<Self> {
assert_task_context("Mutex::new");
let handle = unsafe { xQueueCreateMutex(queueQUEUE_TYPE_MUTEX) };
if handle.is_null() {
None
} else {
Some(Self {
handle,
data: UnsafeCell::new(value),
owns_handle: true,
})
}
}
pub fn new_static(
_context: &TaskContext,
value: T,
mutex_buffer: &'static mut StaticQueue_t,
) -> Option<Self> {
assert_task_context("Mutex::new_static");
let handle = unsafe {
xQueueCreateMutexStatic(queueQUEUE_TYPE_MUTEX, mutex_buffer as *mut StaticQueue_t)
};
if handle.is_null() {
None
} else {
Some(Self {
handle,
data: UnsafeCell::new(value),
owns_handle: true,
})
}
}
pub fn lock(&self, context: &TaskContext) -> Option<MutexGuard<'_, T>> {
self.lock_timeout(context, portMAX_DELAY)
}
pub fn try_lock(&self, context: &TaskContext) -> Option<MutexGuard<'_, T>> {
self.lock_timeout(context, 0)
}
pub fn lock_timeout(
&self,
_context: &TaskContext,
ticks: TickType_t,
) -> Option<MutexGuard<'_, T>> {
assert_can_block("Mutex::lock_timeout", ticks);
let result = unsafe { xQueueSemaphoreTake(self.handle, ticks) };
if result == pdTRUE {
Some(MutexGuard {
mutex: self,
_not_send: core::marker::PhantomData,
})
} else {
None
}
}
pub unsafe fn raw_handle(&self) -> QueueHandle_t {
self.handle
}
pub fn into_raw(mut self) -> QueueHandle_t {
self.owns_handle = false;
self.handle
}
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
pub fn delete(mut self, _context: &TaskContext) {
assert_task_context("Mutex::delete");
unsafe { vQueueDelete(self.handle) };
self.owns_handle = false;
}
fn release(&self) {
let result =
unsafe { xQueueGenericSend(self.handle, core::ptr::null(), 0, queueSEND_TO_BACK) };
assert_eq!(result, pdTRUE, "FreeRTOS mutex release failed");
}
}
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
impl<T> Drop for Mutex<T> {
fn drop(&mut self) {
if self.owns_handle && !is_in_isr() {
unsafe { vQueueDelete(self.handle) };
self.owns_handle = false;
}
}
}
pub struct MutexGuard<'a, T> {
mutex: &'a Mutex<T>,
_not_send: core::marker::PhantomData<*const ()>,
}
impl<T> Deref for MutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.mutex.data.get() }
}
}
impl<T> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.data.get() }
}
}
impl<T> Drop for MutexGuard<'_, T> {
fn drop(&mut self) {
self.mutex.release();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failed_acquisition_never_constructs_a_guard() {
let storage = std::boxed::Box::leak(std::boxed::Box::new(StaticQueue_t::new()));
let context = unsafe { TaskContext::assume() };
let mutex =
Mutex::new_static(&context, 7_u32, storage).expect("static mutex creation failed");
let first = mutex
.try_lock(&context)
.expect("initial mutex acquisition failed");
assert!(mutex.try_lock(&context).is_none());
drop(first);
assert!(mutex.try_lock(&context).is_some());
}
#[test]
fn mutex_send_sync_tracks_protected_value_send() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Mutex<u32>>();
}
}