Skip to main content

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}