veecle_freertos_integration/task/
mod.rs

1//! Utilities for working with FreeRTOS tasks.
2
3use alloc::boxed::Box;
4use alloc::ffi::CString;
5use alloc::string::String;
6use core::ffi::CStr;
7use core::ptr::null_mut;
8
9use veecle_freertos_sys::bindings::{
10    StackType_t, TaskHandle_t, UBaseType_t, eNotifyAction, eNotifyAction_eIncrement,
11    eNotifyAction_eNoAction, eNotifyAction_eSetBits, eNotifyAction_eSetValueWithOverwrite,
12    eNotifyAction_eSetValueWithoutOverwrite, pdFALSE, pdTRUE, shim_pcTaskGetName,
13    shim_ulTaskNotifyTake, shim_xTaskNotify, shim_xTaskNotifyFromISR, shim_xTaskNotifyWait,
14    uxTaskGetStackHighWaterMark, uxTaskGetTaskNumber, vTaskDelay, vTaskSetTaskNumber, vTaskSuspend,
15    xTaskCreate, xTaskGetCurrentTaskHandle,
16};
17
18pub use self::block_on_future::block_on_future;
19use crate::units::Duration;
20use crate::{FreeRtosError, InterruptContext};
21
22mod block_on_future;
23
24// SAFETY: All task APIs we expose are fine to call from any task/thread because they use internal locking where
25// necessary, or they are marked unsafe and it's up to users to provide thread safety on those specific APIs.
26unsafe impl Send for Task {}
27
28// SAFETY: All task APIs we expose are fine to call from any task/thread because they use internal locking where
29// necessary, or they are marked unsafe and it's up to users to provide thread safety on those specific APIs.
30unsafe impl Sync for Task {}
31
32/// Handle for a FreeRTOS task
33#[allow(clippy::new_without_default)]
34#[derive(Debug, Clone)]
35pub struct Task {
36    /// # Safety
37    ///
38    /// This handle refers to a valid undeleted task, this must be guaranteed on construction and can be assumed on
39    /// use.
40    task_handle: TaskHandle_t,
41}
42
43/// Task's execution priority. Low priority numbers denote low priority tasks.
44#[derive(Debug, Copy, Clone)]
45pub struct TaskPriority(pub UBaseType_t);
46
47/// Notification to be sent to a task.
48#[derive(Debug, Copy, Clone)]
49pub enum TaskNotification {
50    /// Send the event, unblock the task, the task's notification value isn't changed.
51    NoAction,
52    /// Perform a logical or with the task's notification value.
53    SetBits(u32),
54    /// Increment the task's notification value by one.
55    Increment,
56    /// Set the task's notification value to this value.
57    OverwriteValue(u32),
58    /// Try to set the task's notification value to this value. Succeeds
59    /// only if the task has no pending notifications. Otherwise, the
60    /// notification call will fail.
61    SetValue(u32),
62}
63
64impl TaskNotification {
65    fn to_freertos(self) -> (u32, eNotifyAction) {
66        match self {
67            TaskNotification::NoAction => (0, eNotifyAction_eNoAction),
68            TaskNotification::SetBits(v) => (v, eNotifyAction_eSetBits),
69            TaskNotification::Increment => (0, eNotifyAction_eIncrement),
70            TaskNotification::OverwriteValue(v) => (v, eNotifyAction_eSetValueWithOverwrite),
71            TaskNotification::SetValue(v) => (v, eNotifyAction_eSetValueWithoutOverwrite),
72        }
73    }
74}
75
76impl TaskPriority {
77    fn to_freertos(self) -> UBaseType_t {
78        self.0
79    }
80}
81
82/// Helper for spawning a new task. Instantiate with [`Task::new()`].
83///
84/// [`Task::new()`]: struct.Task.html#method.new
85#[allow(clippy::new_without_default)]
86#[derive(Debug)]
87pub struct TaskBuilder {
88    task_name: CString,
89    task_stack_size: StackType_t,
90    task_priority: TaskPriority,
91}
92
93impl TaskBuilder {
94    /// Set the task's name.
95    pub fn name(&mut self, name: &CStr) -> &mut Self {
96        self.task_name = name.into();
97        self
98    }
99
100    /// Set the stack size, in words.
101    pub fn stack_size(&mut self, stack_size: StackType_t) -> &mut Self {
102        self.task_stack_size = stack_size;
103        self
104    }
105
106    /// Set the task's priority.
107    pub fn priority(&mut self, priority: TaskPriority) -> &mut Self {
108        self.task_priority = priority;
109        self
110    }
111
112    /// Start a new task that can't return a value.
113    pub fn start<F>(&self, func: F) -> Result<Task, FreeRtosError>
114    where
115        F: FnOnce(Task),
116        F: Send + 'static,
117    {
118        Task::spawn(
119            &self.task_name,
120            self.task_stack_size,
121            self.task_priority,
122            func,
123        )
124    }
125}
126
127impl Task {
128    /// Internal check to prove that once we observe a [`Task`] the backing [`TaskHandle_t`] will never be invalidated.
129    ///
130    /// There is no runtime cost, the assertion runs at compile time (but this should be called as a normal fn to make
131    /// the compiler error messages more readable).
132    const fn assert_no_task_deletion() {
133        // Using a nested inline const ensures there's only one error emitted to users, no matter how many times this
134        // function is called.
135        const {
136            assert!(
137                !cfg!(INCLUDE_vTaskDelete),
138                "it is not possible to have a safe wrapper around tasks that may be deleted at any time, you must \
139                 disable `INCLUDE_vTaskDelete` to use `Task`"
140            )
141        }
142    }
143
144    /// Prepare a builder object for the new task.
145    #[allow(clippy::new_ret_no_self)]
146    pub fn new() -> TaskBuilder {
147        TaskBuilder {
148            task_name: c"rust_task".into(),
149            task_stack_size: 1024,
150            task_priority: TaskPriority(1),
151        }
152    }
153
154    /// # Safety
155    ///
156    /// `handle` must be a valid FreeRTOS task handle.
157    #[inline]
158    pub unsafe fn from_raw_handle(handle: TaskHandle_t) -> Self {
159        Self {
160            task_handle: handle,
161        }
162    }
163    #[inline]
164    pub fn raw_handle(&self) -> TaskHandle_t {
165        self.task_handle
166    }
167
168    unsafe fn spawn_inner(
169        f: Box<dyn FnOnce(Task)>,
170        name: &CStr,
171        stack_size: StackType_t,
172        priority: TaskPriority,
173    ) -> Result<Task, FreeRtosError> {
174        let f = Box::new(f);
175        let param_ptr = Box::into_raw(f);
176
177        let (success, task_handle) = {
178            let mut task_handle = core::ptr::null_mut();
179
180            // SAFETY:
181            // The function `thread_start` cannot finish without panicking, and relies on `extern "C"` doing an
182            // abort-on-panic, so it will never return to the scheduler. On success, the memory pointed to by
183            // `param_ptr` is leaked to ensure the pointer stays valid until the application terminates.
184            // `name` points to a valid, null-terminated cstring and outlives the `xTaskCreate` call, which copies the
185            // value pointed to.
186            let ret = unsafe {
187                xTaskCreate(
188                    Some(thread_start),
189                    name.as_ptr(),
190                    stack_size,
191                    param_ptr.cast(),
192                    priority.to_freertos(),
193                    &mut task_handle,
194                )
195            };
196
197            (ret == pdTRUE(), task_handle)
198        };
199
200        if !success {
201            // SAFETY:
202            // We created `param_ptr` from a valid `Box` earlier in this function, thus `param_ptr` points to valid
203            // memory for `Box::from_raw` and `xTaskCreate` failed, so we retain sole ownership.
204            drop(unsafe { Box::from_raw(param_ptr) });
205            return Err(FreeRtosError::OutOfMemory);
206        }
207
208        use core::ffi::c_void;
209        extern "C" fn thread_start(main: *mut c_void) {
210            // SAFETY:
211            // The `main` pointer is the `param_ptr` passed into `xTaskCreate` above, so we know it is a raw pointer for
212            // a `Box<dyn FnOnce(Task)>`.
213            let task_main_function = unsafe { Box::from_raw(main.cast::<Box<dyn FnOnce(Task)>>()) };
214
215            task_main_function(
216                Task::current().expect("in a task, the current task should be available"),
217            );
218
219            panic!("Not allowed to quit the task!");
220        }
221
222        Ok(Task { task_handle })
223    }
224
225    fn spawn<F>(
226        name: &CStr,
227        stack_size: StackType_t,
228        priority: TaskPriority,
229        f: F,
230    ) -> Result<Task, FreeRtosError>
231    where
232        F: FnOnce(Task),
233        F: Send + 'static,
234    {
235        // SAFETY:
236        // TODO: `Task::spawn_inner` has no safety requirements, it should probably not be `unsafe`.
237        unsafe { Task::spawn_inner(Box::new(f), name, stack_size, priority) }
238    }
239
240    /// Get the name of the current task.
241    #[allow(clippy::result_unit_err)]
242    pub fn get_name(&self) -> Result<String, ()> {
243        Task::assert_no_task_deletion();
244        // SAFETY: Our handle is a valid undeleted task based on above guarantee.
245        let name_ptr = unsafe { shim_pcTaskGetName(self.task_handle) };
246        // SAFETY: Not entirely documented, but FreeRTOS returns a valid non-null null-terminated C string.
247        unsafe { CStr::from_ptr(name_ptr) }
248            .to_str()
249            .map_err(|_| ())
250            .map(String::from)
251    }
252
253    /// Try to find the task of the current execution context.
254    pub fn current() -> Result<Task, FreeRtosError> {
255        // SAFETY:
256        // Calling `xTaskGetCurrentTaskHandle` from outside a task is asserted afterwards, returning a Rust error if the
257        // retrived handle is null. TODO(unsound): It is unsound to call `xTaskGetCurrentTaskHandle` from a thread
258        // outside FreeRTOS.
259        let task_handle = unsafe { xTaskGetCurrentTaskHandle() };
260
261        if task_handle.is_null() {
262            return Err(FreeRtosError::TaskNotFound);
263        }
264
265        Ok(Task { task_handle })
266    }
267
268    /// Forcibly set the notification value for this task.
269    pub fn set_notification_value(&self, val: u32) {
270        self.notify(TaskNotification::OverwriteValue(val))
271    }
272
273    /// Notify this task.
274    pub fn notify(&self, notification: TaskNotification) {
275        let (value, action) = notification.to_freertos();
276        Task::assert_no_task_deletion();
277        // SAFETY:
278        // Our handle is a valid undeleted task based on the field guarantee.
279        unsafe { shim_xTaskNotify(self.task_handle, value, action) };
280    }
281
282    /// Notify this task from an interrupt.
283    pub fn notify_from_isr(
284        &self,
285        context: &mut InterruptContext,
286        notification: TaskNotification,
287    ) -> Result<(), FreeRtosError> {
288        let (value, action) = notification.to_freertos();
289
290        Task::assert_no_task_deletion();
291        // SAFETY:
292        // Our handle is a valid undeleted task based on the field guarantee.
293        if unsafe {
294            shim_xTaskNotifyFromISR(
295                self.task_handle,
296                value,
297                action,
298                context.get_task_field_mut(),
299            )
300        } == pdTRUE()
301        {
302            Ok(())
303        } else {
304            Err(FreeRtosError::QueueFull)
305        }
306    }
307
308    /// Wait for a notification to be posted.
309    pub fn wait_for_notification(
310        &self,
311        clear_bits_enter: u32,
312        clear_bits_exit: u32,
313        wait_for: Duration,
314    ) -> Result<u32, FreeRtosError> {
315        let mut val = 0;
316
317        // TODO: This isn't using this task handle, should it be a `CurrentTask` method?
318        //
319        // SAFETY:
320        // A writable pointer to `val` is passed as the `pulNotificationValue` argument, ensuring it is safe to write
321        // the notification value in that local variable.
322        if unsafe {
323            shim_xTaskNotifyWait(
324                clear_bits_enter,
325                clear_bits_exit,
326                &mut val as *mut _,
327                wait_for.ticks(),
328            )
329        } == pdTRUE()
330        {
331            Ok(val)
332        } else {
333            Err(FreeRtosError::Timeout)
334        }
335    }
336
337    /// Get the minimum amount of stack that was ever left on this task.
338    pub fn get_stack_high_water_mark(&self) -> UBaseType_t {
339        Task::assert_no_task_deletion();
340        // SAFETY:
341        // A Task cannot be created without spawning it, ensuring the value of `xTask` is correct.
342        unsafe { uxTaskGetStackHighWaterMark(self.task_handle) as UBaseType_t }
343    }
344
345    /// # Safety
346    ///
347    /// This function is not thread safe, you must synchronize all usage of it, [`Task::set_id`], and
348    /// `vTaskSetTaskNumber` called with the same task handle manually.
349    pub unsafe fn get_id(&self) -> UBaseType_t {
350        Task::assert_no_task_deletion();
351        // SAFETY:
352        // Our handle is a valid undeleted task based on the field guarantee.
353        unsafe { uxTaskGetTaskNumber(self.task_handle) }
354    }
355
356    /// # Safety
357    ///
358    /// This function is not thread safe, you must synchronize all usage of it, [`Task::get_id`], `uxTaskGetTaskNumber`
359    /// and `vTaskSetTaskNumber` called with the same task handle manually.
360    pub unsafe fn set_id(&self, value: UBaseType_t) {
361        Task::assert_no_task_deletion();
362        // SAFETY:
363        // Our handle is a valid undeleted task based on the field guarantee.
364        unsafe { vTaskSetTaskNumber(self.task_handle, value) };
365    }
366}
367
368/// Helper methods to be performed on the task that is currently executing.
369#[derive(Debug)]
370pub struct CurrentTask;
371
372impl CurrentTask {
373    /// Delay the execution of the current task.
374    pub fn delay(delay: Duration) {
375        vTaskDelay(delay.ticks());
376    }
377
378    pub fn suspend() {
379        // SAFETY:
380        // TODO(unsound): The caller must ensure this is called from inside a FreeRTOS task.
381        unsafe { vTaskSuspend(null_mut()) }
382    }
383
384    /// Take the notification and either clear the notification value or decrement it by one.
385    pub fn take_notification(clear: bool, wait_for: Duration) -> u32 {
386        let clear = if clear { pdTRUE() } else { pdFALSE() };
387
388        // SAFETY:
389        // TODO(unsound): The caller must ensure this is called from inside a FreeRTOS task.
390        unsafe { shim_ulTaskNotifyTake(clear, wait_for.ticks()) }
391    }
392
393    /// Get the minimum amount of stack that was ever left on the current task.
394    pub fn get_stack_high_water_mark() -> UBaseType_t {
395        // SAFETY:
396        // TODO(unsound): The caller must ensure this is called from inside a FreeRTOS task.
397        unsafe { uxTaskGetStackHighWaterMark(null_mut()) }
398    }
399}