Skip to main content

rp235x_hal/
multicore.rs

1//! Multicore support
2//!
3//! This module handles setup of the 2nd cpu core on the rp235x, which we refer to as core1.
4//! It provides functionality for setting up the stack, and starting core1.
5//!
6//! The entrypoint for core1 can be any function that never returns, including closures.
7//!
8//! # Usage
9//!
10//! ```no_run
11//! use rp235x_hal::{
12//!     gpio::Pins,
13//!     multicore::{Multicore, Stack},
14//!     pac,
15//!     sio::Sio,
16//! };
17//!
18//! static mut CORE1_STACK: Stack<4096> = Stack::new();
19//!
20//! fn core1_task() {
21//!     loop {}
22//! }
23//!
24//! fn main() -> ! {
25//!     let mut pac = hal::pac::Peripherals::take().unwrap();
26//!     let mut sio = Sio::new(pac.SIO);
27//!     // Other init code above this line
28//!     let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo);
29//!     let cores = mc.cores();
30//!     let core1 = &mut cores[1];
31//!     let _test = core1.spawn(CORE1_STACK.take().unwrap(), core1_task);
32//!     // The rest of your application below this line
33//!     # loop {}
34//! }
35//!
36//! ```
37//!
38//! For inter-processor communications, see [`crate::sio::SioFifo`] and [`crate::sio::Spinlock0`]
39//!
40//! For a detailed example, see [examples/multicore_fifo_blink.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs)
41
42use core::cell::Cell;
43use core::cell::UnsafeCell;
44use core::mem::ManuallyDrop;
45use core::ops::Range;
46use core::sync::atomic::compiler_fence;
47use core::sync::atomic::Ordering;
48
49use crate::pac;
50use crate::Sio;
51
52/// Errors for multicore operations.
53#[derive(Debug)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55pub enum Error {
56    /// Operation is invalid on this core.
57    InvalidCore,
58    /// Core was unresponsive to commands.
59    Unresponsive,
60}
61
62#[cfg(all(target_arch = "arm", target_os = "none"))]
63fn install_stack_guard(stack_limit: *mut usize) {
64    // Cortex-M33 has dedicated stack checking functionality via msplim
65    unsafe {
66        cortex_m::register::msplim::write(stack_limit as u32);
67    }
68}
69
70#[cfg(not(all(target_arch = "arm", target_os = "none")))]
71#[inline(always)]
72fn install_stack_guard(_stack_limit: *mut usize) {
73    // TBD riscv stack guard
74}
75
76#[inline(always)]
77fn core1_setup(stack_limit: *mut usize) {
78    install_stack_guard(stack_limit);
79    #[cfg(all(target_arch = "arm", target_os = "none"))]
80    unsafe {
81        crate::arch::enable_coprocessors()
82    };
83    // TODO: irq priorities
84}
85
86/// Set the EXTEXCLALL bit in ACTLR.
87///
88/// The default MPU memory map marks all memory as non-shareable, so atomics don't
89/// synchronize memory accesses between cores at all. This bit forces all memory to be
90/// considered shareable regardless of what the MPU says.
91///
92/// TODO: does this interfere somehow if the user wants to use a custom MPU configuration?
93/// maybe we need to add a way to disable this?
94///
95/// This must be done FOR EACH CORE.
96///
97/// (Copied from https://github.com/embassy-rs/embassy/blob/9da04cc38ea5cc17740bd9921f9f5cbb1c689a31/embassy-rp/src/lib.rs)
98fn enable_actlr_extexclall() {
99    unsafe {
100        (*cortex_m::peripheral::ICB::PTR)
101            .actlr
102            .modify(|w| w | (1 << 29));
103    }
104}
105
106/// Multicore execution management.
107pub struct Multicore<'p> {
108    cores: [Core<'p>; 2],
109}
110
111/// Data type for a properly aligned stack of N 32-bit (usize) words
112#[repr(C, align(32))]
113pub struct Stack<const SIZE: usize> {
114    /// Memory to be used for the stack
115    mem: UnsafeCell<[usize; SIZE]>,
116    taken: Cell<bool>,
117}
118
119impl<const SIZE: usize> Default for Stack<SIZE> {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125// Safety: Only one thread can `take` access to contents of the
126// struct, guarded by a critical section.
127unsafe impl<const SIZE: usize> Sync for Stack<SIZE> {}
128
129impl<const SIZE: usize> Stack<SIZE> {
130    /// Construct a stack of length SIZE, initialized to 0
131    ///
132    /// The minimum allowed SIZE is 64 bytes, but most programs
133    /// will need a significantly larger stack.
134    pub const fn new() -> Stack<SIZE> {
135        const { assert!(SIZE >= 64, "Stack too small") };
136        Stack {
137            mem: UnsafeCell::new([0; SIZE]),
138            taken: Cell::new(false),
139        }
140    }
141
142    /// Take the StackAllocation out of this Stack.
143    ///
144    /// This returns None if the stack is already taken.
145    pub fn take(&self) -> Option<StackAllocation> {
146        let taken = critical_section::with(|_| self.taken.replace(true));
147        if taken {
148            None
149        } else {
150            // Safety: We know the size of this allocation
151            unsafe {
152                let start = self.mem.get() as *mut usize;
153                let end = start.add(SIZE);
154                Some(StackAllocation::from_raw_parts(start, end))
155            }
156        }
157    }
158
159    /// Reset the taken flag of the stack area
160    ///
161    /// # Safety
162    ///
163    /// The caller must ensure that the stack is no longer in use, eg. because
164    /// the core that used it was reset. This method doesn't do any synchronization
165    /// so it must not be called from multiple threads concurrently.
166    pub unsafe fn reset(&self) {
167        self.taken.replace(false);
168    }
169}
170
171/// This object represents a memory area which can be used for a stack.
172///
173/// It is essentially a range of pointers with these additional guarantees:
174/// The begin / end pointers must define a stack
175/// with proper alignment (at least 8 bytes, preferably 32 bytes)
176/// and sufficient size (64 bytes would be sound but much too little for
177/// most real-world workloads). The underlying memory must
178/// have a static lifetime and must be owned by the object exclusively.
179/// No mutable references to that memory must exist.
180/// Therefore, a function that gets passed such an object is free to write
181/// to arbitrary memory locations in the range.
182#[derive(Debug)]
183#[cfg_attr(feature = "defmt", derive(defmt::Format))]
184pub struct StackAllocation {
185    /// Start and end pointer of the StackAllocation as a Range
186    mem: Range<*mut usize>,
187}
188
189impl StackAllocation {
190    fn get(&self) -> Range<*mut usize> {
191        self.mem.clone()
192    }
193
194    /// Unsafely construct a stack allocation
195    ///
196    /// This is mainly useful to construct a stack allocation in some memory region
197    /// defined in a linker script, for example to place the stack in the SRAM8/9 regions.
198    ///
199    /// # Safety
200    ///
201    /// The caller must ensure that the guarantees that a StackAllocation provides
202    /// are upheld.
203    pub unsafe fn from_raw_parts(start: *mut usize, end: *mut usize) -> Self {
204        StackAllocation { mem: start..end }
205    }
206}
207
208impl<const SIZE: usize> From<&Stack<SIZE>> for Option<StackAllocation> {
209    fn from(stack: &Stack<SIZE>) -> Self {
210        let taken = critical_section::with(|_| stack.taken.replace(true));
211        if taken {
212            None
213        } else {
214            // Safety: We know the size of this allocation
215            unsafe {
216                let start = stack.mem.get() as *mut usize;
217                let end = start.add(SIZE);
218                Some(StackAllocation::from_raw_parts(start, end))
219            }
220        }
221    }
222}
223
224impl<'p> Multicore<'p> {
225    /// Create a new |Multicore| instance.
226    pub fn new(
227        psm: &'p mut pac::PSM,
228        ppb: &'p mut pac::PPB,
229        sio: &'p mut crate::sio::SioFifo,
230    ) -> Self {
231        Self {
232            cores: [
233                Core { inner: None },
234                Core {
235                    inner: Some((psm, ppb, sio)),
236                },
237            ],
238        }
239    }
240
241    /// Get the available |Core| instances.
242    pub fn cores(&mut self) -> &mut [Core<'p>] {
243        &mut self.cores
244    }
245}
246
247/// A handle for controlling a logical core.
248pub struct Core<'p> {
249    inner: Option<(
250        &'p mut pac::PSM,
251        &'p mut pac::PPB,
252        &'p mut crate::sio::SioFifo,
253    )>,
254}
255
256impl Core<'_> {
257    /// Get the id of this core.
258    pub fn id(&self) -> u8 {
259        match self.inner {
260            None => 0,
261            Some(..) => 1,
262        }
263    }
264
265    /// Spawn a function on this core.
266    ///
267    /// The closure should not return. It is currently defined as `-> ()` because `-> !` is not yet
268    /// stable.
269    ///
270    /// Core 1 will be reset from core 0 in order to spawn another task.
271    ///
272    /// Resetting a single core of a running program can have undesired consequences. Deadlocks are
273    /// likely if the core being reset happens to be inside a critical section.
274    /// It may even break safety assumptions of some unsafe code. So, be careful when calling this method
275    /// more than once.
276    pub fn spawn<F>(&mut self, stack: StackAllocation, entry: F) -> Result<(), Error>
277    where
278        F: FnOnce() + Send + 'static,
279    {
280        // Needs to be enabled on both cores to make atomics work between cores.
281        enable_actlr_extexclall();
282        if let Some((psm, ppb, fifo)) = self.inner.as_mut() {
283            // The first two ignored `u64` parameters are there to take up all of the registers,
284            // which means that the rest of the arguments are taken from the stack,
285            // where we're able to put them from core 0.
286            extern "C" fn core1_startup<F: FnOnce()>(
287                _: u64,
288                _: u64,
289                entry: *mut ManuallyDrop<F>,
290                stack_limit: *mut usize,
291            ) -> ! {
292                // Needs to be enabled on both cores to make atomics work between cores.
293                enable_actlr_extexclall();
294                core1_setup(stack_limit);
295
296                let entry = unsafe { ManuallyDrop::take(&mut *entry) };
297
298                // make sure the preceding read doesn't get reordered past the following fifo write
299                compiler_fence(Ordering::SeqCst);
300
301                // Signal that it's safe for core 0 to get rid of the original value now.
302                //
303                // We don't have any way to get at core 1's SIO without using `Peripherals::steal` right now,
304                // since svd2rust doesn't really support multiple cores properly.
305                let peripherals = unsafe { pac::Peripherals::steal() };
306                let mut sio = Sio::new(peripherals.SIO);
307                sio.fifo.write_blocking(1);
308
309                entry();
310                loop {
311                    crate::arch::wfe()
312                }
313            }
314
315            // Reset the core
316            // TODO: resetting without prior check that the core is actually stowed is not great.
317            // But there does not seem to be any obvious way to check that. A marker flag could be
318            // set from this method and cleared for the wrapper after `entry` returned. But doing
319            // so wouldn't be zero cost.
320            psm.frce_off().modify(|_, w| w.proc1().set_bit());
321            while !psm.frce_off().read().proc1().bit_is_set() {
322                crate::arch::nop();
323            }
324            psm.frce_off().modify(|_, w| w.proc1().clear_bit());
325
326            // Set up the stack
327            // AAPCS requires in 6.2.1.2 that the stack is 8bytes aligned., we may need to trim the
328            // array size to guaranty that the base of the stack (the end of the array) meets that requirement.
329            // The start of the array does not need to be aligned.
330
331            let stack = stack.get();
332            let mut stack_ptr = stack.end;
333            // on rp235x, usize are 4 bytes, so align_offset(8) on a *mut usize returns either 0 or 1.
334            let misalignment_offset = stack_ptr.align_offset(8);
335
336            // We don't want to drop this, since it's getting moved to the other core.
337            let mut entry = ManuallyDrop::new(entry);
338
339            // Push the arguments to `core1_startup` onto the stack.
340            unsafe {
341                stack_ptr = stack_ptr.sub(misalignment_offset);
342
343                // Push `stack_limit`.
344                stack_ptr = stack_ptr.sub(1);
345                stack_ptr.cast::<*mut usize>().write(stack.start);
346
347                // Push `entry`.
348                stack_ptr = stack_ptr.sub(1);
349                stack_ptr.cast::<*mut ManuallyDrop<F>>().write(&mut entry);
350            }
351
352            // Make sure the compiler does not reorder the stack writes after to after the
353            // below FIFO writes, which would result in them not being seen by the second
354            // core.
355            //
356            // From the compiler perspective, this doesn't guarantee that the second core
357            // actually sees those writes. However, we know that the rp235x doesn't have
358            // memory caches, and writes happen in-order.
359            compiler_fence(Ordering::Release);
360
361            let vector_table = ppb.vtor().read().bits();
362
363            // After reset, core 1 is waiting to receive commands over FIFO.
364            // This is the sequence to have it jump to some code.
365            let cmd_seq = [
366                0,
367                0,
368                1,
369                vector_table as usize,
370                stack_ptr as usize,
371                core1_startup::<F> as *const () as usize,
372            ];
373
374            let mut seq = 0;
375            let mut fails = 0;
376            loop {
377                let cmd = cmd_seq[seq] as u32;
378                if cmd == 0 {
379                    fifo.drain();
380                    crate::arch::sev();
381                }
382                fifo.write_blocking(cmd);
383                let response = fifo.read_blocking();
384                if cmd == response {
385                    seq += 1;
386                } else {
387                    seq = 0;
388                    fails += 1;
389                    if fails > 16 {
390                        // The second core isn't responding, and isn't going to take the entrypoint,
391                        // so we have to drop it ourselves.
392                        drop(ManuallyDrop::into_inner(entry));
393                        return Err(Error::Unresponsive);
394                    }
395                }
396                if seq >= cmd_seq.len() {
397                    break;
398                }
399            }
400
401            // Wait until the other core has copied `entry` before returning.
402            fifo.read_blocking();
403
404            Ok(())
405        } else {
406            Err(Error::InvalidCore)
407        }
408    }
409}