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(
24 multi_core,
25 doc = "
26// Optionally, start the scheduler on the second core
27esp_rtos::start_second_core(
28 software_interrupt.software_interrupt1,
29 || {}, // Second core's main function.
30);
31"
32)]
33#![doc = ""]
34#![doc = ""]
45#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_rtos_config_table.md"))]
46#![doc = ""]
47#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
49#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
50#![no_std]
51#![cfg_attr(xtensa, feature(asm_experimental_arch))]
52#![cfg_attr(docsrs, feature(doc_cfg))]
53#![deny(missing_docs)]
54
55#[cfg(feature = "alloc")]
56extern crate alloc;
57
58mod fmt;
60
61#[cfg(feature = "esp-radio")]
62mod esp_radio;
63mod run_queue;
64mod scheduler;
65mod syscall;
66mod task;
67mod timer;
68#[cfg(feature = "esp-radio")]
70mod wait_queue;
71
72#[cfg(feature = "embassy")]
73#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
74pub mod embassy;
75
76use core::mem::MaybeUninit;
77
78#[cfg(feature = "alloc")]
79pub(crate) use esp_alloc::InternalMemory;
80#[cfg(systimer_driver_supported)]
81use esp_hal::timer::systimer::Alarm;
82#[cfg(timergroup_driver_supported)]
83use esp_hal::timer::timg::Timer;
84use esp_hal::{
85 Blocking,
86 interrupt::software::SoftwareInterrupt,
87 system::Cpu,
88 time::Instant,
89 timer::{AnyTimer, OneShotTimer, any::Degrade},
90};
91#[cfg(multi_core)]
92use esp_hal::{
93 peripherals::CPU_CTRL,
94 system::{CpuControl, Stack},
95 time::Duration,
96};
97#[cfg(feature = "embassy")]
98#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
99pub use macros::rtos_main as main;
100pub(crate) use scheduler::SCHEDULER;
101pub use task::CurrentThreadHandle;
102
103use crate::{task::IdleFn, timer::TimeDriver};
104
105type TimeBase = OneShotTimer<'static, Blocking>;
106
107#[cfg(feature = "rtos-trace")]
109pub enum TraceEvents {
110 RunSchedule,
112
113 YieldTask,
115
116 TimerTickHandler,
118
119 ProcessTimerQueue,
121
122 #[cfg(feature = "embassy")]
124 ProcessEmbassyTimerQueue,
125}
126
127#[cfg(all(feature = "alloc", not(feature = "esp-alloc")))]
129mod esp_alloc {
130 use core::{alloc::Layout, ptr::NonNull};
131
132 use allocator_api2::alloc::{AllocError, Allocator};
133
134 unsafe extern "C" {
135 fn malloc_internal(size: usize) -> *mut u8;
136
137 fn free_internal(ptr: *mut u8);
138 }
139
140 pub struct InternalMemory;
142
143 unsafe impl Allocator for InternalMemory {
144 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
145 let ptr = if layout.align() <= 4 {
148 unsafe { malloc_internal(layout.size()) }
149 } else {
150 let extra = layout.align().max(4);
160
161 let allocation = unsafe { malloc_internal(layout.size() + extra) };
162
163 if allocation.is_null() {
164 return Err(AllocError);
165 }
166
167 let ptr = allocation.wrapping_add(4);
169
170 let align_offset = ptr.align_offset(layout.align());
171
172 let data_ptr = ptr.wrapping_add(align_offset);
173 let prefix_ptr = data_ptr.wrapping_sub(4);
174
175 unsafe { prefix_ptr.cast::<usize>().write(align_offset) };
177
178 data_ptr
179 };
180
181 let ptr = NonNull::new(ptr).ok_or(AllocError)?;
182 Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
183 }
184
185 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
186 if layout.align() <= 4 {
189 unsafe { free_internal(ptr.as_ptr()) };
190 } else {
191 let prefix_ptr = ptr.as_ptr().wrapping_sub(4);
193 let prefix_bytes = unsafe { prefix_ptr.cast::<usize>().read() };
194
195 unsafe { free_internal(prefix_ptr.wrapping_sub(prefix_bytes)) };
196 }
197 }
198 }
199}
200
201pub trait TimerSource: private::Sealed + 'static {
205 fn timer(self) -> TimeBase;
207}
208
209mod private {
210 pub trait Sealed {}
211}
212
213impl private::Sealed for TimeBase {}
214impl private::Sealed for AnyTimer<'static> {}
215#[cfg(timergroup_driver_supported)]
216impl private::Sealed for Timer<'static> {}
217#[cfg(systimer_driver_supported)]
218impl private::Sealed for Alarm<'static> {}
219
220impl TimerSource for TimeBase {
221 fn timer(self) -> TimeBase {
222 self
223 }
224}
225
226impl TimerSource for AnyTimer<'static> {
227 fn timer(self) -> TimeBase {
228 TimeBase::new(self)
229 }
230}
231
232#[cfg(timergroup_driver_supported)]
233impl TimerSource for Timer<'static> {
234 fn timer(self) -> TimeBase {
235 TimeBase::new(self.degrade())
236 }
237}
238
239#[cfg(systimer_driver_supported)]
240impl TimerSource for Alarm<'static> {
241 fn timer(self) -> TimeBase {
242 TimeBase::new(self.degrade())
243 }
244}
245
246pub fn start(timer: impl TimerSource, int0: SoftwareInterrupt<'static, 0>) {
255 start_with_idle_hook(timer, int0, crate::task::idle_hook)
256}
257
258pub fn start_with_idle_hook(
279 timer: impl TimerSource,
280 int0: SoftwareInterrupt<'static, 0>,
281 idle_hook: IdleFn,
282) {
283 #[cfg(feature = "rtos-trace")]
284 {
285 rtos_trace::trace::name_marker(TraceEvents::YieldTask as u32, "yield task");
286 rtos_trace::trace::name_marker(TraceEvents::RunSchedule as u32, "run scheduler");
287 rtos_trace::trace::name_marker(TraceEvents::TimerTickHandler as u32, "timer tick handler");
288 rtos_trace::trace::name_marker(
289 TraceEvents::ProcessTimerQueue as u32,
290 "process timer queue",
291 );
292 rtos_trace::trace::name_marker(
293 TraceEvents::ProcessEmbassyTimerQueue as u32,
294 "process embassy timer queue",
295 );
296 rtos_trace::trace::start();
297 }
298
299 fn is_thread_mode() -> bool {
300 esp_hal::interrupt::RunLevel::current().is_thread()
301 }
302
303 trace!("Starting scheduler for the first core");
304 assert_eq!(Cpu::current(), Cpu::ProCpu);
305 assert!(
306 is_thread_mode(),
307 "esp_rtos::start must not be called from an interrupt handler"
308 );
309
310 SCHEDULER.with(move |scheduler| {
311 scheduler.setup(TimeDriver::new(timer.timer()), idle_hook);
312 syscall::setup_syscalls();
313
314 unsafe extern "C" {
317 static _stack_start_cpu0: u32;
318 static _stack_end_cpu0: u32;
319 static __stack_chk_guard: u32;
320 }
321 let stack_top = &raw const _stack_start_cpu0;
322 let stack_bottom = (&raw const _stack_end_cpu0).cast::<MaybeUninit<u32>>();
323 let stack_slice = core::ptr::slice_from_raw_parts_mut(
324 stack_bottom.cast_mut(),
325 stack_top as usize - stack_bottom as usize,
326 );
327
328 task::allocate_main_task(
329 scheduler,
330 stack_slice,
331 esp_config::esp_config_int!(usize, "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"),
332 unsafe { (&raw const __stack_chk_guard).read_volatile() },
335 );
336
337 task::setup_multitasking(int0);
338
339 task::yield_task();
341 })
342}
343
344#[cfg(multi_core)]
355pub fn start_second_core<const STACK_SIZE: usize>(
356 cpu_control: CPU_CTRL,
357 int1: SoftwareInterrupt<'static, 1>,
358 stack: &'static mut Stack<STACK_SIZE>,
359 func: impl FnOnce() + Send + 'static,
360) {
361 start_second_core_with_stack_guard_offset::<STACK_SIZE>(cpu_control, int1, stack, None, func);
362}
363
364#[cfg(multi_core)]
379pub fn start_second_core_with_stack_guard_offset<const STACK_SIZE: usize>(
380 cpu_control: CPU_CTRL,
381 int1: SoftwareInterrupt<'static, 1>,
382 stack: &'static mut Stack<STACK_SIZE>,
383 stack_guard_offset: Option<usize>,
384 func: impl FnOnce() + Send + 'static,
385) {
386 trace!("Starting scheduler for the second core");
387
388 struct SecondCoreStack {
389 stack: *mut [MaybeUninit<u32>],
390 }
391 unsafe impl Send for SecondCoreStack {}
392 let stack_ptrs = SecondCoreStack {
393 stack: core::ptr::slice_from_raw_parts_mut(
394 stack.bottom().cast::<MaybeUninit<u32>>(),
395 STACK_SIZE,
396 ),
397 };
398
399 let stack_guard_offset = stack_guard_offset.unwrap_or(esp_config::esp_config_int!(
400 usize,
401 "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"
402 ));
403
404 let mut cpu_control = CpuControl::new(cpu_control);
405 let guard = cpu_control
406 .start_app_core_with_stack_guard_offset(stack, Some(stack_guard_offset), move || {
407 trace!("Second core running");
408 task::setup_smp(int1);
409 SCHEDULER.with(move |scheduler| {
410 let ptrs = stack_ptrs;
412 assert!(
413 scheduler.time_driver.is_some(),
414 "The scheduler must be started on the first core first."
415 );
416
417 let stack_bottom = ptrs.stack.cast::<u32>();
421 let stack_guard = unsafe { stack_bottom.byte_add(stack_guard_offset) };
422
423 task::allocate_main_task(scheduler, ptrs.stack, stack_guard_offset, unsafe {
424 stack_guard.read()
425 });
426 task::yield_task();
427 trace!("Second core scheduler initialized");
428 });
429
430 func();
431
432 loop {
433 SCHEDULER.sleep_until(Instant::EPOCH + Duration::MAX);
434 }
435 })
436 .unwrap();
437
438 let start = Instant::now();
440
441 while start.elapsed() < Duration::from_secs(1) {
442 if SCHEDULER.with(|s| s.per_cpu[1].initialized) {
443 break;
444 }
445 esp_hal::rom::ets_delay_us(1);
446 }
447
448 if !SCHEDULER.with(|s| s.per_cpu[1].initialized) {
449 panic!(
450 "Second core scheduler failed to initialize. \
451 This can happen if its main function overflowed the stack."
452 );
453 }
454
455 core::mem::forget(guard);
456}
457
458const TICK_RATE: u32 = esp_config::esp_config_int!(u32, "ESP_RTOS_CONFIG_TICK_RATE_HZ");
459
460pub(crate) fn now() -> u64 {
461 Instant::now().duration_since_epoch().as_micros()
462}
463
464#[cfg(feature = "embassy")]
465embassy_time_driver::time_driver_impl!(static TIMER_QUEUE: crate::timer::embassy::EmbassyTimeDriver = crate::timer::embassy::EmbassyTimeDriver);