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}