aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkalkyl <[email protected]>2022-12-10 08:26:35 +0100
committerkalkyl <[email protected]>2022-12-10 08:26:35 +0100
commit1ee58492fbc58b721dc5ed9037c6787af257cbeb (patch)
treece840a88cd6dec6b21005943a547f6418e9a1f1b
parent5d4f09156af094732edc5c01332af8b13db10e0f (diff)
embassy-rp: Add multicore support
-rw-r--r--embassy-rp/Cargo.toml2
-rw-r--r--embassy-rp/src/critical_section_impl.rs142
-rw-r--r--embassy-rp/src/lib.rs2
-rw-r--r--embassy-rp/src/multicore.rs266
-rw-r--r--examples/rp/Cargo.toml3
-rw-r--r--examples/rp/src/bin/multicore.rs62
6 files changed, 475 insertions, 2 deletions
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml
index 284d458c6..07cd1bc1b 100644
--- a/embassy-rp/Cargo.toml
+++ b/embassy-rp/Cargo.toml
@@ -50,7 +50,7 @@ nb = "1.0.0"
50cfg-if = "1.0.0" 50cfg-if = "1.0.0"
51cortex-m-rt = ">=0.6.15,<0.8" 51cortex-m-rt = ">=0.6.15,<0.8"
52cortex-m = "0.7.6" 52cortex-m = "0.7.6"
53critical-section = "1.1" 53critical-section = { version = "1.1", features = ["restore-state-u8"] }
54futures = { version = "0.3.17", default-features = false, features = ["async-await"] } 54futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
55chrono = { version = "0.4", default-features = false, optional = true } 55chrono = { version = "0.4", default-features = false, optional = true }
56embedded-io = { version = "0.4.0", features = ["async"], optional = true } 56embedded-io = { version = "0.4.0", features = ["async"], optional = true }
diff --git a/embassy-rp/src/critical_section_impl.rs b/embassy-rp/src/critical_section_impl.rs
new file mode 100644
index 000000000..ce284c856
--- /dev/null
+++ b/embassy-rp/src/critical_section_impl.rs
@@ -0,0 +1,142 @@
1use core::sync::atomic::{AtomicU8, Ordering};
2
3use crate::pac;
4
5struct RpSpinlockCs;
6critical_section::set_impl!(RpSpinlockCs);
7
8/// Marker value to indicate no-one has the lock.
9///
10/// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice
11const LOCK_UNOWNED: u8 = 0;
12
13/// Indicates which core owns the lock so that we can call critical_section recursively.
14///
15/// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock
16static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED);
17
18/// Marker value to indicate that we already owned the lock when we started the `critical_section`.
19///
20/// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership.
21/// `critical_section` provides a token for communicating between `acquire` and `release` so we use that.
22/// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively.
23/// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release`
24const LOCK_ALREADY_OWNED: u8 = 2;
25
26unsafe impl critical_section::Impl for RpSpinlockCs {
27 unsafe fn acquire() -> u8 {
28 RpSpinlockCs::acquire()
29 }
30
31 unsafe fn release(token: u8) {
32 RpSpinlockCs::release(token);
33 }
34}
35
36impl RpSpinlockCs {
37 unsafe fn acquire() -> u8 {
38 // Store the initial interrupt state and current core id in stack variables
39 let interrupts_active = cortex_m::register::primask::read().is_active();
40 // We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1.
41 let core = pac::SIO.cpuid().read() as u8 + 1;
42 // Do we already own the spinlock?
43 if LOCK_OWNER.load(Ordering::Acquire) == core {
44 // We already own the lock, so we must have called acquire within a critical_section.
45 // Return the magic inner-loop value so that we know not to re-enable interrupts in release()
46 LOCK_ALREADY_OWNED
47 } else {
48 // Spin until we get the lock
49 loop {
50 // Need to disable interrupts to ensure that we will not deadlock
51 // if an interrupt enters critical_section::Impl after we acquire the lock
52 cortex_m::interrupt::disable();
53 // Ensure the compiler doesn't re-order accesses and violate safety here
54 core::sync::atomic::compiler_fence(Ordering::SeqCst);
55 // Read the spinlock reserved for `critical_section`
56 if let Some(lock) = Spinlock31::try_claim() {
57 // We just acquired the lock.
58 // 1. Forget it, so we don't immediately unlock
59 core::mem::forget(lock);
60 // 2. Store which core we are so we can tell if we're called recursively
61 LOCK_OWNER.store(core, Ordering::Relaxed);
62 break;
63 }
64 // We didn't get the lock, enable interrupts if they were enabled before we started
65 if interrupts_active {
66 cortex_m::interrupt::enable();
67 }
68 }
69 // If we broke out of the loop we have just acquired the lock
70 // As the outermost loop, we want to return the interrupt status to restore later
71 interrupts_active as _
72 }
73 }
74
75 unsafe fn release(token: u8) {
76 // Did we already own the lock at the start of the `critical_section`?
77 if token != LOCK_ALREADY_OWNED {
78 // No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it.
79 // Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead
80 LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed);
81 // Ensure the compiler doesn't re-order accesses and violate safety here
82 core::sync::atomic::compiler_fence(Ordering::SeqCst);
83 // Release the spinlock to allow others to enter critical_section again
84 Spinlock31::release();
85 // Re-enable interrupts if they were enabled when we first called acquire()
86 // We only do this on the outermost `critical_section` to ensure interrupts stay disabled
87 // for the whole time that we have the lock
88 if token != 0 {
89 cortex_m::interrupt::enable();
90 }
91 }
92 }
93}
94
95pub struct Spinlock<const N: usize>(core::marker::PhantomData<()>)
96where
97 Spinlock<N>: SpinlockValid;
98
99impl<const N: usize> Spinlock<N>
100where
101 Spinlock<N>: SpinlockValid,
102{
103 /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is
104 /// already in use somewhere else.
105 pub fn try_claim() -> Option<Self> {
106 // Safety: We're only reading from this register
107 unsafe {
108 let lock = pac::SIO.spinlock(N).read();
109 if lock > 0 {
110 Some(Self(core::marker::PhantomData))
111 } else {
112 None
113 }
114 }
115 }
116
117 /// Clear a locked spin-lock.
118 ///
119 /// # Safety
120 ///
121 /// Only call this function if you hold the spin-lock.
122 pub unsafe fn release() {
123 unsafe {
124 // Write (any value): release the lock
125 pac::SIO.spinlock(N).write_value(1);
126 }
127 }
128}
129
130impl<const N: usize> Drop for Spinlock<N>
131where
132 Spinlock<N>: SpinlockValid,
133{
134 fn drop(&mut self) {
135 // This is safe because we own the object, and hence hold the lock.
136 unsafe { Self::release() }
137 }
138}
139
140pub(crate) type Spinlock31 = Spinlock<31>;
141pub trait SpinlockValid {}
142impl SpinlockValid for Spinlock<31> {}
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs
index d21b5f7b0..9a2fb7fc3 100644
--- a/embassy-rp/src/lib.rs
+++ b/embassy-rp/src/lib.rs
@@ -5,6 +5,7 @@
5// This mod MUST go first, so that the others see its macros. 5// This mod MUST go first, so that the others see its macros.
6pub(crate) mod fmt; 6pub(crate) mod fmt;
7 7
8mod critical_section_impl;
8mod intrinsics; 9mod intrinsics;
9 10
10pub mod adc; 11pub mod adc;
@@ -23,6 +24,7 @@ pub mod usb;
23 24
24pub mod clocks; 25pub mod clocks;
25pub mod flash; 26pub mod flash;
27pub mod multicore;
26mod reset; 28mod reset;
27 29
28// Reexports 30// Reexports
diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs
new file mode 100644
index 000000000..dd3b70a67
--- /dev/null
+++ b/embassy-rp/src/multicore.rs
@@ -0,0 +1,266 @@
1//! Multicore support
2//!
3//! This module handles setup of the 2nd cpu core on the rp2040, 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
8use core::mem::ManuallyDrop;
9use core::sync::atomic::{compiler_fence, Ordering};
10
11use crate::pac;
12
13/// Errors for multicore operations.
14#[derive(Debug)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub enum Error {
17 /// Operation is invalid on this core.
18 InvalidCore,
19 /// Core was unresponsive to commands.
20 Unresponsive,
21}
22
23/// Core ID
24#[derive(Debug)]
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26pub enum CoreId {
27 Core0,
28 Core1,
29}
30
31#[inline(always)]
32fn install_stack_guard(stack_bottom: *mut usize) {
33 let core = unsafe { cortex_m::Peripherals::steal() };
34
35 // Trap if MPU is already configured
36 if core.MPU.ctrl.read() != 0 {
37 cortex_m::asm::udf();
38 }
39
40 // The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will
41 // just shorten the valid stack range a tad.
42 let addr = (stack_bottom as u32 + 31) & !31;
43 // Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want
44 let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7));
45 unsafe {
46 core.MPU.ctrl.write(5); // enable mpu with background default map
47 core.MPU.rbar.write((addr & !0xff) | 0x8);
48 core.MPU.rasr.write(
49 1 // enable region
50 | (0x7 << 1) // size 2^(7 + 1) = 256
51 | (subregion_select << 8)
52 | 0x10000000, // XN = disable instruction fetch; no other bits means no permissions
53 );
54 }
55}
56
57#[inline(always)]
58fn core1_setup(stack_bottom: *mut usize) {
59 install_stack_guard(stack_bottom);
60 // TODO: irq priorities
61}
62
63/// Multicore execution management.
64pub struct Multicore {
65 cores: (Core, Core),
66}
67
68/// Data type for a properly aligned stack of N 32-bit (usize) words
69#[repr(C, align(32))]
70pub struct Stack<const SIZE: usize> {
71 /// Memory to be used for the stack
72 pub mem: [usize; SIZE],
73}
74
75impl<const SIZE: usize> Stack<SIZE> {
76 /// Construct a stack of length SIZE, initialized to 0
77 pub const fn new() -> Stack<SIZE> {
78 Stack { mem: [0; SIZE] }
79 }
80}
81
82impl Multicore {
83 /// Create a new |Multicore| instance.
84 pub fn new() -> Self {
85 Self {
86 cores: (Core { id: CoreId::Core0 }, Core { id: CoreId::Core1 }),
87 }
88 }
89
90 /// Get the available |Core| instances.
91 pub fn cores(&mut self) -> &mut (Core, Core) {
92 &mut self.cores
93 }
94}
95
96/// A handle for controlling a logical core.
97pub struct Core {
98 pub id: CoreId,
99}
100
101impl Core {
102 /// Spawn a function on this core.
103 pub fn spawn<F>(&mut self, stack: &'static mut [usize], entry: F) -> Result<(), Error>
104 where
105 F: FnOnce() -> bad::Never + Send + 'static,
106 {
107 fn fifo_write(value: u32) {
108 unsafe {
109 let sio = pac::SIO;
110 // Wait for the FIFO to have some space
111 while !sio.fifo().st().read().rdy() {
112 cortex_m::asm::nop();
113 }
114 // Signal that it's safe for core 0 to get rid of the original value now.
115 sio.fifo().wr().write_value(value);
116 }
117
118 // Fire off an event to the other core.
119 // This is required as the other core may be `wfe` (waiting for event)
120 cortex_m::asm::sev();
121 }
122
123 fn fifo_read() -> u32 {
124 unsafe {
125 let sio = pac::SIO;
126 // Keep trying until FIFO has data
127 loop {
128 if sio.fifo().st().read().vld() {
129 return sio.fifo().rd().read();
130 } else {
131 // We expect the sending core to `sev` on write.
132 cortex_m::asm::wfe();
133 }
134 }
135 }
136 }
137
138 fn fifo_drain() {
139 unsafe {
140 let sio = pac::SIO;
141 while sio.fifo().st().read().vld() {
142 let _ = sio.fifo().rd().read();
143 }
144 }
145 }
146
147 match self.id {
148 CoreId::Core1 => {
149 // The first two ignored `u64` parameters are there to take up all of the registers,
150 // which means that the rest of the arguments are taken from the stack,
151 // where we're able to put them from core 0.
152 extern "C" fn core1_startup<F: FnOnce() -> bad::Never>(
153 _: u64,
154 _: u64,
155 entry: &mut ManuallyDrop<F>,
156 stack_bottom: *mut usize,
157 ) -> ! {
158 core1_setup(stack_bottom);
159 let entry = unsafe { ManuallyDrop::take(entry) };
160 // Signal that it's safe for core 0 to get rid of the original value now.
161 fifo_write(1);
162 entry()
163 }
164
165 // Reset the core
166 unsafe {
167 let psm = pac::PSM;
168 psm.frce_off().modify(|w| w.set_proc1(true));
169 while !psm.frce_off().read().proc1() {
170 cortex_m::asm::nop();
171 }
172 psm.frce_off().modify(|w| w.set_proc1(false));
173 }
174
175 // Set up the stack
176 let mut stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len()) };
177
178 // We don't want to drop this, since it's getting moved to the other core.
179 let mut entry = ManuallyDrop::new(entry);
180
181 // Push the arguments to `core1_startup` onto the stack.
182 unsafe {
183 // Push `stack_bottom`.
184 stack_ptr = stack_ptr.sub(1);
185 stack_ptr.cast::<*mut usize>().write(stack.as_mut_ptr());
186
187 // Push `entry`.
188 stack_ptr = stack_ptr.sub(1);
189 stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry);
190 }
191
192 // Make sure the compiler does not reorder the stack writes after to after the
193 // below FIFO writes, which would result in them not being seen by the second
194 // core.
195 //
196 // From the compiler perspective, this doesn't guarantee that the second core
197 // actually sees those writes. However, we know that the RP2040 doesn't have
198 // memory caches, and writes happen in-order.
199 compiler_fence(Ordering::Release);
200
201 let p = unsafe { cortex_m::Peripherals::steal() };
202 let vector_table = p.SCB.vtor.read();
203
204 // After reset, core 1 is waiting to receive commands over FIFO.
205 // This is the sequence to have it jump to some code.
206 let cmd_seq = [
207 0,
208 0,
209 1,
210 vector_table as usize,
211 stack_ptr as usize,
212 core1_startup::<F> as usize,
213 ];
214
215 let mut seq = 0;
216 let mut fails = 0;
217 loop {
218 let cmd = cmd_seq[seq] as u32;
219 if cmd == 0 {
220 fifo_drain();
221 cortex_m::asm::sev();
222 }
223 fifo_write(cmd);
224
225 let response = fifo_read();
226 if cmd == response {
227 seq += 1;
228 } else {
229 seq = 0;
230 fails += 1;
231 if fails > 16 {
232 // The second core isn't responding, and isn't going to take the entrypoint,
233 // so we have to drop it ourselves.
234 drop(ManuallyDrop::into_inner(entry));
235 return Err(Error::Unresponsive);
236 }
237 }
238 if seq >= cmd_seq.len() {
239 break;
240 }
241 }
242
243 // Wait until the other core has copied `entry` before returning.
244 fifo_read();
245
246 Ok(())
247 }
248 _ => Err(Error::InvalidCore),
249 }
250 }
251}
252
253// https://github.com/nvzqz/bad-rs/blob/master/src/never.rs
254mod bad {
255 pub(crate) type Never = <F as HasOutput>::Output;
256
257 pub trait HasOutput {
258 type Output;
259 }
260
261 impl<O> HasOutput for fn() -> O {
262 type Output = O;
263 }
264
265 type F = fn() -> !;
266}
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml
index 60a8ba94d..bd624a329 100644
--- a/examples/rp/Cargo.toml
+++ b/examples/rp/Cargo.toml
@@ -18,7 +18,8 @@ embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" }
18defmt = "0.3" 18defmt = "0.3"
19defmt-rtt = "0.4" 19defmt-rtt = "0.4"
20 20
21cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } 21#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
22cortex-m = { version = "0.7.6" }
22cortex-m-rt = "0.7.0" 23cortex-m-rt = "0.7.0"
23panic-probe = { version = "0.3", features = ["print-defmt"] } 24panic-probe = { version = "0.3", features = ["print-defmt"] }
24futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } 25futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs
new file mode 100644
index 000000000..46d3cd17c
--- /dev/null
+++ b/examples/rp/src/bin/multicore.rs
@@ -0,0 +1,62 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use defmt::*;
6use embassy_executor::Executor;
7use embassy_executor::_export::StaticCell;
8use embassy_rp::gpio::{Level, Output};
9use embassy_rp::peripherals::PIN_25;
10use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
11use embassy_sync::channel::Channel;
12use embassy_time::{Duration, Timer};
13use embassy_rp::multicore::{Multicore, Stack};
14use {defmt_rtt as _, panic_probe as _};
15
16static mut CORE1_STACK: Stack<4096> = Stack::new();
17static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
18static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
19static CHANNEL: Channel<CriticalSectionRawMutex, LedState, 1> = Channel::new();
20
21enum LedState {
22 On,
23 Off,
24}
25
26#[cortex_m_rt::entry]
27fn main() -> ! {
28 let p = embassy_rp::init(Default::default());
29 let led = Output::new(p.PIN_25, Level::Low);
30
31 let mut mc = Multicore::new();
32 let (_, core1) = mc.cores();
33 let _ = core1.spawn(unsafe { &mut CORE1_STACK.mem }, move || {
34 let executor1 = EXECUTOR1.init(Executor::new());
35 executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led))));
36 });
37
38 let executor0 = EXECUTOR0.init(Executor::new());
39 executor0.run(|spawner| unwrap!(spawner.spawn(core0_task())));
40}
41
42#[embassy_executor::task]
43async fn core0_task() {
44 info!("Hello from core 0");
45 loop {
46 CHANNEL.send(LedState::On).await;
47 Timer::after(Duration::from_millis(100)).await;
48 CHANNEL.send(LedState::Off).await;
49 Timer::after(Duration::from_millis(400)).await;
50 }
51}
52
53#[embassy_executor::task]
54async fn core1_task(mut led: Output<'static, PIN_25>) {
55 info!("Hello from core 1");
56 loop {
57 match CHANNEL.recv().await {
58 LedState::On => led.set_high(),
59 LedState::Off => led.set_low(),
60 }
61 }
62} \ No newline at end of file