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::{AtomicPtr, Ordering},
7};
8
9#[instability::unstable]
10pub use crate::soc::cpu_control::is_running;
11use crate::{
12    peripherals::CPU_CTRL,
13    soc::cpu_control::{internal_park_core, start_core1_init},
14    system::Cpu,
15};
16
17/// Data type for a properly aligned stack of N bytes
18// Xtensa ISA 10.5: [B]y default, the
19// stack frame is 16-byte aligned. However, the maximal alignment allowed for a
20// TIE ctype is 64-bytes. If a function has any wide-aligned (>16-byte aligned)
21// data type for their arguments or the return values, the caller has to ensure
22// that the SP is aligned to the largest alignment right before the call.
23//
24// ^ this means that we should be able to get away with 16 bytes of alignment
25// because our root stack frame has no arguments and no return values.
26//
27// This alignment also doesn't align the stack frames, only the end of stack.
28// Stack frame alignment depends on the SIZE as well as the placement of the
29// array.
30#[repr(C, align(16))]
31#[instability::unstable]
32pub struct Stack<const SIZE: usize> {
33    /// Memory to be used for the stack
34    pub mem: MaybeUninit<[u8; SIZE]>,
35}
36
37impl<const SIZE: usize> Default for Stack<SIZE> {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43#[allow(clippy::len_without_is_empty)]
44impl<const SIZE: usize> Stack<SIZE> {
45    /// Creates a new stack of length SIZE, uninitialized.
46    #[instability::unstable]
47    pub const fn new() -> Stack<SIZE> {
48        const {
49            // Make sure stack top is aligned, too.
50            ::core::assert!(SIZE.is_multiple_of(16));
51        }
52
53        Stack {
54            mem: MaybeUninit::uninit(),
55        }
56    }
57
58    /// Returns the length of the stack in bytes.
59    #[instability::unstable]
60    pub const fn len(&self) -> usize {
61        SIZE
62    }
63
64    /// Returns a mutable pointer to the bottom of the stack.
65    #[instability::unstable]
66    pub fn bottom(&mut self) -> *mut u32 {
67        self.mem.as_mut_ptr() as *mut u32
68    }
69
70    /// Returns a mutable pointer to the top of the stack.
71    #[instability::unstable]
72    pub fn top(&mut self) -> *mut u32 {
73        unsafe { self.bottom().add(SIZE / 4) }
74    }
75}
76
77// Pointer to the closure that will be executed on the second core. The closure
78// is copied to the core's stack.
79pub(crate) static START_CORE1_FUNCTION: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
80pub(crate) static APP_CORE_STACK_TOP: AtomicPtr<u32> = AtomicPtr::new(core::ptr::null_mut());
81pub(crate) static APP_CORE_STACK_GUARD: AtomicPtr<u32> = AtomicPtr::new(core::ptr::null_mut());
82
83/// Will park the APP (second) core when dropped.
84#[must_use = "Dropping this guard will park the APP core"]
85#[instability::unstable]
86pub struct AppCoreGuard<'a> {
87    phantom: PhantomData<&'a ()>,
88}
89
90impl Drop for AppCoreGuard<'_> {
91    fn drop(&mut self) {
92        unsafe { internal_park_core(Cpu::AppCpu, true) };
93    }
94}
95
96/// Represents errors that can occur while working with the core.
97#[derive(Debug, Clone, Copy, PartialEq)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99#[instability::unstable]
100pub enum Error {
101    /// The core is already running.
102    CoreAlreadyRunning,
103}
104
105#[procmacros::doc_replace]
106/// Control CPU Cores
107///
108/// # Examples
109///
110/// ```rust, no_run
111/// # {before_snippet}
112/// # use esp_hal::delay::Delay;
113/// # use esp_hal::system::{CpuControl, Stack};
114/// # use core::{cell::RefCell, ptr::addr_of_mut};
115/// # use critical_section::Mutex;
116/// # let delay = Delay::new();
117/// static mut APP_CORE_STACK: Stack<8192> = Stack::new();
118///
119/// let counter = Mutex::new(RefCell::new(0));
120///
121/// let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL);
122/// let cpu1_fnctn = || {
123///     cpu1_task(&delay, &counter);
124/// };
125/// let _guard =
126///     cpu_control.start_app_core(unsafe { &mut *addr_of_mut!(APP_CORE_STACK) }, cpu1_fnctn)?;
127///
128/// loop {
129///     delay.delay(Duration::from_secs(1));
130///     let count = critical_section::with(|cs| *counter.borrow_ref(cs));
131/// }
132/// # }
133///
134/// // Where `cpu1_task()` may be defined as:
135/// # use esp_hal::delay::Delay;
136/// # use core::cell::RefCell;
137///
138/// fn cpu1_task(delay: &Delay, counter: &critical_section::Mutex<RefCell<i32>>) -> ! {
139///     loop {
140///         delay.delay(Duration::from_millis(500));
141///
142///         critical_section::with(|cs| {
143///             let mut val = counter.borrow_ref_mut(cs);
144///             *val = val.wrapping_add(1);
145///         });
146///     }
147/// }
148/// ```
149#[instability::unstable]
150pub struct CpuControl<'d> {
151    _cpu_control: CPU_CTRL<'d>,
152}
153
154impl<'d> CpuControl<'d> {
155    /// Creates a new instance of `CpuControl`.
156    #[instability::unstable]
157    pub fn new(cpu_control: CPU_CTRL<'d>) -> CpuControl<'d> {
158        CpuControl {
159            _cpu_control: cpu_control,
160        }
161    }
162
163    /// Parks the given core.
164    ///
165    /// # Safety
166    ///
167    /// The caller must ensure that the core being parked is not the core which is
168    /// currently executing this code.
169    #[instability::unstable]
170    pub unsafe fn park_core(&mut self, core: Cpu) {
171        unsafe { internal_park_core(core, true) };
172    }
173
174    /// Unparks the given core.
175    #[instability::unstable]
176    pub fn unpark_core(&mut self, core: Cpu) {
177        unsafe { internal_park_core(core, false) };
178    }
179
180    /// Runs the core1 closure.
181    #[inline(never)]
182    pub(crate) unsafe fn start_core1_run<F>() -> !
183    where
184        F: FnOnce(),
185    {
186        let entry = START_CORE1_FUNCTION.load(Ordering::Acquire);
187        debug_assert!(!entry.is_null());
188
189        unsafe {
190            let entry = ManuallyDrop::take(&mut *entry.cast::<ManuallyDrop<F>>());
191            entry();
192            loop {
193                internal_park_core(Cpu::current(), true);
194            }
195        }
196    }
197
198    /// Starts the APP (second) core.
199    ///
200    /// The second core starts running the closure `entry`. If the closure exits, the core is
201    /// parked.
202    ///
203    /// Dropping the returned guard will park the core.
204    #[instability::unstable]
205    pub fn start_app_core<'a, const SIZE: usize, F>(
206        &mut self,
207        stack: &'static mut Stack<SIZE>,
208        entry: F,
209    ) -> Result<AppCoreGuard<'a>, Error>
210    where
211        F: FnOnce(),
212        F: Send + 'a,
213    {
214        cfg_select! {
215            all(stack_guard_monitoring) => {
216                let stack_guard_offset = Some(esp_config::esp_config_int!(
217                    usize,
218                    "ESP_HAL_CONFIG_STACK_GUARD_OFFSET"
219                ));
220            }
221            _ => {
222                let stack_guard_offset = None;
223            }
224        };
225
226        self.start_app_core_with_stack_guard_offset(stack, stack_guard_offset, entry)
227    }
228
229    /// Starts the APP (second) core.
230    ///
231    /// The second core starts running the closure `entry`. If the closure exits, the core is
232    /// parked.
233    ///
234    /// Dropping the returned guard will park the core.
235    #[instability::unstable]
236    pub fn start_app_core_with_stack_guard_offset<'a, const SIZE: usize, F>(
237        &mut self,
238        stack: &'static mut Stack<SIZE>,
239        stack_guard_offset: Option<usize>,
240        entry: F,
241    ) -> Result<AppCoreGuard<'a>, Error>
242    where
243        F: FnOnce(),
244        F: Send + 'a,
245    {
246        if !crate::debugger::debugger_connected() && is_running(Cpu::AppCpu) {
247            return Err(Error::CoreAlreadyRunning);
248        }
249
250        setup_second_core_stack(stack, stack_guard_offset, entry);
251
252        crate::soc::cpu_control::start_core1(start_core1_init::<F> as *const u32);
253
254        self.unpark_core(Cpu::AppCpu);
255
256        Ok(AppCoreGuard {
257            phantom: PhantomData,
258        })
259    }
260}
261
262fn setup_second_core_stack<'a, F, const SIZE: usize>(
263    stack: &'static mut Stack<SIZE>,
264    stack_guard_offset: Option<usize>,
265    entry: F,
266) where
267    F: FnOnce(),
268    F: Send + 'a,
269{
270    // We don't want to drop this, since it's getting moved to the other core.
271    let entry = ManuallyDrop::new(entry);
272
273    unsafe {
274        let stack_bottom = stack.bottom().cast::<u8>();
275        let (stack_guard, stack_bottom_above_guard) =
276            if let Some(stack_guard_offset) = stack_guard_offset {
277                assert!(stack_guard_offset.is_multiple_of(4));
278                assert!(stack_guard_offset <= stack.len() - 4);
279                (
280                    stack_bottom.byte_add(stack_guard_offset),
281                    stack_bottom.byte_add(stack_guard_offset).byte_add(4),
282                )
283            } else {
284                (core::ptr::null_mut(), stack_bottom)
285            };
286
287        // Push `entry` to an aligned address at the (physical) bottom of the stack, but above
288        // the stack guard. The second core will copy it into its proper place, then
289        // calls it.
290        let align_offset = stack_bottom_above_guard.align_offset(core::mem::align_of::<F>());
291        let entry_dst = stack_bottom_above_guard
292            .add(align_offset)
293            .cast::<ManuallyDrop<F>>();
294
295        entry_dst.write(entry);
296
297        let entry_fn = entry_dst.cast::<()>();
298        START_CORE1_FUNCTION.store(entry_fn, Ordering::Release);
299        APP_CORE_STACK_TOP.store(stack.top(), Ordering::Release);
300        APP_CORE_STACK_GUARD.store(stack_guard.cast(), Ordering::Release);
301    }
302}