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 scheduler must implement the following
13//! capabilities:
14//!
15//! - A preemptive task scheduler: [`Scheduler`]
16//! - Semaphores: [`semaphore::SemaphoreImplementation`]
17//! - Queues: [`queue::QueueImplementation`]
18//! - Timers (functions that are executed at a specific time): [`timer::TimerImplementation`]
19//!
20//! [`esp-rtos`]: https://crates.io/crates/esp-rtos
21
22#![no_std]
23
24pub mod queue;
25pub mod semaphore;
26pub mod timer;
27
28use core::ffi::c_void;
29
30// Timer callbacks need to be heap-allocated.
31extern crate alloc;
32
33use crate::semaphore::SemaphorePtr;
34
35unsafe extern "Rust" {
36 fn esp_rtos_initialized() -> bool;
37 fn esp_rtos_yield_task();
38 fn esp_rtos_yield_task_from_isr();
39 fn esp_rtos_current_task() -> *mut c_void;
40 fn esp_rtos_max_task_priority() -> u32;
41 fn esp_rtos_task_create(
42 name: &str,
43 task: extern "C" fn(*mut c_void),
44 param: *mut c_void,
45 priority: u32,
46 pin_to_core: Option<u32>,
47 task_stack_size: usize,
48 ) -> *mut c_void;
49 fn esp_rtos_schedule_task_deletion(task_handle: *mut c_void);
50 fn esp_rtos_current_task_thread_semaphore() -> SemaphorePtr;
51
52 fn esp_rtos_usleep(us: u32);
53 fn esp_rtos_now() -> u64;
54}
55
56/// Set the Scheduler implementation.
57///
58/// See the [module documentation][crate] for an example.
59#[macro_export]
60macro_rules! scheduler_impl {
61 ($vis:vis static $driver:ident: $t: ty = $val:expr) => {
62 $vis static $driver: $t = $val;
63
64 #[unsafe(no_mangle)]
65 #[inline]
66 fn esp_rtos_initialized() -> bool {
67 <$t as $crate::Scheduler>::initialized(&$driver)
68 }
69
70 #[unsafe(no_mangle)]
71 #[inline]
72 fn esp_rtos_yield_task() {
73 <$t as $crate::Scheduler>::yield_task(&$driver)
74 }
75
76 #[unsafe(no_mangle)]
77 #[inline]
78 fn esp_rtos_yield_task_from_isr() {
79 <$t as $crate::Scheduler>::yield_task_from_isr(&$driver)
80 }
81
82 #[unsafe(no_mangle)]
83 #[inline]
84 fn esp_rtos_current_task() -> *mut c_void {
85 <$t as $crate::Scheduler>::current_task(&$driver)
86 }
87
88 #[unsafe(no_mangle)]
89 #[inline]
90 fn esp_rtos_max_task_priority() -> u32 {
91 <$t as $crate::Scheduler>::max_task_priority(&$driver)
92 }
93
94 #[unsafe(no_mangle)]
95 #[inline]
96 fn esp_rtos_task_create(
97 name: &str,
98 task: extern "C" fn(*mut c_void),
99 param: *mut c_void,
100 priority: u32,
101 core_id: Option<u32>,
102 task_stack_size: usize,
103 ) -> *mut c_void {
104 <$t as $crate::Scheduler>::task_create(
105 &$driver,
106 name,
107 task,
108 param,
109 priority,
110 core_id,
111 task_stack_size,
112 )
113 }
114
115 #[unsafe(no_mangle)]
116 #[inline]
117 fn esp_rtos_schedule_task_deletion(task_handle: *mut c_void) {
118 <$t as $crate::Scheduler>::schedule_task_deletion(&$driver, task_handle)
119 }
120
121 #[unsafe(no_mangle)]
122 #[inline]
123 fn esp_rtos_current_task_thread_semaphore() -> $crate::semaphore::SemaphorePtr {
124 <$t as $crate::Scheduler>::current_task_thread_semaphore(&$driver)
125 }
126
127 #[unsafe(no_mangle)]
128 #[inline]
129 fn esp_rtos_usleep(us: u32) {
130 <$t as $crate::Scheduler>::usleep(&$driver, us)
131 }
132
133 #[unsafe(no_mangle)]
134 #[inline]
135 fn esp_rtos_now() -> u64 {
136 <$t as $crate::Scheduler>::now(&$driver)
137 }
138 };
139}
140
141/// The scheduler interface.
142///
143/// This trait needs to be implemented by a driver crate to integrate esp-radio with a software
144/// platform.
145///
146/// The following snippet demonstrates the boilerplate necessary to implement a scheduler using the
147/// `Scheduler` trait:
148///
149/// ```rust,no_run
150/// struct MyScheduler {}
151///
152/// impl esp_radio_rtos_driver::Scheduler for MyScheduler {
153///
154/// fn initialized(&self) -> bool {
155/// unimplemented!()
156/// }
157///
158/// fn yield_task(&self) {
159/// unimplemented!()
160/// }
161///
162/// fn yield_task_from_isr(&self) {
163/// unimplemented!()
164/// }
165///
166/// fn max_task_priority(&self) -> u32 {
167/// unimplemented!()
168/// }
169///
170/// fn task_create(
171/// &self,
172/// name: &str,
173/// task: extern "C" fn(*mut c_void),
174/// param: *mut c_void,
175/// priority: u32,
176/// pin_to_core: Option<u32>,
177/// task_stack_size: usize,
178/// ) -> *mut c_void {
179/// unimplemented!()
180/// }
181///
182/// fn current_task(&self) -> *mut c_void {
183/// unimplemented!()
184/// }
185///
186/// fn schedule_task_deletion(&self, task_handle: *mut c_void) {
187/// unimplemented!()
188/// }
189///
190/// fn current_task_thread_semaphore(&self) -> SemaphorePtr {
191/// unimplemented!()
192/// }
193///
194/// fn usleep(&self, us: u32) {
195/// unimplemented!()
196/// }
197///
198/// fn now(&self) -> u64 {
199/// unimplemented!()
200/// }
201/// }
202///
203/// esp_radio_rtos_driver::scheduler_impl!(static SCHEDULER: MyScheduler = MyScheduler {});
204/// ```
205pub trait Scheduler: Send + Sync + 'static {
206 /// This function is called by `esp_radio::init` to verify that the scheduler is properly set
207 /// up.
208 fn initialized(&self) -> bool;
209
210 /// This function is called by `esp_radio` to yield control to another task.
211 fn yield_task(&self);
212
213 /// This function is called by `esp_radio` to yield control to another task.
214 fn yield_task_from_isr(&self);
215
216 /// This function is called by `esp_radio::init` to retrieve a pointer to the current task.
217 fn current_task(&self) -> *mut c_void;
218
219 /// This function returns the maximum task priority level.
220 /// Higher number is considered to be higher priority.
221 fn max_task_priority(&self) -> u32;
222
223 /// This function is used to create threads.
224 /// It should allocate the stack.
225 fn task_create(
226 &self,
227 name: &str,
228 task: extern "C" fn(*mut c_void),
229 param: *mut c_void,
230 priority: u32,
231 core_id: Option<u32>,
232 task_stack_size: usize,
233 ) -> *mut c_void;
234
235 /// This function is called to let the scheduler know this thread is not
236 /// needed anymore and should be deleted. After this function is called,
237 /// the thread should not be scheduled anymore. The thread stack can be
238 /// free'ed.
239 ///
240 /// Passing `null` as the task handle should delete the currently running task.
241 fn schedule_task_deletion(&self, task_handle: *mut c_void);
242
243 /// This function should return an opaque per-thread pointer to an
244 /// usize-sized memory location, which will be used to store a pointer
245 /// to a semaphore for this thread.
246 fn current_task_thread_semaphore(&self) -> SemaphorePtr;
247
248 /// This function is called by a task to sleep for the specified number of microseconds.
249 fn usleep(&self, us: u32);
250
251 /// Returns the current timestamp in microseconds.
252 ///
253 /// The underlying timer is expected not to overflow during the lifetime of the program.
254 ///
255 /// The clock that generates this timestamp must be the same one used to trigger timer events.
256 fn now(&self) -> u64;
257}
258
259// API used (mostly) by esp-radio
260
261/// Returns whether the task scheduler has been initialized.
262#[inline]
263pub fn initialized() -> bool {
264 unsafe { esp_rtos_initialized() }
265}
266
267/// Yields control to another task.
268#[inline]
269pub fn yield_task() {
270 unsafe { esp_rtos_yield_task() }
271}
272
273/// Yields control to another task for an interrupt.
274#[inline]
275pub fn yield_task_from_isr() {
276 unsafe { esp_rtos_yield_task_from_isr() }
277}
278
279/// Returns a pointer to the current task.
280#[inline]
281pub fn current_task() -> *mut c_void {
282 unsafe { esp_rtos_current_task() }
283}
284
285/// Returns the maximum priority a task can have.
286///
287/// This function assumes that a bigger number means higher priority.
288#[inline]
289pub fn max_task_priority() -> u32 {
290 unsafe { esp_rtos_max_task_priority() }
291}
292
293/// Creates a new task with the given initial parameter and stack size.
294///
295/// ## Safety
296///
297/// The `param` parameter must be valid for the lifetime of the task. The data
298/// pointed to by `param` needs to be `Send` and the task takes ownership over it.
299#[inline]
300pub unsafe fn task_create(
301 name: &str,
302 task: extern "C" fn(*mut c_void),
303 param: *mut c_void,
304 priority: u32,
305 pin_to_core: Option<u32>,
306 task_stack_size: usize,
307) -> *mut c_void {
308 unsafe { esp_rtos_task_create(name, task, param, priority, pin_to_core, task_stack_size) }
309}
310
311/// Schedules the given task for deletion.
312///
313/// Passing `null` will schedule the current task to be deleted.
314///
315/// ## Safety
316///
317/// The `task_handle` must be a pointer to a task, obtained either by calling [`task_create`] or
318/// [`current_task`].
319#[inline]
320pub unsafe fn schedule_task_deletion(task_handle: *mut c_void) {
321 unsafe { esp_rtos_schedule_task_deletion(task_handle) }
322}
323
324/// Returns a pointer to the current thread's semaphore.
325#[inline]
326pub fn current_task_thread_semaphore() -> SemaphorePtr {
327 unsafe { esp_rtos_current_task_thread_semaphore() }
328}
329
330/// Puts the current task to sleep for the specified number of microseconds.
331#[inline]
332pub fn usleep(us: u32) {
333 unsafe { esp_rtos_usleep(us) }
334}
335
336/// Returns the current timestamp, in microseconds.
337#[inline]
338pub fn now() -> u64 {
339 unsafe { esp_rtos_now() }
340}