Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion freertos-rust/src/isr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl InterruptContext {
}
}

pub unsafe fn get_task_field_mut(&self) -> FreeRtosBaseTypeMutPtr {
pub fn get_task_field_mut(&self) -> FreeRtosBaseTypeMutPtr {
self.x_higher_priority_task_woken as *mut _
}
}
Expand Down
96 changes: 61 additions & 35 deletions freertos-rust/src/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,15 @@ where
}
}

impl<T> MutexImpl<T, MutexNormal> {
/// Create a new mutex with the given inner value
pub fn new(t: T) -> Result<Self, FreeRtosError> {
Ok(MutexImpl {
mutex: MutexNormal::create()?,
data: UnsafeCell::new(t),
})
}
}

impl<T> MutexImpl<T, MutexRecursive> {
/// Create a new recursive mutex with the given inner value
pub fn new(t: T) -> Result<Self, FreeRtosError> {
Ok(MutexImpl {
mutex: MutexRecursive::create()?,
data: UnsafeCell::new(t),
})
}
}

impl<T, M> MutexImpl<T, M>
where
M: MutexInnerImpl,
{
/// Create a new mutex with the given inner value
pub fn new(value: T) -> Result<Self, FreeRtosError> {
Ok(Self::from_parts(M::create()?, value))
}

/// Try to obtain a lock and mutable access to our inner value
pub fn lock<D: DurationTicks>(&self, max_wait: D) -> Result<MutexGuard<T, M>, FreeRtosError> {
self.mutex.take(max_wait)?;
Expand All @@ -62,23 +47,35 @@ where

/// Consume the mutex and return its inner value
pub fn into_inner(self) -> T {
// Manually deconstruct the structure, because it implements Drop
// and we cannot move the data value out of it.
unsafe {
let (mutex, data) = {
let Self {
ref mutex,
ref data,
} = self;
(ptr::read(mutex), ptr::read(data))
};
mem::forget(self);

drop(mutex);

data.into_inner()
self.into_parts().1
}

/// Get mutable reference to inner value.
///
/// This method does not lock the mutex because mutable reference guarantees exclusive access.
pub fn get_mut(&mut self) -> &mut T {
self.data.get_mut()
}

/// Create owning mutex from non-owning mutex and inner value.
///
/// It is safe to pass an already locked `mutex` although it is not recommended.
pub fn from_parts(mutex: M, value: T) -> Self {
Self {
mutex,
data: UnsafeCell::new(value),
}
}

/// Split owning mutex into non-owning mutex and inner value.
pub fn into_parts(self) -> (M, T) {
(self.mutex, self.data.into_inner())
}

/// Get mutable reference to inner non-owning mutex.
pub fn inner_mutex_mut(&mut self) -> &mut M {
&mut self.mutex
}
}

/// Holds the mutex until we are dropped
Expand Down Expand Up @@ -126,6 +123,15 @@ where
fn create() -> Result<Self, FreeRtosError>;
fn take<D: DurationTicks>(&self, max_wait: D) -> Result<(), FreeRtosError>;
fn give(&self);

/// # Safety
///
/// `handle` must be a valid FreeRTOS mutex handle.
///
/// The type of `handle` (normal or recursive mutex) must match the type
/// of instance being created ([`MutexNormal`] or [`MutexRecursive`] respectively).
unsafe fn from_raw_handle(handle: FreeRtosSemaphoreHandle) -> Self;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a risk that the wrong type of handle is passed in here and later treated incorrectly? E.g. if MutexRecursive::from_raw_handle() is called with a handle made via xSemaphoreCreateBinary(), what happens when that MutexRecursive instance is used to give/take? The docs for xSemaphoreTakeRecursive() say this, which sounds like it would be a problem:
The mutex must have previously been created using a call to xSemaphoreCreateRecursiveMutex();
I also see the CreateRecursiveMutex docs say:
xSemaphoreTake() and xSemaphoreGive() must not be used.

It's not necessarily a deal breaker, but it would be nice to avoid such errors and confusion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Links to docs I mentioned:
https://www.freertos.org/xSemaphoreTakeRecursive.html
https://www.freertos.org/xSemaphoreCreateRecursiveMutex.html

Also occurs to me that this could cause issues if the user leaks the handle with raw_handle() and then drops the owning struct, leaving the handle invalid behind.

What are you envisioning is the use for the user accessing the raw handle like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a risk that the wrong type of handle is passed in here and later treated incorrectly?

Yes, such risk exists as well as risk of passing a null or dangling pointer. This method is unsafe and that usually means that it's up to user to ensure that the handle passed is valid and of correct type.
I didn't find a way to check the mutex type in FreeRTOS API. It seems, that the only thing we can do here is to write a doc comment to draw the user's attention to possible mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also occurs to me that this could cause issues if the user leaks the handle with raw_handle() and then drops the owning struct, leaving the handle invalid behind.

Yes, such issue is also possible. But the raw handle is just a pointer and it's safe in Rust to have dangling pointers. And if you want to use that raw handle you need to call from_raw_handle which is unsafe that means you have to make sure that the raw handle is valid.

Copy link
Contributor Author

@agerasev agerasev Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you envisioning is the use for the user accessing the raw handle like this?

In my case I just needed to get a task identifier which is unique among existing tasks. And for completeness I've added the raw_handle method to all types that already have from_raw_handle and also added raw handle conversion for mutexes.

It's common practice for safe Rust wrappers of foreign API to provide an access to underlying unsafe entities (for example, raw fd traits in Rust stdlib). This can be useful when you want to interoperate with foreign code or use some API functions that aren't yet supported by the wrapper.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I see you already added a comment here but any other new unsafe functions should have comments (this always seemed like a good guide to me for reference)

fn raw_handle(&self) -> FreeRtosSemaphoreHandle;
}

pub struct MutexNormal(FreeRtosSemaphoreHandle);
Expand Down Expand Up @@ -154,6 +160,16 @@ impl MutexInnerImpl for MutexNormal {
freertos_rs_give_semaphore(self.0);
}
}

