Skip to main content

esp_radio_rtos_driver/
lib.rs

1//! # esp-radio task scheduler interface.
2//!
3//! `esp-radio` requires a task scheduler to operate. This crate allows the task scheduler to be
4//! tailored to specific software platforms (such as ArielOS). Trying to use multiple scheduler
5//! crates in a firmware project will not build.
6//!
7//! If you want to use esp-radio without any OS, you can use the [`esp-rtos`]
8//! crate as the task scheduler.
9//!
10//! ## Implementing a scheduler driver
11//!
12//! This crate abstracts the capabilities of FreeRTOS. The implementor crate has two different
13//! possible ways to implement the required capabilities. The `SchedulerImplementation` trait must
14//! be implemented in both cases.
15//!
16//! The implementation types must be registered using the respective `register_x_implementation`
17//! macros.
18//!
19//! ### Without `ipc-implementations`
20//!
21//! The implementor must implement the [`semaphore::SemaphoreImplementation`],
22//! [`queue::QueueImplementation`], and [`timer::TimerImplementation`] traits.
23//!
24//! ```rust
25//! use esp_radio_rtos_driver::{
26//!     SchedulerImplementation,
27//!     queue::QueueImplementation,
28//!     register_queue_implementation,
29//!     register_scheduler_implementation,
30//!     register_semaphore_implementation,
31//!     register_timer_implementation,
32//!     semaphore::SemaphoreImplementation,
33//!     timer::TimerImplementation,
34//! };
35//!
36//! struct MyScheduler {
37//!     // ...
38//! }
39//! impl SchedulerImplementation for MyScheduler {
40//!     // ...
41//! }
42//!
43//! struct MySemaphore {
44//!     // ...
45//! }
46//! impl SemaphoreImplementation for MySemaphore {
47//!     // ...
48//! }
49//!
50//! struct MyTimer {
51//!     // ...
52//! }
53//! impl TimerImplementation for MyTimer {
54//!     // ...
55//! }
56//!
57//! struct MyQueue {
58//!     // ...
59//! }
60//! impl QueueImplementation for MyQueue {
61//!     // ...
62//! }
63//!
64//! register_scheduler_implementation!(static SCHEDULER: MyScheduler = MyScheduler {});
65//! register_semaphore_implementation!(MySemaphore);
66//! register_timer_implementation!(MyTimer);
67//! register_queue_implementation!(MyQueue);
68//! ```
69//!
70//! ### Using `ipc-implementations`
71//!
72//! The implementor must implement the [`wait_queue::WaitQueueImplementation`] trait, and can use
73//! the various `Compat` types as the default implementations for the IPC types.
74//!
75//! You still have the option to provide custom implementations for the IPC types.
76//!
77//! ```rust, ignore
78//! use esp_radio_rtos_driver::{
79//!     SchedulerImplementation,
80//!     queue::CompatQueue,
81//!     register_queue_implementation,
82//!     register_scheduler_implementation,
83//!     register_semaphore_implementation,
84//!     register_timer_implementation,
85//!     register_wait_queue_implementation,
86//!     semaphore::CompatSemaphore,
87//!     timer::CompatTimer,
88//!     wait_queue::WaitQueueImplementation,
89//! };Expand commentComment on lines R78 to R89ResolvedCode has comments. Press enter to view.
90//!
91//! struct MyScheduler {
92//!     // ...
93//! }
94//! impl SchedulerImplementation for MyScheduler {
95//!     // ...
96//! }
97//!
98//! struct MyWaitQueue {
99//!     // ...
100//! }
101//! impl WaitQueueImplementation for MyWaitQueue {
102//!     // ...
103//! }
104//!
105//! register_scheduler_implementation!(static SCHEDULER: MyScheduler = MyScheduler {});
106//! register_wait_queue_implementation!(MyWaitQueue);
107//!
108//! register_semaphore_implementation!(CompatSemaphore);
109//! register_timer_implementation!(CompatTimer);
110//! register_queue_implementation!(CompatQueue);
111//! ```
112//!
113//! [`esp-rtos`]: https://crates.io/crates/esp-rtos
114
115#![no_std]
116
117// MUST be the first module
118mod fmt;
119
120pub mod queue;
121pub mod semaphore;
122pub mod timer;
123#[cfg(feature = "ipc-implementations")]
124pub mod wait_queue;
125
126use core::{ffi::c_void, ptr::NonNull};
127
128// Timer callbacks need to be heap-allocated.
129extern crate alloc;
130
131use crate::semaphore::SemaphorePtr;
132
133pub type ThreadPtr = NonNull<()>;
134
135unsafe extern "Rust" {
136    fn esp_rtos_initialized() -> bool;
137    fn esp_rtos_yield_task();
138    fn esp_rtos_yield_task_from_isr();
139    fn esp_rtos_current_task() -> ThreadPtr;
140    fn esp_rtos_max_task_priority() -> u32;
141    fn esp_rtos_task_create(
142        name: &str,
143        task: extern "C" fn(*mut c_void),
144        param: *mut c_void,
145        priority: u32,
146        pin_to_core: Option<u32>,
147        task_stack_size: usize,
148    ) -> ThreadPtr;
149    fn esp_rtos_schedule_task_deletion(task_handle: Option<ThreadPtr>);
150    fn esp_rtos_current_task_thread_semaphore() -> SemaphorePtr;
151    #[cfg(feature = "ipc-implementations")]
152    fn esp_rtos_task_priority(task: ThreadPtr) -> u32;
153    #[cfg(feature = "ipc-implementations")]
154    fn esp_rtos_set_task_priority(task: ThreadPtr, priority: u32);
155
156    fn esp_rtos_usleep(us: u32);
157    fn esp_rtos_usleep_until(us: u64);
158    fn esp_rtos_now() -> u64;
159}
160
161/// Set the Scheduler implementation.
162///
163/// See the [module documentation][crate] for an example.
164#[macro_export]
165macro_rules! register_scheduler_implementation {
166    ($vis:vis static $driver:ident: $t: ty = $val:expr) => {
167        $vis static $driver: $t = $val;
168
169        #[unsafe(no_mangle)]
170        #[inline]
171        fn esp_rtos_initialized() -> bool {
172            <$t as $crate::SchedulerImplementation>::initialized(&$driver)
173        }
174
175        #[unsafe(no_mangle)]
176        #[inline]
177        fn esp_rtos_yield_task() {
178            <$t as $crate::SchedulerImplementation>::yield_task(&$driver)
179        }
180
181        #[unsafe(no_mangle)]
182        #[inline]
183        fn esp_rtos_yield_task_from_isr() {
184            <$t as $crate::SchedulerImplementation>::yield_task_from_isr(&$driver)
185        }
186
187        #[unsafe(no_mangle)]
188        #[inline]
189        fn esp_rtos_current_task() -> $crate::ThreadPtr {
190            <$t as $crate::SchedulerImplementation>::current_task(&$driver)
191        }
192
193        #[unsafe(no_mangle)]
194        #[inline]
195        fn esp_rtos_max_task_priority() -> u32 {
196            <$t as $crate::SchedulerImplementation>::max_task_priority(&$driver)
197        }
198
199        #[unsafe(no_mangle)]
200        #[inline]
201        fn esp_rtos_task_create(
202            name: &str,
203            task: extern "C" fn(*mut c_void),
204            param: *mut c_void,
205            priority: u32,
206            core_id: Option<u32>,
207            task_stack_size: usize,
208        ) -> $crate::ThreadPtr {
209            <$t as $crate::SchedulerImplementation>::task_create(
210                &$driver,
211                name,
212                task,
213                param,
214                priority,
215                core_id,
216                task_stack_size,
217            )
218        }
219
220        #[unsafe(no_mangle)]
221        #[inline]
222        fn esp_rtos_schedule_task_deletion(task_handle: Option<$crate::ThreadPtr>) {
223            <$t as $crate::SchedulerImplementation>::schedule_task_deletion(&$driver, task_handle)
224        }
225
226        #[unsafe(no_mangle)]
227        #[inline]
228        fn esp_rtos_current_task_thread_semaphore() -> $crate::semaphore::SemaphorePtr {
229            <$t as $crate::SchedulerImplementation>::current_task_thread_semaphore(&$driver)
230        }
231
232        #[unsafe(no_mangle)]
233        #[inline]
234        fn esp_rtos_task_priority(task: $crate::ThreadPtr) -> u32 {
235            unsafe { <$t as $crate::SchedulerImplementation>::task_priority(&$driver, task) }
236        }
237
238        #[unsafe(no_mangle)]
239        #[inline]
240        fn esp_rtos_set_task_priority(task: $crate::ThreadPtr, priority: u32) {
241            unsafe { <$t as $crate::SchedulerImplementation>::set_task_priority(&$driver, task, priority) }
242        }
243
244        #[unsafe(no_mangle)]
245        #[inline]
246        fn esp_rtos_usleep(us: u32) {
247            <$t as $crate::SchedulerImplementation>::usleep(&$driver, us)
248        }
249
250        #[unsafe(no_mangle)]
251        #[inline]
252        fn esp_rtos_usleep_until(target: u64) {
253            <$t as $crate::SchedulerImplementation>::usleep_until(&$driver, target)
254        }
255
256        #[unsafe(no_mangle)]
257        #[inline]
258        fn esp_rtos_now() -> u64 {
259            <$t as $crate::SchedulerImplementation>::now(&$driver)
260        }
261    };
262}
263
264/// The scheduler interface.
265///
266/// This trait needs to be implemented by a driver crate to integrate esp-radio with a software
267/// platform.
268///
269/// The following snippet demonstrates the boilerplate necessary to implement a scheduler using the
270/// `Scheduler` trait:
271///
272/// ```rust, no_run
273/// use esp_radio_rtos_driver::{ThreadPtr, SchedulerImplementation, register_scheduler_implementation};
274/// struct MyScheduler {}
275///
276/// impl SchedulerImplementation for MyScheduler {
277///
278///     fn initialized(&self) -> bool {
279///         unimplemented!()
280///     }
281///
282///     fn yield_task(&self) {
283///         unimplemented!()
284///     }
285///
286///     fn yield_task_from_isr(&self) {
287///         unimplemented!()
288///     }
289///
290///     fn max_task_priority(&self) -> u32 {
291///         unimplemented!()
292///     }
293///
294///     fn task_create(
295///        &self,
296///        name: &str,
297///        task: extern "C" fn(*mut c_void),
298///        param: *mut c_void,
299///        priority: u32,
300///        pin_to_core: Option<u32>,
301///        task_stack_size: usize,
302///     ) -> ThreadPtr {
303///         unimplemented!()
304///     }
305///
306///     fn current_task(&self) -> ThreadPtr {
307///         unimplemented!()
308///     }
309///
310///     fn schedule_task_deletion(&self, task_handle: Option<ThreadPtr>) {
311///         unimplemented!()
312///     }
313///
314///     fn current_task_thread_semaphore(&self) -> SemaphorePtr {
315///         unimplemented!()
316///     }
317///
318///     fn task_priority(&self, task: ThreadPtr) -> u32 {
319///         unimplemented!()
320///     }
321///
322///     fn set_task_priority(&self, task: ThreadPtr, priority: u32) {
323///         unimplemented!()
324///     }
325///
326///     fn usleep(&self, us: u32) {
327///         unimplemented!()
328///     }
329///
330///     fn usleep_until(&self, target: u64) {
331///         unimplemented!()
332///     }
333///
334///     fn now(&self) -> u64 {
335///         unimplemented!()
336///     }
337/// }
338///
339/// register_scheduler_implementation!(static SCHEDULER: MyScheduler = MyScheduler {});
340/// ```
341pub trait SchedulerImplementation: Send + Sync + 'static {
342    /// This function is called by `esp_radio::init` to verify that the scheduler is properly set
343    /// up.
344    fn initialized(&self) -> bool;
345
346    /// This function is called by `esp_radio` to yield control to another task.
347    fn yield_task(&self);
348
349    /// This function is called by `esp_radio` to yield control to another task.
350    fn yield_task_from_isr(&self);
351
352    /// This function is called by `esp_radio::init` to retrieve a pointer to the current task.
353    fn current_task(&self) -> ThreadPtr;
354
355    /// This function returns the maximum task priority level.
356    /// Higher number is considered to be higher priority.
357    fn max_task_priority(&self) -> u32;
358
359    /// This function is used to create threads.
360    /// It should allocate the stack.
361    fn task_create(
362        &self,
363        name: &str,
364        task: extern "C" fn(*mut c_void),
365        param: *mut c_void,
366        priority: u32,
367        core_id: Option<u32>,
368        task_stack_size: usize,
369    ) -> ThreadPtr;
370
371    /// This function is called to let the scheduler know this thread is not
372    /// needed anymore and should be deleted. After this function is called,
373    /// the thread should not be scheduled anymore. The thread stack can be
374    /// free'ed.
375    ///
376    /// Passing `None` as the task handle should delete the currently running task.
377    fn schedule_task_deletion(&self, task_handle: Option<ThreadPtr>);
378
379    /// This function should return an opaque per-thread pointer to an
380    /// usize-sized memory location, which will be used to store a pointer
381    /// to a semaphore for this thread.
382    fn current_task_thread_semaphore(&self) -> SemaphorePtr;
383
384    /// This function returns the priority of the given task.
385    ///
386    /// # Safety
387    ///
388    /// The task pointer must be valid and point to a task that was created using
389    /// [`Self::task_create`].
390    unsafe fn task_priority(&self, task: ThreadPtr) -> u32;
391
392    /// This function sets the priority of the given task.
393    ///
394    /// # Safety
395    ///
396    /// The task pointer must be valid and point to a task that was created using
397    /// [`Self::task_create`].
398    unsafe fn set_task_priority(&self, task: ThreadPtr, priority: u32);
399
400    /// This function is called by a task to sleep for the specified number of microseconds.
401    fn usleep(&self, us: u32);
402
403    /// This function is called by a task to sleep until the specified timestamp.
404    ///
405    /// The timestamp is measured in microseconds, from the time the timer was started.
406    fn usleep_until(&self, target: u64);
407
408    /// Returns the current timestamp in microseconds.
409    ///
410    /// The underlying timer is expected not to overflow during the lifetime of the program.
411    ///
412    /// The clock that generates this timestamp must be the same one used to trigger timer events.
413    fn now(&self) -> u64;
414}
415
416// API used (mostly) by esp-radio
417
418/// Returns whether the task scheduler has been initialized.
419#[inline]
420pub fn initialized() -> bool {
421    unsafe { esp_rtos_initialized() }
422}
423
424/// Yields control to another task.
425#[inline]
426pub fn yield_task() {
427    unsafe { esp_rtos_yield_task() }
428}
429
430/// Yields control to another task for an interrupt.
431#[inline]
432pub fn yield_task_from_isr() {
433    unsafe { esp_rtos_yield_task_from_isr() }
434}
435
436/// Returns a pointer to the current task.
437#[inline]
438pub fn current_task() -> ThreadPtr {
439    unsafe { esp_rtos_current_task() }
440}
441
442/// Returns the maximum priority a task can have.
443///
444/// This function assumes that a bigger number means higher priority.
445#[inline]
446pub fn max_task_priority() -> u32 {
447    unsafe { esp_rtos_max_task_priority() }
448}
449
450/// Creates a new task with the given initial parameter and stack size.
451///
452/// ## Safety
453///
454/// The `param` parameter must be valid for the lifetime of the task. The data
455/// pointed to by `param` needs to be `Send` and the task takes ownership over it.
456#[inline]
457pub unsafe fn task_create(
458    name: &str,
459    task: extern "C" fn(*mut c_void),
460    param: *mut c_void,
461    priority: u32,
462    pin_to_core: Option<u32>,
463    task_stack_size: usize,
464) -> ThreadPtr {
465    let priority = priority.min(max_task_priority());
466    unsafe { esp_rtos_task_create(name, task, param, priority, pin_to_core, task_stack_size) }
467}
468
469/// Schedules the given task for deletion.
470///
471/// Passing `None` will schedule the current task to be deleted.
472///
473/// ## Safety
474///
475/// The `task_handle` must be a pointer to a task, obtained either by calling [`task_create`] or
476/// [`current_task`].
477#[inline]
478pub unsafe fn schedule_task_deletion(task_handle: Option<ThreadPtr>) {
479    unsafe { esp_rtos_schedule_task_deletion(task_handle) }
480}
481
482/// Returns a pointer to the current thread's semaphore.
483#[inline]
484pub fn current_task_thread_semaphore() -> SemaphorePtr {
485    unsafe { esp_rtos_current_task_thread_semaphore() }
486}
487
488/// Puts the current task to sleep for the specified number of microseconds.
489#[inline]
490pub fn usleep(us: u32) {
491    unsafe { esp_rtos_usleep(us) }
492}
493
494/// Puts the current task to sleep until the specified timestamp.
495///
496/// The timestamp is measured in microseconds, from the time the timer was started.
497#[inline]
498pub fn usleep_until(target: u64) {
499    unsafe { esp_rtos_usleep_until(target) }
500}
501
502/// Returns the current timestamp, in microseconds.
503#[inline]
504pub fn now() -> u64 {
505    unsafe { esp_rtos_now() }
506}
507
508#[inline]
509#[cfg(feature = "ipc-implementations")]
510unsafe fn task_priority(task: ThreadPtr) -> u32 {
511    unsafe { esp_rtos_task_priority(task) }
512}
513
514#[inline]
515#[cfg(feature = "ipc-implementations")]
516unsafe fn set_task_priority(task: ThreadPtr, priority: u32) {
517    unsafe { esp_rtos_set_task_priority(task, priority) }
518}