1#![cfg_attr(
2 all(docsrs, not(not_really_docsrs)),
3 doc = "<div style='padding:30px;background:#810;color:#fff;text-align:center;'><p>You might want to <a href='https://docs.espressif.com/projects/rust/'>browse the <code>esp-hal</code> documentation on the esp-rs website</a> instead.</p><p>The documentation here on <a href='https://docs.rs'>docs.rs</a> is built for a single chip only (ESP32-C6, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs change depending on the chip.</p></div>\n\n<br/>\n\n"
4)]
5#![doc = esp_hal::before_snippet!()]
17#![cfg_attr(
33 multi_core,
34 doc = "
35// Optionally, start the scheduler on the second core
36use static_cell::ConstStaticCell;
37use esp_hal::system::Stack;
38
39static STACK: ConstStaticCell<Stack<8192>> = ConstStaticCell::new(Stack::new());
40esp_rtos::start_second_core(
41 peripherals.CPU_CTRL,
42 peripherals.FROM_CPU_INTR1,
43 STACK.take(),
44 || {}, // Second core's main function.
45);
46"
47)]
48#![doc = ""]
49#![doc = esp_hal::after_snippet!()]
52#![cfg_attr(
58 multi_core,
59 doc = r"
60The scheduler can also run on the second core alone, which keeps the first core free for bare-metal
61code. See [`start_on_second_core_only`] for that configuration and its restrictions.
62"
63)]
64#![cfg_attr(
65 sleep_light_sleep,
66 doc = r"
67## Automatic Light Sleep (experimental)
68
69When enabled, the CPU will automatically enter light sleep mode when there are no tasks to run,
70and wake up when a task is ready to run. This can help reduce power consumption.
71
72Because waking up from automatic light sleep can increase latency, the minimum expected idle time
73can be configured using `ESP_RTOS_CONFIG_LIGHT_SLEEP_MIN_US`.
74
75To enable automatic light sleep, call the [`sleep::configure`] function and pass the
76idle hook from the returned [`Sleep`](sleep::Sleep) object to [`start_with_idle_hook`].
77
78To prevent the CPU from entering light sleep, take a [`WakeLock`]. Drop the lock when you are done
79with the critical code.
80"
81)]
82#![cfg_attr(
83 all(sleep_light_sleep, multi_core),
84 doc = r"
85
86⚠️ If you are using bare-metal code on the second core (i.e. the second core is
87not managed by the RTOS), make sure to take a [`WakeLock`], otherwise the automatic
88light sleep may cause unexpected behavior.
89"
90)]
91#![cfg_attr(
92 sleep_light_sleep,
93 doc = r"
94### Example
95
96```rust,no_run
97#![no_std]
98# #[panic_handler]
99# fn panic(_: &core::panic::PanicInfo) -> ! {
100# loop {}
101# }
102# struct FakeHeap;
103# unsafe impl core::alloc::GlobalAlloc for FakeHeap {
104# unsafe fn alloc(&self, _: core::alloc::Layout) -> *mut u8 {
105# unimplemented!()
106# }
107# unsafe fn dealloc(&self, _: *mut u8, _: core::alloc::Layout) {
108# unimplemented!()
109# }
110# }
111# #[global_allocator]
112# static ALLOCATOR: FakeHeap = FakeHeap;
113
114use esp_hal::timer::timg::TimerGroup;
115
116# fn main() {
117let p = esp_hal::init(esp_hal::Config::default());
118
119let timg0 = TimerGroup::new(p.TIMG0);
120
121let sleep = esp_rtos::sleep::configure(p.LPWR);
122
123esp_rtos::start_with_idle_hook(
124 timg0.timer0,
125 p.FROM_CPU_INTR0,
126 sleep.light_sleep_hook,
127);
128# }
129```
130
131[`WakeLock`]: esp_hal::rtc_cntl::WakeLock
132"
133)]
134#![doc = ""]
136#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_rtos_config_table.md"))]
137#![doc = ""]
138#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
140#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
141#![no_std]
142#![cfg_attr(xtensa, feature(asm_experimental_arch))]
143#![cfg_attr(docsrs, feature(doc_cfg))]
144#![deny(missing_docs)]
145
146#[cfg(feature = "alloc")]
147extern crate alloc;
148
149mod fmt;
151
152#[cfg(feature = "esp-radio")]
153mod esp_radio;
154mod run_queue;
155mod scheduler;
156#[cfg(sleep_light_sleep)]
157pub mod sleep;
158mod syscall;
159mod task;
160mod timer;
161#[cfg(feature = "esp-radio")]
163mod wait_queue;
164
165#[cfg(feature = "embassy")]
166#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
167pub mod embassy;
168
169use core::mem::MaybeUninit;
170
171#[cfg(feature = "alloc")]
172pub(crate) use esp_alloc::InternalMemory;
173#[cfg(systimer_driver_supported)]
174use esp_hal::timer::systimer::Alarm;
175#[cfg(timergroup_driver_supported)]
176use esp_hal::timer::timg::Timer;
177use esp_hal::{
178 Blocking,
179 peripherals::FROM_CPU_INTR0,
180 system::Cpu,
181 time::Instant,
182 timer::{AnyTimer, OneShotTimer, any::Degrade},
183};
184#[cfg(multi_core)]
185use esp_hal::{
186 peripherals::{CPU_CTRL, FROM_CPU_INTR1},
187 system::{CpuControl, Stack},
188 time::Duration,
189};
190#[cfg(feature = "embassy")]
191#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
192pub use macros::main;
193#[cfg(multi_core)]
194use scheduler::ActiveCores;
195pub(crate) use scheduler::SCHEDULER;
196pub use task::CurrentThreadHandle;
197
198use crate::{task::IdleFn, timer::TimeDriver};
199
200type TimeBase = OneShotTimer<'static, Blocking>;
201
202#[cfg(feature = "rtos-trace")]
204pub enum TraceEvents {
205 RunSchedule,
207
208 YieldTask,
210
211 TimerTickHandler,
213
214 ProcessTimerQueue,
216
217 #[cfg(feature = "embassy")]
219 ProcessEmbassyTimerQueue,
220}
221
222#[cfg(all(feature = "alloc", not(feature = "esp-alloc")))]
224mod esp_alloc {
225 use core::{alloc::Layout, ptr::NonNull};
226
227 use allocator_api2::alloc::{AllocError, Allocator};
228
229 unsafe extern "C" {
230 fn malloc_internal(size: usize) -> *mut u8;
231
232 fn free_internal(ptr: *mut u8);
233 }
234
235 pub struct InternalMemory;
237
238 unsafe impl Allocator for InternalMemory {
239 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
240 let ptr = if layout.align() <= 4 {
243 unsafe { malloc_internal(layout.size()) }
244 } else {
245 let extra = layout.align().max(4);
255
256 let allocation = unsafe { malloc_internal(layout.size() + extra) };
257
258 if allocation.is_null() {
259 return Err(AllocError);
260 }
261
262 let ptr = allocation.wrapping_add(4);
264
265 let align_offset = ptr.align_offset(layout.align());
266
267 let data_ptr = ptr.wrapping_add(align_offset);
268 let prefix_ptr = data_ptr.wrapping_sub(4);
269
270 unsafe { prefix_ptr.cast::<usize>().write(align_offset) };
272
273 data_ptr
274 };
275
276 let ptr = NonNull::new(ptr).ok_or(AllocError)?;
277 Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
278 }
279
280 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
281 if layout.align() <= 4 {
284 unsafe { free_internal(ptr.as_ptr()) };
285 } else {
286 let prefix_ptr = ptr.as_ptr().wrapping_sub(4);
288 let prefix_bytes = unsafe { prefix_ptr.cast::<usize>().read() };
289
290 unsafe { free_internal(prefix_ptr.wrapping_sub(prefix_bytes)) };
291 }
292 }
293 }
294}
295
296pub trait TimerSource: private::Sealed + 'static {
300 fn timer(self) -> TimeBase;
302}
303
304mod private {
305 pub trait Sealed {}
306}
307
308impl private::Sealed for TimeBase {}
309impl private::Sealed for AnyTimer<'static> {}
310#[cfg(timergroup_driver_supported)]
311impl private::Sealed for Timer<'static> {}
312#[cfg(systimer_driver_supported)]
313impl private::Sealed for Alarm<'static> {}
314
315impl TimerSource for TimeBase {
316 fn timer(self) -> TimeBase {
317 self
318 }
319}
320
321impl TimerSource for AnyTimer<'static> {
322 fn timer(self) -> TimeBase {
323 TimeBase::new(self)
324 }
325}
326
327#[cfg(timergroup_driver_supported)]
328impl TimerSource for Timer<'static> {
329 fn timer(self) -> TimeBase {
330 TimeBase::new(self.degrade())
331 }
332}
333
334#[cfg(systimer_driver_supported)]
335impl TimerSource for Alarm<'static> {
336 fn timer(self) -> TimeBase {
337 TimeBase::new(self.degrade())
338 }
339}
340
341fn init_tracing() {
342 #[cfg(feature = "rtos-trace")]
343 {
344 rtos_trace::trace::name_marker(TraceEvents::YieldTask as u32, "yield task");
345 rtos_trace::trace::name_marker(TraceEvents::RunSchedule as u32, "run scheduler");
346 rtos_trace::trace::name_marker(TraceEvents::TimerTickHandler as u32, "timer tick handler");
347 rtos_trace::trace::name_marker(
348 TraceEvents::ProcessTimerQueue as u32,
349 "process timer queue",
350 );
351 rtos_trace::trace::name_marker(
352 TraceEvents::ProcessEmbassyTimerQueue as u32,
353 "process embassy timer queue",
354 );
355 rtos_trace::trace::start();
356 }
357}
358
359fn assert_thread_mode(function: &str) {
360 assert!(
361 esp_hal::interrupt::RunLevel::current().is_thread(),
362 "{} must not be called from an interrupt handler",
363 function
364 );
365}
366
367pub fn start(timer: impl TimerSource, int0: FROM_CPU_INTR0<'static>) {
376 start_with_idle_hook(timer, int0, crate::task::idle_hook)
377}
378
379pub fn start_with_idle_hook(
397 timer: impl TimerSource,
398 int0: FROM_CPU_INTR0<'static>,
399 idle_hook: IdleFn,
400) {
401 init_tracing();
402
403 trace!("Starting scheduler for the first core");
404 assert_eq!(Cpu::current(), Cpu::ProCpu);
405 assert_thread_mode("esp_rtos::start");
406
407 SCHEDULER.with(move |scheduler| {
408 scheduler.setup(TimeDriver::new(timer.timer()), idle_hook);
409 syscall::setup_syscalls();
410
411 unsafe extern "C" {
414 static _stack_start_cpu0: u32;
415 static _stack_end_cpu0: u32;
416 static __stack_chk_guard: u32;
417 }
418 let stack_top = &raw const _stack_start_cpu0;
419 let stack_bottom = (&raw const _stack_end_cpu0).cast::<MaybeUninit<u32>>();
420 let stack_slice = core::ptr::slice_from_raw_parts_mut(
421 stack_bottom.cast_mut(),
422 (stack_top as usize - stack_bottom as usize) / 4,
423 );
424
425 task::allocate_main_task(
426 scheduler,
427 stack_slice,
428 esp_config::esp_config_int!(usize, "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"),
429 unsafe { (&raw const __stack_chk_guard).read_volatile() },
432 );
433
434 task::setup_multitasking(int0);
435
436 task::yield_task();
438 })
439}
440
441#[cfg(multi_core)]
452pub fn start_second_core<const STACK_SIZE: usize>(
453 cpu_control: CPU_CTRL,
454 int1: FROM_CPU_INTR1<'static>,
455 stack: &'static mut Stack<STACK_SIZE>,
456 func: impl FnOnce() + Send + 'static,
457) {
458 start_second_core_with_stack_guard_offset::<STACK_SIZE>(cpu_control, int1, stack, None, func);
459}
460
461#[cfg(multi_core)]
463struct SecondCoreStack {
464 stack: *mut [MaybeUninit<u32>],
465}
466
467#[cfg(multi_core)]
468unsafe impl Send for SecondCoreStack {}
469
470#[cfg(multi_core)]
471impl SecondCoreStack {
472 fn new<const STACK_SIZE: usize>(stack: &mut Stack<STACK_SIZE>) -> Self {
473 Self {
474 stack: core::ptr::slice_from_raw_parts_mut(
475 stack.bottom().cast::<MaybeUninit<u32>>(),
476 STACK_SIZE / 4,
477 ),
478 }
479 }
480
481 fn allocate_main_task(self, scheduler: &mut scheduler::SchedulerState, guard_offset: usize) {
486 let stack_bottom = self.stack.cast::<u32>();
490 let stack_guard = unsafe { stack_bottom.byte_add(guard_offset) };
491
492 task::allocate_main_task(scheduler, self.stack, guard_offset, unsafe {
493 stack_guard.read()
494 });
495 }
496}
497
498#[cfg(multi_core)]
499fn default_stack_guard_offset() -> usize {
500 esp_config::esp_config_int!(usize, "ESP_HAL_CONFIG_STACK_GUARD_OFFSET")
501}
502
503#[cfg(multi_core)]
505fn wait_for_second_core_scheduler() {
506 let start = Instant::now();
507
508 while start.elapsed() < Duration::from_secs(1) {
509 if SCHEDULER.with(|s| s.active_cores.contains(Cpu::AppCpu)) {
510 return;
511 }
512 esp_hal::rom::ets_delay_us(1);
513 }
514
515 panic!(
516 "Second core scheduler failed to initialize. \
517 This can happen if its main function overflowed the stack."
518 );
519}
520
521#[cfg(multi_core)]
522fn suspend_main_task() {
523 loop {
524 SCHEDULER.sleep_until(Instant::EPOCH + Duration::MAX);
525 }
526}
527
528#[cfg(multi_core)]
543pub fn start_second_core_with_stack_guard_offset<const STACK_SIZE: usize>(
544 cpu_control: CPU_CTRL,
545 int1: FROM_CPU_INTR1<'static>,
546 stack: &'static mut Stack<STACK_SIZE>,
547 stack_guard_offset: Option<usize>,
548 func: impl FnOnce() + Send + 'static,
549) {
550 trace!("Starting scheduler for the second core");
551
552 match SCHEDULER.with(|scheduler| scheduler.active_cores) {
553 ActiveCores::Single(Cpu::ProCpu) => {}
554 _ => unreachable!(),
555 }
556
557 let stack_ptrs = SecondCoreStack::new(stack);
558 let stack_guard_offset = stack_guard_offset.unwrap_or_else(default_stack_guard_offset);
559
560 let mut cpu_control = CpuControl::new(cpu_control);
561 let guard = cpu_control
562 .start_app_core_with_stack_guard_offset(stack, Some(stack_guard_offset), move || {
563 trace!("Second core running");
564 SCHEDULER.with(move |scheduler| {
565 task::setup_smp(int1);
566 assert!(
567 scheduler.time_driver.is_some(),
568 "The scheduler must be started on the first core first."
569 );
570
571 scheduler.active_cores = ActiveCores::All;
572
573 stack_ptrs.allocate_main_task(scheduler, stack_guard_offset);
574 task::yield_task();
575 trace!("Second core scheduler initialized");
576 });
577
578 func();
579 suspend_main_task();
580 })
581 .unwrap();
582
583 wait_for_second_core_scheduler();
584
585 core::mem::forget(guard);
586}
587
588#[doc = esp_hal::before_snippet!()]
619#[doc = esp_hal::after_snippet!()]
652#[cfg(multi_core)]
654pub fn start_on_second_core_only<const STACK_SIZE: usize>(
655 cpu_control: CPU_CTRL<'static>,
656 int1: FROM_CPU_INTR1<'static>,
657 timer: impl TimerSource + Send,
658 stack: &'static mut Stack<STACK_SIZE>,
659 func: impl FnOnce() + Send + 'static,
660) {
661 init_tracing();
662
663 trace!("Starting scheduler for the second core only");
664 assert_eq!(Cpu::current(), Cpu::ProCpu);
665 assert_thread_mode("esp_rtos::start_on_second_core_only");
666 assert!(
667 SCHEDULER.with(|scheduler| scheduler.time_driver.is_none()),
668 "The scheduler has already been started. \
669 esp_rtos::start_on_second_core_only must not be combined with esp_rtos::start."
670 );
671
672 let stack_ptrs = SecondCoreStack::new(stack);
673 let stack_guard_offset = default_stack_guard_offset();
674
675 let mut cpu_control = CpuControl::new(cpu_control);
676 let guard = cpu_control
677 .start_app_core_with_stack_guard_offset(stack, Some(stack_guard_offset), move || {
678 trace!("Second core running");
679 SCHEDULER.with(move |scheduler| {
680 task::setup_multitasking(int1);
681
682 scheduler.setup(TimeDriver::new(timer.timer()), crate::task::idle_hook);
686 syscall::setup_syscalls();
687
688 stack_ptrs.allocate_main_task(scheduler, stack_guard_offset);
689 task::yield_task();
690 trace!("Second core scheduler initialized");
691 });
692
693 func();
694 suspend_main_task();
695 })
696 .unwrap();
697
698 wait_for_second_core_scheduler();
699
700 core::mem::forget(guard);
701}
702
703const TICK_RATE: u32 = esp_config::esp_config_int!(u32, "ESP_RTOS_CONFIG_TICK_RATE_HZ");
704
705pub(crate) fn now() -> u64 {
706 Instant::now().duration_since_epoch().as_micros()
707}
708
709pub fn rearm_alarm() {
713 SCHEDULER.with(|s| {
714 if let Some(time_driver) = s.time_driver.as_mut() {
715 time_driver.rearm(now());
716 }
717 });
718}
719
720#[cfg(feature = "embassy")]
721embassy_time_driver::time_driver_impl!(static TIMER_QUEUE: crate::timer::embassy::EmbassyTimeDriver = crate::timer::embassy::EmbassyTimeDriver);