leodos_libcfs/os/task.rs
1//! Safe, idiomatic wrappers for OSAL Task APIs.
2//!
3//! This module provides utilities for task management, such as delaying the
4//! current task, retrieving task IDs, and managing task priorities.
5
6use bitflags::bitflags;
7
8use crate::error::{CfsError, OsalError, Result};
9use crate::ffi;
10use crate::os::id::OsalId;
11use crate::cstring;
12use crate::string_from_c_buf;
13use crate::status::check;
14use core::mem::MaybeUninit;
15use core::time::Duration;
16use heapless::String;
17
18bitflags! {
19 /// Options for task creation.
20 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21 pub struct TaskFlags: u32 {
22 /// Enable floating-point register context switching
23 /// for this task. Without this flag, using FP
24 /// instructions may corrupt other tasks' FP state.
25 const FP_ENABLED = ffi::OS_FP_ENABLED;
26 }
27}
28
29/// A type-safe, zero-cost wrapper for an OSAL Task ID.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[repr(transparent)]
32pub struct TaskId(pub ffi::osal_id_t);
33
34impl TaskId {
35 /// Sets the priority of this task.
36 ///
37 /// # Arguments
38 /// * `priority`: The new priority (0=highest, 255=lowest).
39 pub fn set_priority(&self, priority: u8) -> Result<()> {
40 check(unsafe { ffi::OS_TaskSetPriority(self.0, priority) })?;
41 Ok(())
42 }
43
44 /// Retrieves the `TaskId` of the currently executing task.
45 pub fn current() -> Result<Self> {
46 let id = unsafe { ffi::OS_TaskGetId() };
47 if id == 0 {
48 Err(CfsError::Osal(OsalError::InvalidId))
49 } else {
50 Ok(Self(id))
51 }
52 }
53
54 /// Returns a zero-based index for this task, suitable for
55 /// indexing into a user-provided per-task context array.
56 ///
57 /// This is the workaround for OSAL not supporting per-task
58 /// arguments (see nasa/osal#1394). Usage:
59 ///
60 /// ```ignore
61 /// static CONTEXTS: [AtomicPtr<()>; 16] = /* ... */;
62 ///
63 /// // At task creation:
64 /// let task = Task::new("worker", worker_entry, ...)?;
65 /// CONTEXTS[task.id().index()? as usize].store(ctx_ptr, Ordering::Release);
66 ///
67 /// // Inside the task:
68 /// let idx = TaskId::current()?.index()? as usize;
69 /// let ctx = CONTEXTS[idx].load(Ordering::Acquire);
70 /// ```
71 pub fn index(&self) -> Result<u32> {
72 OsalId(self.0).to_index()
73 }
74
75 /// Shorthand: get the current task's zero-based index.
76 pub fn current_index() -> Result<u32> {
77 Self::current()?.index()
78 }
79
80 /// Finds an existing OSAL task ID by its name.
81 pub fn from_name(name: &str) -> Result<Self> {
82 let c_name = cstring::<{ ffi::OS_MAX_API_NAME as usize }>(name)?;
83
84 let mut task_id = MaybeUninit::uninit();
85 check(unsafe { ffi::OS_TaskGetIdByName(task_id.as_mut_ptr(), c_name.as_ptr()) })?;
86 Ok(Self(unsafe { task_id.assume_init() }))
87 }
88}
89
90/// Type alias for a handler function called on task deletion.
91pub type TaskDeleteHandler = unsafe extern "C" fn();
92
93/// Properties of an OSAL task, returned by `Task::get_info`.
94#[derive(Debug, Clone)]
95pub struct TaskProp {
96 /// The registered name of the task.
97 pub name: String<{ ffi::OS_MAX_API_NAME as usize }>,
98 /// The OSAL ID of the task that created this task.
99 pub creator: OsalId,
100 /// The allocated stack size of the task in bytes.
101 pub stack_size: usize,
102 /// The priority of the task (0=highest, 255=lowest).
103 pub priority: u8,
104}
105
106/// A handle to an OSAL task.
107///
108/// This is a wrapper around an `osal_id_t` that will automatically call
109/// `OS_TaskDelete` when it goes out of scope, preventing resource leaks.
110#[derive(Debug)]
111pub struct Task {
112 id: TaskId,
113}
114
115impl Task {
116 /// Creates a new OSAL task and starts it running.
117 ///
118 /// The stack for the task is allocated from the system memory heap.
119 ///
120 /// # Arguments
121 /// * `name`: A unique string to identify the task.
122 /// * `entry_point`: The function that the new task will execute. This must
123 /// be a free function with `extern "C"` linkage.
124 /// * `stack_size`: The size of the stack to allocate for the new task.
125 /// A value of 0 is non-portable: some RTOS use a default,
126 /// others create a task with no stack. Always specify an
127 /// actual size.
128 /// * `priority`: The priority of the new task (0=highest, 255=lowest).
129 /// * `flags`: Task creation flags (e.g. `TaskFlags::FP_ENABLED`).
130 pub fn new(
131 name: &str,
132 entry_point: unsafe extern "C" fn(),
133 stack_size: usize,
134 priority: u8,
135 flags: TaskFlags,
136 ) -> Result<Self> {
137 let c_name = cstring::<{ ffi::OS_MAX_API_NAME as usize }>(name)?;
138
139 let mut task_id = MaybeUninit::uninit();
140 check(unsafe {
141 ffi::OS_TaskCreate(
142 task_id.as_mut_ptr(),
143 c_name.as_ptr(),
144 Some(entry_point),
145 core::ptr::null_mut(), // OSAL_TASK_STACK_ALLOCATE
146 stack_size,
147 priority,
148 flags.bits(),
149 )
150 })?;
151
152 Ok(Self {
153 id: TaskId(unsafe { task_id.assume_init() }),
154 })
155 }
156
157 /// Returns the underlying `TaskId` for this task.
158 pub fn id(&self) -> TaskId {
159 self.id
160 }
161
162 /// Retrieves information about this task.
163 pub fn get_info(&self) -> Result<TaskProp> {
164 let mut prop = MaybeUninit::<ffi::OS_task_prop_t>::uninit();
165 check(unsafe { ffi::OS_TaskGetInfo(self.id.0, prop.as_mut_ptr()) })?;
166 let prop = unsafe { prop.assume_init() };
167
168 Ok(TaskProp {
169 name: string_from_c_buf(&prop.name)?,
170 creator: OsalId(prop.creator),
171 stack_size: prop.stack_size,
172 priority: prop.priority,
173 })
174 }
175}
176
177impl Drop for Task {
178 /// Deletes the OSAL task when the `Task` object goes out of scope.
179 fn drop(&mut self) {
180 let _ = unsafe { ffi::OS_TaskDelete(self.id.0) };
181 }
182}
183
184/// Delays the execution of the current task for at least the specified duration.
185///
186/// # Arguments
187/// * `duration`: The minimum amount of time to delay.
188pub fn delay(duration: Duration) -> Result<()> {
189 let millis = duration.as_millis();
190 // Clamp to u32::MAX, which is ~49 days. This is a reasonable upper limit for a delay.
191 let millis_u32 = millis.try_into().unwrap_or(u32::MAX);
192 check(unsafe { ffi::OS_TaskDelay(millis_u32) })?;
193 Ok(())
194}
195
196/// Installs a handler function to be called when the current task is deleted.
197///
198/// This is useful for cleaning up resources that a task creates before it is
199/// removed from the system.
200///
201/// # Safety
202/// The provided `handler` function must be a valid `extern "C"` function pointer.
203/// It will be called in the context of task deletion, so it should be brief
204/// and avoid complex operations or blocking, especially any that would try to
205/// interact further with OSAL.
206pub fn install_delete_handler(handler: TaskDeleteHandler) -> Result<()> {
207 check(unsafe { ffi::OS_TaskInstallDeleteHandler(Some(handler)) })?;
208 Ok(())
209}
210
211/// Exits the calling task.
212///
213/// This function terminates the currently running task and does not return.
214pub fn exit() -> ! {
215 unsafe {
216 ffi::OS_TaskExit();
217 }
218 // This function never returns, but we add a loop to satisfy the compiler.
219 loop {}
220}
221
222/// Reverse-looks up an OSAL task ID from an underlying operating system ID.
223///
224/// This is for special cases like exception handling where the OS provides a
225/// native task ID that needs to be mapped back to an OSAL ID.
226///
227/// # Safety
228/// The `sys_data` pointer must be a valid pointer to the OS-specific task identifier data,
229/// and `sys_data_size` must be the correct size of that data.
230pub unsafe fn find_id_by_system_data(sys_data: *const (), sys_data_size: usize) -> Result<TaskId> {
231 let mut task_id = MaybeUninit::uninit();
232 check(ffi::OS_TaskFindIdBySystemData(
233 task_id.as_mut_ptr(),
234 sys_data as *const _,
235 sys_data_size,
236 ))?;
237 Ok(TaskId(task_id.assume_init()))
238}