use core::ffi::c_void;
use core::marker::PhantomData;
use core::mem::size_of;
use core::ptr::{self, NonNull};
use crate::config::{configMAX_PRIORITIES, configMAX_TASK_NAME_LEN, configMINIMAL_STACK_SIZE};
#[cfg(feature = "task-delete")]
use crate::kernel::tasks::vTaskDelete;
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
use crate::kernel::tasks::xTaskCreate;
use crate::kernel::tasks::{
taskSCHEDULER_NOT_STARTED, xTaskCreateStatic, xTaskGetCurrentTaskHandle,
xTaskGetSchedulerState, StaticTask_t,
};
#[cfg(feature = "task-suspend")]
use crate::kernel::tasks::{vTaskResume, vTaskSuspend};
use crate::types::*;
const _: () = assert!(configMAX_TASK_NAME_LEN > 0);
const _: () = assert!(configMAX_PRIORITIES > 0);
pub type TaskFn = extern "C" fn(*mut c_void);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TaskCreateError {
StackTooSmall {
supplied_words: usize,
minimum_words: usize,
},
StackByteSizeOverflow { supplied_words: usize },
InvalidPriority(InvalidTaskPriority),
KernelRejected,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidTaskPriority {
pub requested: UBaseType_t,
pub maximum: UBaseType_t,
}
pub struct TaskContext {
_not_send_or_sync: PhantomData<*mut ()>,
}
impl TaskContext {
pub unsafe fn assume() -> Self {
Self {
_not_send_or_sync: PhantomData,
}
}
pub fn current(&self) -> Option<CurrentTask<'_>> {
if xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED {
return None;
}
NonNull::new(xTaskGetCurrentTaskHandle()).map(|handle| CurrentTask {
handle,
_context: PhantomData,
_not_send_or_sync: PhantomData,
})
}
}
pub struct TaskHandle {
handle: NonNull<c_void>,
_not_send_or_sync: PhantomData<*mut ()>,
}
unsafe impl Send for TaskHandle {}
impl TaskHandle {
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
pub fn spawn(
_context: &TaskContext,
name: &[u8],
stack_size: usize,
priority: UBaseType_t,
task_fn: TaskFn,
) -> Result<Self, TaskCreateError> {
unsafe {
Self::spawn_with_param_inner(name, stack_size, priority, task_fn, ptr::null_mut())
}
}
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
pub unsafe fn spawn_with_param(
_context: &TaskContext,
name: &[u8],
stack_size: usize,
priority: UBaseType_t,
task_fn: TaskFn,
param: *mut c_void,
) -> Result<Self, TaskCreateError> {
unsafe { Self::spawn_with_param_inner(name, stack_size, priority, task_fn, param) }
}
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
unsafe fn spawn_with_param_inner(
name: &[u8],
stack_size: usize,
priority: UBaseType_t,
task_fn: TaskFn,
param: *mut c_void,
) -> Result<Self, TaskCreateError> {
validate_creation(stack_size, priority)?;
let name = PreparedTaskName::new(name);
let mut handle: TaskHandle_t = ptr::null_mut();
let result = unsafe {
xTaskCreate(
task_fn,
name.as_ptr(),
stack_size,
param,
priority,
&mut handle,
)
};
if result != pdPASS {
return Err(TaskCreateError::KernelRejected);
}
Self::from_created_raw(handle)
}
pub fn spawn_static(
_context: &TaskContext,
name: &[u8],
stack: &'static mut [StackType_t],
tcb: &'static mut StaticTask_t,
priority: UBaseType_t,
task_fn: TaskFn,
) -> Result<Self, TaskCreateError> {
unsafe {
Self::spawn_static_with_param_inner(
name,
stack,
tcb,
priority,
task_fn,
ptr::null_mut(),
)
}
}
pub unsafe fn spawn_static_with_param(
_context: &TaskContext,
name: &[u8],
stack: &'static mut [StackType_t],
tcb: &'static mut StaticTask_t,
priority: UBaseType_t,
task_fn: TaskFn,
param: *mut c_void,
) -> Result<Self, TaskCreateError> {
unsafe { Self::spawn_static_with_param_inner(name, stack, tcb, priority, task_fn, param) }
}
unsafe fn spawn_static_with_param_inner(
name: &[u8],
stack: &'static mut [StackType_t],
tcb: &'static mut StaticTask_t,
priority: UBaseType_t,
task_fn: TaskFn,
param: *mut c_void,
) -> Result<Self, TaskCreateError> {
validate_creation(stack.len(), priority)?;
let name = PreparedTaskName::new(name);
let handle = unsafe {
xTaskCreateStatic(
task_fn,
name.as_ptr(),
stack.len(),
param,
priority,
stack.as_mut_ptr(),
tcb as *mut StaticTask_t,
)
};
Self::from_created_raw(handle)
}
fn from_created_raw(handle: TaskHandle_t) -> Result<Self, TaskCreateError> {
NonNull::new(handle)
.map(Self::from_non_null)
.ok_or(TaskCreateError::KernelRejected)
}
fn from_non_null(handle: NonNull<c_void>) -> Self {
Self {
handle,
_not_send_or_sync: PhantomData,
}
}
pub fn borrow(&self) -> TaskRef<'_> {
TaskRef {
handle: self.handle,
_owner: PhantomData,
_not_sync: PhantomData,
}
}
#[cfg(feature = "task-suspend")]
pub fn suspend(&mut self, _context: &TaskContext) {
unsafe { vTaskSuspend(self.handle.as_ptr()) };
}
#[cfg(feature = "task-suspend")]
pub fn resume(&mut self, _context: &TaskContext) {
assert_has_selected_task("TaskHandle::resume", xTaskGetCurrentTaskHandle());
unsafe { vTaskResume(self.handle.as_ptr()) };
}
#[cfg(feature = "task-delete")]
pub fn delete(self, context: &TaskContext) {
let current = context.current().map(|task| task.as_raw());
assert_deletable_task(self.handle.as_ptr(), current);
unsafe { vTaskDelete(self.handle.as_ptr()) };
}
#[cfg(feature = "task-priority-set")]
pub fn priority(&self, _context: &TaskContext) -> UBaseType_t {
unsafe { crate::kernel::tasks::uxTaskPriorityGet(self.handle.as_ptr()) }
}
#[cfg(feature = "task-priority-set")]
pub fn set_priority(
&mut self,
_context: &TaskContext,
new_priority: UBaseType_t,
) -> Result<(), InvalidTaskPriority> {
validate_priority(new_priority)?;
assert_has_selected_task("TaskHandle::set_priority", xTaskGetCurrentTaskHandle());
unsafe { crate::kernel::tasks::vTaskPrioritySet(self.handle.as_ptr(), new_priority) };
Ok(())
}
pub fn as_raw(&self) -> TaskHandle_t {
self.handle.as_ptr()
}
pub fn into_raw(self) -> TaskHandle_t {
self.handle.as_ptr()
}
pub unsafe fn from_raw_owned(handle: TaskHandle_t) -> Option<Self> {
NonNull::new(handle).map(Self::from_non_null)
}
}
pub struct TaskRef<'task> {
handle: NonNull<c_void>,
_owner: PhantomData<&'task TaskHandle>,
_not_sync: PhantomData<*mut ()>,
}
unsafe impl Send for TaskRef<'_> {}
impl TaskRef<'_> {
#[cfg(feature = "task-suspend")]
pub fn resume(&mut self, _context: &TaskContext) {
assert_has_selected_task("TaskRef::resume", xTaskGetCurrentTaskHandle());
unsafe { vTaskResume(self.handle.as_ptr()) };
}
#[cfg(feature = "task-priority-set")]
pub fn priority(&self, _context: &TaskContext) -> UBaseType_t {
unsafe { crate::kernel::tasks::uxTaskPriorityGet(self.handle.as_ptr()) }
}
pub fn as_raw(&self) -> TaskHandle_t {
self.handle.as_ptr()
}
}
pub struct CurrentTask<'context> {
handle: NonNull<c_void>,
_context: PhantomData<&'context TaskContext>,
_not_send_or_sync: PhantomData<*mut ()>,
}
impl CurrentTask<'_> {
#[cfg(feature = "task-suspend")]
pub fn suspend(self) {
unsafe { vTaskSuspend(self.handle.as_ptr()) };
}
#[cfg(feature = "task-priority-set")]
pub fn priority(&self) -> UBaseType_t {
unsafe { crate::kernel::tasks::uxTaskPriorityGet(self.handle.as_ptr()) }
}
#[cfg(feature = "task-priority-set")]
pub fn set_priority(&mut self, new_priority: UBaseType_t) -> Result<(), InvalidTaskPriority> {
validate_priority(new_priority)?;
unsafe { crate::kernel::tasks::vTaskPrioritySet(self.handle.as_ptr(), new_priority) };
Ok(())
}
pub fn as_raw(&self) -> TaskHandle_t {
self.handle.as_ptr()
}
}
#[cfg(feature = "task-suspend")]
pub fn suspend_self(context: &TaskContext) -> bool {
match context.current() {
Some(current) => {
current.suspend();
true
}
None => false,
}
}
#[cfg(feature = "task-delete")]
pub unsafe fn delete_self(context: TaskContext) -> ! {
assert!(
context.current().is_some(),
"delete_self requires a running task"
);
unsafe { vTaskDelete(ptr::null_mut()) };
unreachable!("a deleted FreeRTOS task became runnable again")
}
struct PreparedTaskName {
bytes: [u8; configMAX_TASK_NAME_LEN],
}
impl PreparedTaskName {
fn new(name: &[u8]) -> Self {
let mut bytes = [0; configMAX_TASK_NAME_LEN];
let source_len = name
.iter()
.position(|byte| *byte == 0)
.unwrap_or(name.len());
let copy_len = core::cmp::min(source_len, configMAX_TASK_NAME_LEN - 1);
bytes[..copy_len].copy_from_slice(&name[..copy_len]);
Self { bytes }
}
fn as_ptr(&self) -> *const u8 {
self.bytes.as_ptr()
}
}
fn validate_creation(stack_words: usize, priority: UBaseType_t) -> Result<(), TaskCreateError> {
if stack_words < configMINIMAL_STACK_SIZE {
return Err(TaskCreateError::StackTooSmall {
supplied_words: stack_words,
minimum_words: configMINIMAL_STACK_SIZE,
});
}
if stack_words.checked_mul(size_of::<StackType_t>()).is_none() {
return Err(TaskCreateError::StackByteSizeOverflow {
supplied_words: stack_words,
});
}
validate_priority(priority).map_err(TaskCreateError::InvalidPriority)
}
fn validate_priority(priority: UBaseType_t) -> Result<(), InvalidTaskPriority> {
if priority >= configMAX_PRIORITIES {
return Err(InvalidTaskPriority {
requested: priority,
maximum: configMAX_PRIORITIES - 1,
});
}
Ok(())
}
#[cfg(any(feature = "task-suspend", feature = "task-priority-set"))]
fn assert_has_selected_task(operation: &str, current: TaskHandle_t) {
assert!(
!current.is_null(),
"{operation} requires FreeRTOS to have a selected current task"
);
}
#[cfg(feature = "task-delete")]
fn assert_deletable_task(target: TaskHandle_t, current: Option<TaskHandle_t>) {
let current = current.expect("TaskHandle::delete requires a running task context");
assert!(
current != target,
"TaskHandle::delete cannot delete the current task; use unsafe delete_self"
);
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_send<T: Send>() {}
#[test]
fn unique_and_borrowed_task_authorities_can_be_transferred() {
assert_send::<TaskHandle>();
assert_send::<TaskRef<'static>>();
}
#[test]
fn task_names_are_bounded_and_nul_terminated() {
let short = PreparedTaskName::new(b"worker");
assert_eq!(&short.bytes[..7], b"worker\0");
let terminated = PreparedTaskName::new(b"one\0ignored");
assert_eq!(&terminated.bytes[..4], b"one\0");
assert!(terminated.bytes[4..].iter().all(|byte| *byte == 0));
let long = PreparedTaskName::new(&[b'x'; configMAX_TASK_NAME_LEN + 8]);
assert!(long.bytes[..configMAX_TASK_NAME_LEN - 1]
.iter()
.all(|byte| *byte == b'x'));
assert_eq!(long.bytes[configMAX_TASK_NAME_LEN - 1], 0);
let empty = PreparedTaskName::new(b"");
assert!(empty.bytes.iter().all(|byte| *byte == 0));
}
#[test]
fn creation_validation_rejects_invalid_stacks_in_release_builds() {
assert_eq!(
validate_creation(0, 0),
Err(TaskCreateError::StackTooSmall {
supplied_words: 0,
minimum_words: configMINIMAL_STACK_SIZE,
})
);
assert_eq!(
validate_creation(configMINIMAL_STACK_SIZE - 1, 0),
Err(TaskCreateError::StackTooSmall {
supplied_words: configMINIMAL_STACK_SIZE - 1,
minimum_words: configMINIMAL_STACK_SIZE,
})
);
assert_eq!(
validate_creation(usize::MAX, 0),
Err(TaskCreateError::StackByteSizeOverflow {
supplied_words: usize::MAX,
})
);
assert_eq!(validate_creation(configMINIMAL_STACK_SIZE, 0), Ok(()));
}
#[test]
fn creation_validation_rejects_priority_instead_of_clamping() {
let invalid = InvalidTaskPriority {
requested: configMAX_PRIORITIES,
maximum: configMAX_PRIORITIES - 1,
};
assert_eq!(
validate_creation(configMINIMAL_STACK_SIZE, configMAX_PRIORITIES),
Err(TaskCreateError::InvalidPriority(invalid))
);
assert_eq!(validate_priority(configMAX_PRIORITIES - 1), Ok(()));
}
#[cfg(feature = "task-suspend")]
#[test]
#[should_panic(expected = "requires FreeRTOS to have a selected current task")]
fn resume_rejects_setup_with_every_task_suspended() {
assert_has_selected_task("test resume", ptr::null_mut());
}
#[cfg(feature = "task-suspend")]
#[test]
fn resume_accepts_a_selected_task() {
let mut selected = 0_u8;
assert_has_selected_task("test resume", core::ptr::addr_of_mut!(selected).cast());
}
#[cfg(feature = "task-priority-set")]
#[test]
#[should_panic(expected = "requires FreeRTOS to have a selected current task")]
fn priority_change_rejects_setup_with_every_task_suspended() {
assert_has_selected_task("test priority change", ptr::null_mut());
}
#[cfg(feature = "task-delete")]
#[test]
#[should_panic(expected = "TaskHandle::delete cannot delete the current task")]
fn owning_delete_rejects_the_current_task() {
let mut task_storage = 0_u8;
let task = core::ptr::addr_of_mut!(task_storage).cast();
assert_deletable_task(task, Some(task));
}
#[cfg(feature = "task-delete")]
#[test]
#[should_panic(expected = "TaskHandle::delete requires a running task context")]
fn owning_delete_rejects_pre_scheduler_setup() {
let mut task_storage = 0_u8;
let task = core::ptr::addr_of_mut!(task_storage).cast();
assert_deletable_task(task, None);
}
#[cfg(feature = "task-delete")]
#[test]
fn owning_delete_allows_another_task() {
let mut task_storage = 0_u8;
let mut other_storage = 0_u8;
let task = core::ptr::addr_of_mut!(task_storage).cast();
let other = core::ptr::addr_of_mut!(other_storage).cast();
assert_deletable_task(task, Some(other));
}
}