#[inline]
unsafe fn from_raw_handle(handle: FreeRtosSemaphoreHandle) -> Self {
Self(handle)
}

#[inline]
fn raw_handle(&self) -> FreeRtosSemaphoreHandle {
self.0
}
}

impl Drop for MutexNormal {
Expand Down Expand Up @@ -194,6 +210,16 @@ impl MutexInnerImpl for MutexRecursive {
freertos_rs_give_recursive_semaphore(self.0);
}
}

#[inline]
unsafe fn from_raw_handle(handle: FreeRtosSemaphoreHandle) -> Self {
Self(handle)
}

#[inline]
fn raw_handle(&self) -> FreeRtosSemaphoreHandle {
self.0
}
}

impl Drop for MutexRecursive {
Expand Down
9 changes: 9 additions & 0 deletions freertos-rust/src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,22 @@ impl<T: Sized + Copy> Queue<T> {
})
}

/// # Safety
///
/// `handle` must be a valid FreeRTOS regular queue handle (not semaphore or mutex).
///
/// The item size of the queue must match the size of `T`.
#[inline]
pub unsafe fn from_raw_handle(handle: FreeRtosQueueHandle) -> Self {
Self {
queue: handle,
item_type: PhantomData,
}
}
#[inline]
pub fn raw_handle(&self) -> FreeRtosQueueHandle {
self.queue
}

/// Send an item to the end of the queue. Wait for the queue to have empty space for it.
pub fn send<D: DurationTicks>(&self, item: T, max_wait: D) -> Result<(), FreeRtosError> {
Expand Down
10 changes: 10 additions & 0 deletions freertos-rust/src/semaphore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ impl Semaphore {
}
}

