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}