Skip to main content

esp_hal/system/
multi_core.rs

1//! Multi-core support
2
3use core::{
4    marker::PhantomData,
5    mem::{ManuallyDrop, MaybeUninit},
6    sync::atomic::{AtomicBool, AtomicPtr, Ordering},
7};
8
9#[instability::unstable]
10pub use crate::soc::cpu_control::is_running;
11use crate::{peripherals::CPU_CTRL, soc::cpu_control::internal_park_core, system::Cpu};
12
13/// Data type for a properly aligned stack of N bytes
14// Xtensa ISA 10.5: [B]y default, the
15// stack frame is 16-byte aligned. However, the maximal alignment allowed for a
16// TIE ctype is 64-bytes. If a function has any wide-aligned (>16-byte aligned)
17// data type for their arguments or the return values, the caller has to ensure
18// that the SP is aligned to the largest alignment right before the call.
19//
20// ^ this means that we should be able to get away with 16 bytes of alignment
21// because our root stack frame has no arguments and no return values.
22//
23// This alignment also doesn't align the stack frames, only the end of stack.
24// Stack frame alignment depends on the SIZE as well as the placement of the
25// array.
26#[repr(C, align(16))]
27#[instability::unstable]
28pub struct Stack<const SIZE: usize> {
29    /// Memory to be used for the stack
30    pub mem: MaybeUninit<[u8; SIZE]>,
31}
32
33impl<const SIZE: usize> Default for Stack<SIZE> {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39#[allow(clippy::len_without_is_empty)]
40impl<const SIZE: usize> Stack<SIZE> {
41    /// Creates a new stack of length SIZE, uninitialized.
42    #[instability::unstable]
43    pub const fn new() -> Stack<SIZE> {
44        const {
45            // Make sure stack top is aligned, too.
46            ::core::assert!(SIZE.is_multiple_of(16));
47        }
48
49        Stack {
50            mem: MaybeUninit::uninit(),
51        }
52    }
53
54    /// Returns the length of the stack in bytes.
55    #[instability::unstable]
56    pub const fn len(&self) -> usize {
57        SIZE
58    }
59
60    /// Returns a mutable pointer to the bottom of the stack.
61    #[instability::unstable]
62    pub fn bottom(&mut self) -> *mut u32 {
63        self.mem.as_mut_ptr() as *mut u32
64    }
65
66    /// Returns a mutable pointer to the top of the stack.
67    #[instability::unstable]
68    pub fn top(&mut self) -> *mut u32 {
69        unsafe { self.bottom().add(SIZE / 4) }
70    }
71}
72
73// Pointer to the closure that will be executed on the second core. The closure
74// is copied to the core's stack.
75pub(crate) static START_CORE1_FUNCTION: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
76pub(crate) static APP_CORE_STACK_TOP: AtomicPtr<u32> = AtomicPtr::new(core::ptr::null_mut());
77pub(crate) static APP_CORE_STACK_GUARD: AtomicPtr<u32> = AtomicPtr::new(core::ptr::null_mut());
78
79/// Will park the APP (second) core when dropped.
80#[must_use = "Dropping this guard will park the APP core"]
81#[instability::unstable]
82pub struct AppCoreGuard<'a> {
83    phantom: PhantomData<&'a ()>,
84}
85
86impl Drop for AppCoreGuard<'_> {
87    fn drop(&mut self) {
88        unsafe { internal_park_core(Cpu::AppCpu, true) };
89    }
90}
91
92/// Represents errors that can occur while working with the core.
93#[derive(Debug, Clone, Copy, PartialEq)]
94#[cfg_attr(feature = "defmt", derive(defmt::Format))]
95#[instability::unstable]
96pub enum Error {
97    /// The core is already running.
98    CoreAlreadyRunning,
99}
100
101#[procmacros::doc_replace]
102/// Control CPU Cores
103///
104/// # Examples
105///
106/// ```rust, no_run
107/// # {before_snippet}
108/// # use esp_hal::delay::Delay;
109/// # use esp_hal::system::{CpuControl, Stack};
110/// # use core::{cell::RefCell, ptr::addr_of_mut};
111/// # use critical_section::Mutex;
112/// # let delay = Delay::new();
113/// static mut APP_CORE_STACK: Stack<8192> = Stack::new();
114///
115/// let counter = Mutex::new(RefCell::new(0));
116///
117/// let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL);
118/// let cpu1_fnctn = || {
119///     cpu1_task(&delay, &counter);
120/// };
121/// let _guard =
122///     cpu_control.start_app_core(unsafe { &mut *addr_of_mut!(APP_CORE_STACK) }, cpu1_fnctn)?;
123///
124/// loop {
125///     delay.delay(Duration::from_secs(1));
126///     let count = critical_section::with(|cs| *counter.borrow_ref(cs));
127/// }
128/// # }
129///
130/// // Where `cpu1_task()` may be defined as:
131/// # use esp_hal::delay::Delay;
132/// # use core::cell::RefCell;
133///
134/// fn cpu1_task(delay: &Delay, counter: &critical_section::Mutex<RefCell<i32>>) -> ! {
135///     loop {
136///         delay.delay(Duration::from_millis(500));
137///
138///         critical_section::with(|cs| {
139///             let mut val = counter.borrow_ref_mut(cs);
140///             *val = val.wrapping_add(1);
141///         });
142///     }
143/// }
144/// ```
145#[instability::unstable]
146pub struct CpuControl<'d> {
147    _cpu_control: CPU_CTRL<'d>,
148}
149
150impl<'d> CpuControl<'d> {
151    /// Creates a new instance of `CpuControl`.
152    #[instability::unstable]
153    pub fn new(cpu_control: CPU_CTRL<'d>) -> CpuControl<'d> {
154        CpuControl {
155            _cpu_control: cpu_control,
156        }
157    }
158
159    /// Parks the given core.
160    ///
161    /// # Safety
162    ///
163    /// The caller must ensure that the core being parked is not the core which is
164    /// currently executing this code.
165    #[instability::unstable]
166    pub unsafe fn park_core(&mut self, core: Cpu) {
167        unsafe { internal_park_core(core, true) };
168    }
169
170    /// Unparks the given core.
171    #[instability::unstable]
172    pub fn unpark_core(&mut self, core: Cpu) {
173        unsafe { internal_park_core(core, false) };
174    }
175
176    /// Runs the core1 closure.
177    #[inline(never)]
178    pub(crate) unsafe fn start_core1_run<F>() -> !
179    where
180        F: FnOnce(),
181    {
182        let entry = START_CORE1_FUNCTION.load(Ordering::Acquire);
183        debug_assert!(!entry.is_null());
184
185        unsafe {
186            let entry = ManuallyDrop::take(&mut *entry.cast::<ManuallyDrop<F>>());
187            entry();
188            loop {
189                internal_park_core(Cpu::current(), true);
190            }
191        }
192    }
193
194    /// Starts the APP (second) core.
195    ///
196    /// The second core starts running the closure `entry`. The core enables interrupts before
197    /// `entry` runs. If the closure exits, the core is parked.
198    ///
199    /// Dropping the returned guard will park the core.
200    #[instability::unstable]
201    pub fn start_app_core<'a, const SIZE: usize, F>(
202        &mut self,
203        stack: &'static mut Stack<SIZE>,
204        entry: F,
205    ) -> Result<AppCoreGuard<'a>, Error>
206    where
207        F: FnOnce(),
208        F: Send + 'a,
209    {
210        cfg_select! {
211            all(stack_guard_monitoring) => {
212                let stack_guard_offset = Some(esp_config::esp_config_int!(
213                    usize,
214                    "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"
215                ));
216            }
217            _ => {
218                let stack_guard_offset = None;
219            }
220        };
221
222        self.start_app_core_with_stack_guard_offset(stack, stack_guard_offset, entry)
223    }
224
225    /// Starts the APP (second) core.
226    ///
227    /// The second core starts running the closure `entry`. The core enables interrupts before
228    /// `entry` runs. If the closure exits, the core is parked.
229    ///
230    /// Dropping the returned guard will park the core.
231    #[instability::unstable]
232    pub fn start_app_core_with_stack_guard_offset<'a, const SIZE: usize, F>(
233        &mut self,
234        stack: &'static mut Stack<SIZE>,
235        stack_guard_offset: Option<usize>,
236        entry: F,
237    ) -> Result<AppCoreGuard<'a>, Error>
238    where
239        F: FnOnce(),
240        F: Send + 'a,
241    {
242        if !crate::debugger::debugger_connected() && is_running(Cpu::AppCpu) {
243            return Err(Error::CoreAlreadyRunning);
244        }
245
246        setup_second_core_stack(stack, stack_guard_offset, entry);
247
248        APP_CORE_TAKES_IPC.store(false, Ordering::Release);
249        crate::soc::cpu_control::start_core1(start_core1_init::<F> as *const u32);
250
251        self.unpark_core(Cpu::AppCpu);
252
253        Ok(AppCoreGuard {
254            phantom: PhantomData,
255        })
256    }
257}
258
259fn setup_second_core_stack<'a, F, const SIZE: usize>(
260    stack: &'static mut Stack<SIZE>,
261    stack_guard_offset: Option<usize>,
262    entry: F,
263) where
264    F: FnOnce(),
265    F: Send + 'a,
266{
267    // We don't want to drop this, since it's getting moved to the other core.
268    let entry = ManuallyDrop::new(entry);
269
270    unsafe {
271        let stack_bottom = stack.bottom().cast::<u8>();
272        let (stack_guard, stack_bottom_above_guard) =
273            if let Some(stack_guard_offset) = stack_guard_offset {
274                assert!(stack_guard_offset.is_multiple_of(4));
275                assert!(stack_guard_offset <= stack.len() - 4);
276                (
277                    stack_bottom.byte_add(stack_guard_offset),
278                    stack_bottom.byte_add(stack_guard_offset).byte_add(4),
279                )
280            } else {
281                (core::ptr::null_mut(), stack_bottom)
282            };
283
284        // Push `entry` to an aligned address at the (physical) bottom of the stack, but above
285        // the stack guard. The second core will copy it into its proper place, then
286        // calls it.
287        let align_offset = stack_bottom_above_guard.align_offset(core::mem::align_of::<F>());
288        let entry_dst = stack_bottom_above_guard
289            .add(align_offset)
290            .cast::<ManuallyDrop<F>>();
291
292        entry_dst.write(entry);
293
294        let entry_fn = entry_dst.cast::<()>();
295        #[cfg(esp32s31)]
296        crate::soc::cpu_control::save_core0_pma(&mut *core0_pma_slot::<F>(entry_fn));
297        START_CORE1_FUNCTION.store(entry_fn, Ordering::Release);
298        APP_CORE_STACK_TOP.store(stack.top(), Ordering::Release);
299        APP_CORE_STACK_GUARD.store(stack_guard.cast(), Ordering::Release);
300    }
301}
302
303#[cfg(esp32s31)]
304fn core0_pma_slot<F>(entry: *mut ()) -> *mut [u32; 32] {
305    let end = entry.cast::<u8>().wrapping_add(core::mem::size_of::<F>());
306    end.wrapping_add(end.align_offset(core::mem::align_of::<u32>()))
307        .cast()
308}
309
310/// Core 1 entry point, set as the boot address.
311#[cfg(xtensa)]
312pub(crate) fn start_core1_init<F>() -> !
313where
314    F: FnOnce(),
315{
316    // disables interrupts
317    unsafe {
318        xtensa_lx::interrupt::set_mask(0);
319    }
320
321    // reset cycle compare registers
322    xtensa_lx::timer::set_ccompare0(0);
323    xtensa_lx::timer::set_ccompare1(0);
324    xtensa_lx::timer::set_ccompare2(0);
325
326    unsafe extern "C" {
327        static mut _init_start: u32;
328    }
329
330    // set vector table and stack pointer
331    unsafe {
332        xtensa_lx::set_vecbase(&raw const _init_start);
333        xtensa_lx::set_stack_pointer(APP_CORE_STACK_TOP.load(Ordering::Acquire));
334
335        #[cfg(all(feature = "rt", stack_guard_monitoring))]
336        {
337            let stack_guard = APP_CORE_STACK_GUARD.load(Ordering::Acquire);
338            stack_guard.write_volatile(esp_config::esp_config_int!(
339                u32,
340                "ESP_HAL_CONFIG_STACK_GUARD_VALUE"
341            ));
342            // setting 0 effectively disables the functionality
343            crate::debugger::set_stack_watchpoint(stack_guard as usize);
344        }
345    }
346
347    // The ROM has already handed off to us; clear the AppCpu boot address so a
348    // subsequent reset doesn't see a stale entry point. Matches IDF's
349    // `call_start_cpu1`.
350    cfg_select! {
351        esp32 => {
352            crate::peripherals::DPORT::regs()
353                .appcpu_ctrl_d()
354                .write(|w| unsafe { w.appcpu_boot_addr().bits(0) });
355        }
356        _ => crate::rom::ets_set_appcpu_boot_addr(0),
357    }
358
359    // Do not call setup_interrupts as that would disable peripheral interrupts, too.
360    unsafe { crate::interrupt::init_vectoring() };
361
362    enable_app_core_interrupts();
363
364    // Trampoline to run from the new stack.
365    // start_core1_run should _NEVER_ be inlined
366    // as we rely on the function call to use
367    // the new stack.
368    unsafe { CpuControl::start_core1_run::<F>() }
369}
370
371/// Core 1 entry point, set as the boot address.
372///
373/// ROM jumps here directly, bypassing `_start`, so `gp`, the FPU and the stack pointer need to be
374/// set up before entering regular Rust.
375#[cfg(riscv)]
376#[unsafe(naked)]
377pub(crate) extern "C" fn start_core1_init<F>() -> !
378where
379    F: FnOnce(),
380{
381    core::arch::naked_asm!(
382        ".option push",
383        ".option norelax",
384        "la gp, __global_pointer$",
385        "li ra, 0", // ensure probe-rs stops unwinding
386        ".option pop",
387        // Follow IDF's FPU initialization (rv_utils_enable_fpu, rv_utils_clear_fpu): enable the
388        // FPU in Initial state, touch fcsr (which makes the state Dirty), then clear the dirty
389        // bit to leave mstatus.FS in Clean state (0b10), which is what the interrupt handler
390        // expects.
391        "li t0, 0x2000",
392        "csrs mstatus, t0",
393        "li t0, 1",
394        "csrw fcsr, t0",
395        "li t0, 0x2000",
396        "csrc mstatus, t0",
397        // Switch to Core 1's stack (stored by Core 0 before releasing this core).
398        "la t0, {stack_top}",
399        "lw sp, 0(t0)",
400        "j {init}",
401        stack_top = sym APP_CORE_STACK_TOP,
402        init = sym start_core1_init_impl::<F>,
403    )
404}
405
406#[cfg(riscv)]
407fn start_core1_init_impl<F>() -> !
408where
409    F: FnOnce(),
410{
411    #[cfg(esp32s31)]
412    crate::soc::cpu_control::set_core1_pma(unsafe {
413        &*core0_pma_slot::<F>(START_CORE1_FUNCTION.load(Ordering::Acquire))
414    });
415
416    crate::soc::branch_predictor::enable();
417
418    // The ROM has already handed off to us; clear the AppCpu boot address so a subsequent software
419    // reset doesn't re-enter this stale entry point. Matches IDF's `call_start_cpu1`.
420    crate::rom::ets_set_appcpu_boot_addr(0);
421
422    unsafe {
423        #[cfg(all(feature = "rt", stack_guard_monitoring))]
424        {
425            let guard = APP_CORE_STACK_GUARD.load(Ordering::Acquire);
426            guard.write_volatile(esp_config::esp_config_int!(
427                u32,
428                "ESP_HAL_CONFIG_STACK_GUARD_VALUE"
429            ));
430            crate::debugger::set_stack_watchpoint(guard as usize);
431        }
432        crate::interrupt::init_vectoring();
433        #[cfg(feature = "rt")]
434        crate::interrupt::ipc::install_app();
435    }
436
437    enable_app_core_interrupts();
438
439    unsafe { CpuControl::start_core1_run::<F>() }
440}
441
442fn enable_app_core_interrupts() {
443    unsafe {
444        crate::interrupt::RunLevel::change(crate::interrupt::RunLevel::ThreadMode);
445        #[cfg(riscv)]
446        core::arch::asm!("csrsi mstatus, 8", options(nomem, nostack));
447    }
448    APP_CORE_TAKES_IPC.store(true, Ordering::Release);
449}
450
451static APP_CORE_TAKES_IPC: AtomicBool = AtomicBool::new(false);
452
453/// Returns `true` if [`CpuControl`] started the APP core in this boot. That core takes IPC
454/// interrupts unless it masks them.
455pub(crate) fn app_core_takes_ipc() -> bool {
456    APP_CORE_TAKES_IPC.load(Ordering::Acquire)
457}