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}