/// # Safety
///
/// `handle` must be a valid FreeRTOS semaphore handle.
///
/// Only binary or counting semaphore is expected here.
/// To create mutex from raw handle use [`crate::mutex::MutexInnerImpl::from_raw_handle`].
#[inline]
pub unsafe fn from_raw_handle(handle: FreeRtosSemaphoreHandle) -> Self {
Self { semaphore: handle }
}
#[inline]
pub fn raw_handle(&self) -> FreeRtosSemaphoreHandle {
self.semaphore
}

/// Lock this semaphore in a RAII fashion
pub fn lock<D: DurationTicks>(&self, max_wait: D) -> Result<SemaphoreGuard, FreeRtosError> {
Expand Down
7 changes: 7 additions & 0 deletions freertos-rust/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,17 @@ impl Task {
}
}

/// # Safety
///
/// `handle` must be a valid FreeRTOS task handle.
#[inline]
pub unsafe fn from_raw_handle(handle: FreeRtosTaskHandle) -> Self {
Self { task_handle: handle }
}
#[inline]
pub fn raw_handle(&self) -> FreeRtosTaskHandle {
self.task_handle
}

pub fn suspend_all() {
unsafe {
Expand Down
28 changes: 15 additions & 13 deletions freertos-rust/src/timers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ unsafe impl Sync for Timer {}
/// for that queue to get unblocked.
pub struct Timer {
handle: FreeRtosTimerHandle,
detached: bool,
}

/// Helper builder for a new software timer.
Expand Down Expand Up @@ -71,8 +70,17 @@ impl Timer {
}

/// Create a timer from a raw handle.
///
/// # Safety
///
/// `handle` must be a valid FreeRTOS timer handle.
#[inline]
pub unsafe fn from_raw_handle(handle: FreeRtosTimerHandle) -> Self {
Self { handle, detached: false }
Self { handle }
}
#[inline]
pub fn raw_handle(&self) -> FreeRtosTimerHandle {
self.handle
}

unsafe fn spawn_inner<'a>(
Expand Down Expand Up @@ -109,10 +117,7 @@ impl Timer {
extern "C" fn timer_callback(handle: FreeRtosTimerHandle) -> () {
unsafe {
{
let timer = Timer {
handle: handle,
detached: true,
};
let timer = Timer { handle };
if let Ok(callback_ptr) = timer.get_id() {
let b = Box::from_raw(callback_ptr as *mut Box<dyn Fn(Timer)>);
b(timer);
Expand All @@ -124,7 +129,6 @@ impl Timer {

Ok(Timer {
handle: timer_handle as *const _,
detached: false,
})
}

Expand Down Expand Up @@ -198,8 +202,10 @@ impl Timer {
/// will consume the memory.
///
/// Can be used for timers that will never be changed and don't need to stay in scope.
pub unsafe fn detach(mut self) {
self.detached = true;
///
/// This method is safe because resource leak is safe in Rust.
pub fn detach(self) {
mem::forget(self);
}

fn get_id(&self) -> Result<FreeRtosVoidPtr, FreeRtosError> {
Expand All @@ -210,10 +216,6 @@ impl Timer {
impl Drop for Timer {
#[allow(unused_must_use)]
fn drop(&mut self) {
if self.detached == true {
return;
}

unsafe {
if let Ok(callback_ptr) = self.get_id() {
// free the memory
Expand Down
3 changes: 3 additions & 0 deletions freertos-rust/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ pub fn shim_sanity_check() -> Result<(), TypeSizeError> {
Ok(())
}

/// # Safety
///
/// `str` must be a pointer to the beginning of nul-terminated sequence of bytes.
#[cfg(any(feature = "time", feature = "hooks", feature = "sync"))]
pub unsafe fn str_from_c_string(str: *const u8) -> Result<String, FreeRtosError> {
let mut buf = Vec::new();
Expand Down