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!()]
18#![cfg_attr(
19 any(riscv, multi_core),
20 doc = "
21
22use esp_hal::interrupt::software::SoftwareInterruptControl;
23let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);"
24)]
25#![cfg_attr(
26 xtensa,
27 doc = "
28
29esp_rtos::start(timg0.timer0);"
30)]
31#![cfg_attr(
32 riscv,
33 doc = "
34
35esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);"
36)]
37#![cfg_attr(
38 all(xtensa, multi_core),
39 doc = "
40// Optionally, start the scheduler on the second core
41esp_rtos::start_second_core(
42 software_interrupt.software_interrupt0,
43 software_interrupt.software_interrupt1,
44 || {}, // Second core's main function.
45);
46"
47)]
48#![cfg_attr(
49 all(riscv, multi_core),
50 doc = "
51// Optionally, start the scheduler on the second core
52esp_rtos::start_second_core(
53 software_interrupt.software_interrupt1,
54 || {}, // Second core's main function.
55);
56"
57)]
58#![doc = ""]
59#![doc = ""]
70#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_rtos_config_table.md"))]
71#![doc = ""]
72#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
74#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
75#![no_std]
76#![cfg_attr(xtensa, feature(asm_experimental_arch))]
77#![cfg_attr(docsrs, feature(doc_cfg))]
78#![deny(missing_docs)]
79
80#[cfg(feature = "alloc")]
81extern crate alloc;
82
83mod fmt;
85
86#[cfg(feature = "esp-radio")]
87mod esp_radio;
88mod run_queue;
89mod scheduler;
90pub mod semaphore;
91mod task;
92mod timer;
93mod wait_queue;
94
95#[cfg(feature = "embassy")]
96#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
97pub mod embassy;
98
99use core::mem::MaybeUninit;
100
101#[cfg(feature = "alloc")]
102pub(crate) use esp_alloc::InternalMemory;
103#[cfg(any(multi_core, riscv))]
104use esp_hal::interrupt::software::SoftwareInterrupt;
105#[cfg(systimer)]
106use esp_hal::timer::systimer::Alarm;
107#[cfg(timergroup)]
108use esp_hal::timer::timg::Timer;
109use esp_hal::{
110 Blocking,
111 system::Cpu,
112 time::{Duration, Instant},
113 timer::{AnyTimer, OneShotTimer, any::Degrade},
114};
115#[cfg(multi_core)]
116use esp_hal::{
117 peripherals::CPU_CTRL,
118 system::{CpuControl, Stack},
119};
120#[cfg(feature = "embassy")]
121#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
122pub use macros::rtos_main as main;
123pub(crate) use scheduler::SCHEDULER;
124pub use task::CurrentThreadHandle;
125
126use crate::{task::IdleFn, timer::TimeDriver};
127
128type TimeBase = OneShotTimer<'static, Blocking>;
129
130#[cfg(feature = "rtos-trace")]
132pub enum TraceEvents {
133 RunSchedule,
135
136 YieldTask,
138
139 TimerTickHandler,
141
142 ProcessTimerQueue,
144
145 #[cfg(feature = "embassy")]
147 ProcessEmbassyTimerQueue,
148}
149
150#[cfg(all(feature = "alloc", not(feature = "esp-alloc")))]
152mod esp_alloc {
153 use core::{alloc::Layout, ptr::NonNull};
154
155 use allocator_api2::alloc::{AllocError, Allocator};
156
157 unsafe extern "C" {
158 fn malloc_internal(size: usize) -> *mut u8;
159
160 fn free_internal(ptr: *mut u8);
161 }
162
163 pub struct InternalMemory;
165
166 unsafe impl Allocator for InternalMemory {
167 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
168 let ptr = if layout.align() <= 4 {
171 unsafe { malloc_internal(layout.size()) }
172 } else {
173 let extra = layout.align().max(4);
183
184 let allocation = unsafe { malloc_internal(layout.size() + extra) };
185
186 let ptr = allocation.wrapping_add(4);
188
189 let align_offset = ptr.align_offset(layout.align());
190
191 let data_ptr = ptr.wrapping_add(align_offset);
192 let prefix_ptr = data_ptr.wrapping_sub(4);
193
194 unsafe { prefix_ptr.cast::<usize>().write(align_offset) };
196
197 data_ptr
198 };
199
200 let ptr = NonNull::new(ptr).ok_or(AllocError)?;
201 Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
202 }
203
204 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
205 if layout.align() <= 4 {
208 unsafe { free_internal(ptr.as_ptr()) };
209 } else {
210 let prefix_ptr = ptr.as_ptr().wrapping_sub(4);
212 let prefix_bytes = unsafe { prefix_ptr.cast::<usize>().read() };
213
214 unsafe { free_internal(prefix_ptr.wrapping_sub(prefix_bytes)) };
215 }
216 }
217 }
218}
219
220pub trait TimerSource: private::Sealed + 'static {
224 fn timer(self) -> TimeBase;
226}
227
228mod private {
229 pub trait Sealed {}
230}
231
232impl private::Sealed for TimeBase {}
233impl private::Sealed for AnyTimer<'static> {}
234#[cfg(timergroup)]
235impl private::Sealed for Timer<'static> {}
236#[cfg(systimer)]
237impl private::Sealed for Alarm<'static> {}
238
239impl TimerSource for TimeBase {
240 fn timer(self) -> TimeBase {
241 self
242 }
243}
244
245impl TimerSource for AnyTimer<'static> {
246 fn timer(self) -> TimeBase {
247 TimeBase::new(self)
248 }
249}
250
251#[cfg(timergroup)]
252impl TimerSource for Timer<'static> {
253 fn timer(self) -> TimeBase {
254 TimeBase::new(self.degrade())
255 }
256}
257
258#[cfg(systimer)]
259impl TimerSource for Alarm<'static> {
260 fn timer(self) -> TimeBase {
261 TimeBase::new(self.degrade())
262 }
263}
264
265pub fn start(timer: impl TimerSource, #[cfg(riscv)] int0: SoftwareInterrupt<'static, 0>) {
274 start_with_idle_hook(
275 timer,
276 #[cfg(riscv)]
277 int0,
278 crate::task::idle_hook,
279 )
280}
281
282#[doc = ""]
298#[cfg_attr(
299 riscv,
300 doc = "The `int0` argument must be `SoftwareInterrupt<0>` which will be used to trigger context switches."
301)]
302#[doc = ""]
303pub fn start_with_idle_hook(
305 timer: impl TimerSource,
306 #[cfg(riscv)] int0: SoftwareInterrupt<'static, 0>,
307 idle_hook: IdleFn,
308) {
309 #[cfg(feature = "rtos-trace")]
310 {
311 rtos_trace::trace::name_marker(TraceEvents::YieldTask as u32, "yield task");
312 rtos_trace::trace::name_marker(TraceEvents::RunSchedule as u32, "run scheduler");
313 rtos_trace::trace::name_marker(TraceEvents::TimerTickHandler as u32, "timer tick handler");
314 rtos_trace::trace::name_marker(
315 TraceEvents::ProcessTimerQueue as u32,
316 "process timer queue",
317 );
318 rtos_trace::trace::name_marker(
319 TraceEvents::ProcessEmbassyTimerQueue as u32,
320 "process embassy timer queue",
321 );
322 rtos_trace::trace::start();
323 }
324
325 trace!("Starting scheduler for the first core");
326 assert_eq!(Cpu::current(), Cpu::ProCpu);
327
328 SCHEDULER.with(move |scheduler| {
329 scheduler.setup(TimeDriver::new(timer.timer()), idle_hook);
330
331 unsafe extern "C" {
334 static _stack_start_cpu0: u32;
335 static _stack_end_cpu0: u32;
336 static __stack_chk_guard: u32;
337 }
338 let stack_top = &raw const _stack_start_cpu0;
339 let stack_bottom = (&raw const _stack_end_cpu0).cast::<MaybeUninit<u32>>();
340 let stack_slice = core::ptr::slice_from_raw_parts_mut(
341 stack_bottom.cast_mut(),
342 stack_top as usize - stack_bottom as usize,
343 );
344
345 task::allocate_main_task(
346 scheduler,
347 stack_slice,
348 esp_config::esp_config_int!(usize, "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"),
349 unsafe { (&raw const __stack_chk_guard).read_volatile() },
352 );
353
354 task::setup_multitasking(
355 #[cfg(riscv)]
356 int0,
357 );
358
359 task::yield_task();
360 })
361}
362
363#[cfg(multi_core)]
374pub fn start_second_core<const STACK_SIZE: usize>(
375 cpu_control: CPU_CTRL,
376 #[cfg(xtensa)] int0: SoftwareInterrupt<'static, 0>,
377 int1: SoftwareInterrupt<'static, 1>,
378 stack: &'static mut Stack<STACK_SIZE>,
379 func: impl FnOnce() + Send + 'static,
380) {
381 start_second_core_with_stack_guard_offset::<STACK_SIZE>(
382 cpu_control,
383 #[cfg(xtensa)]
384 int0,
385 int1,
386 stack,
387 None,
388 func,
389 );
390}
391
392#[cfg(multi_core)]
407pub fn start_second_core_with_stack_guard_offset<const STACK_SIZE: usize>(
408 cpu_control: CPU_CTRL,
409 #[cfg(xtensa)] int0: SoftwareInterrupt<'static, 0>,
410 int1: SoftwareInterrupt<'static, 1>,
411 stack: &'static mut Stack<STACK_SIZE>,
412 stack_guard_offset: Option<usize>,
413 func: impl FnOnce() + Send + 'static,
414) {
415 trace!("Starting scheduler for the second core");
416
417 #[cfg(xtensa)]
418 task::setup_smp(int0);
419
420 struct SecondCoreStack {
421 stack: *mut [MaybeUninit<u32>],
422 }
423 unsafe impl Send for SecondCoreStack {}
424 let stack_ptrs = SecondCoreStack {
425 stack: core::ptr::slice_from_raw_parts_mut(
426 stack.bottom().cast::<MaybeUninit<u32>>(),
427 STACK_SIZE,
428 ),
429 };
430
431 let stack_guard_offset = stack_guard_offset.unwrap_or(esp_config::esp_config_int!(
432 usize,
433 "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"
434 ));
435
436 let mut cpu_control = CpuControl::new(cpu_control);
437 let guard = cpu_control
438 .start_app_core_with_stack_guard_offset(stack, Some(stack_guard_offset), move || {
439 trace!("Second core running");
440 task::setup_smp(int1);
441 SCHEDULER.with(move |scheduler| {
442 let ptrs = stack_ptrs;
444 assert!(
445 scheduler.time_driver.is_some(),
446 "The scheduler must be started on the first core first."
447 );
448
449 let stack_bottom = ptrs.stack.cast::<u32>();
453 let stack_guard = unsafe { stack_bottom.byte_add(stack_guard_offset) };
454
455 task::allocate_main_task(scheduler, ptrs.stack, stack_guard_offset, unsafe {
456 stack_guard.read()
457 });
458 task::yield_task();
459 trace!("Second core scheduler initialized");
460 });
461
462 func();
463
464 loop {
465 SCHEDULER.sleep_until(Instant::EPOCH + Duration::MAX);
466 }
467 })
468 .unwrap();
469
470 let start = Instant::now();
472
473 while start.elapsed() < Duration::from_secs(1) {
474 if SCHEDULER.with(|s| s.per_cpu[1].initialized) {
475 break;
476 }
477 esp_hal::rom::ets_delay_us(1);
478 }
479
480 if !SCHEDULER.with(|s| s.per_cpu[1].initialized) {
481 panic!(
482 "Second core scheduler failed to initialize. \
483 This can happen if its main function overflowed the stack."
484 );
485 }
486
487 core::mem::forget(guard);
488}
489
490const TICK_RATE: u32 = esp_config::esp_config_int!(u32, "ESP_RTOS_CONFIG_TICK_RATE_HZ");
491
492pub(crate) fn now() -> u64 {
493 Instant::now().duration_since_epoch().as_micros()
494}
495
496#[cfg(feature = "embassy")]
497embassy_time_driver::time_driver_impl!(static TIMER_QUEUE: crate::timer::embassy::TimerQueue = crate::timer::embassy::TimerQueue::new());
498
499fn with_deadline(timeout_us: Option<u32>, attempt: impl Fn(Instant) -> bool) -> bool {
504 let deadline = timeout_us
505 .map(|us| Instant::now() + Duration::from_micros(us as u64))
506 .unwrap_or(Instant::EPOCH + Duration::MAX);
507
508 while !attempt(deadline) {
509 if timeout_us.is_some() && deadline < Instant::now() {
515 return false;
517 }
518 }
520
521 true
522}