aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-12-13 14:55:34 +0000
committerGitHub <[email protected]>2022-12-13 14:55:34 +0000
commit47747d3b73f392e53ead8ff49cd09fd017df3215 (patch)
tree9fb66fa2bb865d224ba2d98c5f1fa3bdf9e34b26
parent36639e5262a79df6a3c5acf5b083d51943ca5f97 (diff)
parentc4d8f3579e4927446421730d52a409d724f2800c (diff)
Merge #1105
1105: embassy-rp: Add multicore support r=Dirbaio a=kalkyl This PR adds multicore support + critical-section impl using hardware spinlocks. Based on the rp2040-hal implementation. Co-authored-by: kalkyl <[email protected]> Co-authored-by: Henrik Alsér <[email protected]>
-rw-r--r--embassy-rp/Cargo.toml3
-rw-r--r--embassy-rp/src/critical_section_impl.rs142
-rw-r--r--embassy-rp/src/flash.rs29
-rw-r--r--embassy-rp/src/lib.rs6
-rw-r--r--embassy-rp/src/multicore.rs306
-rw-r--r--examples/rp/Cargo.toml5
-rw-r--r--examples/rp/src/bin/multicore.rs60
-rw-r--r--tests/rp/Cargo.toml4
-rw-r--r--tests/rp/src/bin/multicore.rs47
9 files changed, 590 insertions, 12 deletions
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml
index 751875710..6d5bc91ee 100644
--- a/embassy-rp/Cargo.toml
+++ b/embassy-rp/Cargo.toml
@@ -15,6 +15,9 @@ flavors = [
15[features] 15[features]
16defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"] 16defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"]
17 17
18# critical section that is safe for multicore use
19critical-section-impl = ["critical-section/restore-state-u8"]
20
18# Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. 21# Reexport the PAC for the currently enabled chip at `embassy_rp::pac`.
19# This is unstable because semver-minor (non-breaking) releases of embassy-rp may major-bump (breaking) the PAC version. 22# This is unstable because semver-minor (non-breaking) releases of embassy-rp may major-bump (breaking) the PAC version.
20# If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. 23# If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC.
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/flash.rs b/embassy-rp/src/flash.rs
index a972d5f69..f2137ebe1 100644
--- a/embassy-rp/src/flash.rs
+++ b/embassy-rp/src/flash.rs
@@ -6,6 +6,7 @@ use embedded_storage::nor_flash::{
6 ReadNorFlash, 6 ReadNorFlash,
7}; 7};
8 8
9use crate::pac;
9use crate::peripherals::FLASH; 10use crate::peripherals::FLASH;
10 11
11pub const FLASH_BASE: usize = 0x10000000; 12pub const FLASH_BASE: usize = 0x10000000;
@@ -28,6 +29,7 @@ pub enum Error {
28 OutOfBounds, 29 OutOfBounds,
29 /// Unaligned operation or using unaligned buffers. 30 /// Unaligned operation or using unaligned buffers.
30 Unaligned, 31 Unaligned,
32 InvalidCore,
31 Other, 33 Other,
32} 34}
33 35
@@ -46,7 +48,7 @@ impl NorFlashError for Error {
46 match self { 48 match self {
47 Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, 49 Self::OutOfBounds => NorFlashErrorKind::OutOfBounds,
48 Self::Unaligned => NorFlashErrorKind::NotAligned, 50 Self::Unaligned => NorFlashErrorKind::NotAligned,
49 Self::Other => NorFlashErrorKind::Other, 51 _ => NorFlashErrorKind::Other,
50 } 52 }
51 } 53 }
52} 54}
@@ -87,7 +89,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
87 89
88 let len = to - from; 90 let len = to - from;
89 91
90 unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len, true)) }; 92 unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len, true))? };
91 93
92 Ok(()) 94 Ok(())
93 } 95 }
@@ -112,7 +114,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
112 114
113 let unaligned_offset = offset as usize - start; 115 let unaligned_offset = offset as usize - start;
114 116
115 unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true)) } 117 unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true))? }
116 } 118 }
117 119
118 let remaining_len = bytes.len() - start_padding; 120 let remaining_len = bytes.len() - start_padding;
@@ -130,12 +132,12 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
130 if bytes.as_ptr() as usize >= 0x2000_0000 { 132 if bytes.as_ptr() as usize >= 0x2000_0000 {
131 let aligned_data = &bytes[start_padding..end_padding]; 133 let aligned_data = &bytes[start_padding..end_padding];
132 134
133 unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data, true)) } 135 unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data, true))? }
134 } else { 136 } else {
135 for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { 137 for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) {
136 let mut ram_buf = [0xFF_u8; PAGE_SIZE]; 138 let mut ram_buf = [0xFF_u8; PAGE_SIZE];
137 ram_buf.copy_from_slice(chunk); 139 ram_buf.copy_from_slice(chunk);
138 unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf, true)) } 140 unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf, true))? }
139 aligned_offset += PAGE_SIZE; 141 aligned_offset += PAGE_SIZE;
140 } 142 }
141 } 143 }
@@ -150,7 +152,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
150 152
151 let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); 153 let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset);
152 154
153 unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true)) } 155 unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true))? }
154 } 156 }
155 157
156 Ok(()) 158 Ok(())
@@ -159,10 +161,17 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
159 /// Make sure to uphold the contract points with rp2040-flash. 161 /// Make sure to uphold the contract points with rp2040-flash.
160 /// - interrupts must be disabled 162 /// - interrupts must be disabled
161 /// - DMA must not access flash memory 163 /// - DMA must not access flash memory
162 unsafe fn in_ram(&mut self, operation: impl FnOnce()) { 164 unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> {
163 let dma_status = &mut [false; crate::dma::CHANNEL_COUNT]; 165 let dma_status = &mut [false; crate::dma::CHANNEL_COUNT];
164 166
165 // TODO: Make sure CORE1 is paused during the entire duration of the RAM function 167 // Make sure we're running on CORE0
168 let core_id: u32 = unsafe { pac::SIO.cpuid().read() };
169 if core_id != 0 {
170 return Err(Error::InvalidCore);
171 }
172
173 // Make sure CORE1 is paused during the entire duration of the RAM function
174 crate::multicore::pause_core1();
166 175
167 critical_section::with(|_| { 176 critical_section::with(|_| {
168 // Pause all DMA channels for the duration of the ram operation 177 // Pause all DMA channels for the duration of the ram operation
@@ -185,6 +194,10 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
185 } 194 }
186 } 195 }
187 }); 196 });
197
198 // Resume CORE1 execution
199 crate::multicore::resume_core1();
200 Ok(())
188 } 201 }
189} 202}
190 203
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs
index 551392725..0ea97aecf 100644
--- a/embassy-rp/src/lib.rs
+++ b/embassy-rp/src/lib.rs
@@ -5,6 +5,9 @@
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
8#[cfg(feature = "critical-section-impl")]
9mod critical_section_impl;
10
8mod intrinsics; 11mod intrinsics;
9 12
10pub mod adc; 13pub mod adc;
@@ -31,6 +34,7 @@ pub mod usb;
31 34
32pub mod clocks; 35pub mod clocks;
33pub mod flash; 36pub mod flash;
37pub mod multicore;
34mod reset; 38mod reset;
35 39
36// Reexports 40// Reexports
@@ -111,6 +115,8 @@ embassy_hal_common::peripherals! {
111 115
112 ADC, 116 ADC,
113 117
118 CORE1,
119
114 PIO0, 120 PIO0,
115 PIO1, 121 PIO1,
116} 122}
diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs
new file mode 100644
index 000000000..8290c62a7
--- /dev/null
+++ b/embassy-rp/src/multicore.rs
@@ -0,0 +1,306 @@
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//!
8//! Enable the `critical-section-impl` feature in embassy-rp when sharing data across cores using
9//! the `embassy-sync` primitives and `CriticalSectionRawMutex`.
10//!
11//! # Usage
12//! ```no_run
13//! static mut CORE1_STACK: Stack<4096> = Stack::new();
14//! static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
15//! static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
16//!
17//! #[cortex_m_rt::entry]
18//! fn main() -> ! {
19//! let p = embassy_rp::init(Default::default());
20//!
21//! spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
22//! let executor1 = EXECUTOR1.init(Executor::new());
23//! executor1.run(|spawner| unwrap!(spawner.spawn(core1_task())));
24//! });
25//!
26//! let executor0 = EXECUTOR0.init(Executor::new());
27//! executor0.run(|spawner| unwrap!(spawner.spawn(core0_task())));
28//! }
29//! ```
30
31use core::mem::ManuallyDrop;
32use core::sync::atomic::{compiler_fence, Ordering};
33
34use atomic_polyfill::AtomicBool;
35
36use crate::interrupt::{Interrupt, InterruptExt};
37use crate::peripherals::CORE1;
38use crate::{interrupt, pac};
39
40const PAUSE_TOKEN: u32 = 0xDEADBEEF;
41const RESUME_TOKEN: u32 = !0xDEADBEEF;
42static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false);
43
44#[inline(always)]
45fn install_stack_guard(stack_bottom: *mut usize) {
46 let core = unsafe { cortex_m::Peripherals::steal() };
47
48 // Trap if MPU is already configured
49 if core.MPU.ctrl.read() != 0 {
50 cortex_m::asm::udf();
51 }
52
53 // The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will
54 // just shorten the valid stack range a tad.
55 let addr = (stack_bottom as u32 + 31) & !31;
56 // Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want
57 let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7));
58 unsafe {
59 core.MPU.ctrl.write(5); // enable mpu with background default map
60 core.MPU.rbar.write((addr & !0xff) | 0x8);
61 core.MPU.rasr.write(
62 1 // enable region
63 | (0x7 << 1) // size 2^(7 + 1) = 256
64 | (subregion_select << 8)
65 | 0x10000000, // XN = disable instruction fetch; no other bits means no permissions
66 );
67 }
68}
69
70#[inline(always)]
71fn core1_setup(stack_bottom: *mut usize) {
72 install_stack_guard(stack_bottom);
73}
74
75/// Data type for a properly aligned stack of N bytes
76#[repr(C, align(32))]
77pub struct Stack<const SIZE: usize> {
78 /// Memory to be used for the stack
79 pub mem: [u8; SIZE],
80}
81
82impl<const SIZE: usize> Stack<SIZE> {
83 /// Construct a stack of length SIZE, initialized to 0
84 pub const fn new() -> Stack<SIZE> {
85 Stack { mem: [0_u8; SIZE] }
86 }
87}
88
89#[interrupt]
90#[link_section = ".data.ram_func"]
91unsafe fn SIO_IRQ_PROC1() {
92 let sio = pac::SIO;
93 // Clear IRQ
94 sio.fifo().st().write(|w| w.set_wof(false));
95
96 while sio.fifo().st().read().vld() {
97 // Pause CORE1 execution and disable interrupts
98 if fifo_read_wfe() == PAUSE_TOKEN {
99 cortex_m::interrupt::disable();
100 // Signal to CORE0 that execution is paused
101 fifo_write(PAUSE_TOKEN);
102 // Wait for `resume` signal from CORE0
103 while fifo_read_wfe() != RESUME_TOKEN {
104 cortex_m::asm::nop();
105 }
106 cortex_m::interrupt::enable();
107 // Signal to CORE0 that execution is resumed
108 fifo_write(RESUME_TOKEN);
109 }
110 }
111}
112
113/// Spawn a function on this core
114pub fn spawn_core1<F, const SIZE: usize>(_core1: CORE1, stack: &'static mut Stack<SIZE>, entry: F)
115where
116 F: FnOnce() -> bad::Never + Send + 'static,
117{
118 // The first two ignored `u64` parameters are there to take up all of the registers,
119 // which means that the rest of the arguments are taken from the stack,
120 // where we're able to put them from core 0.
121 extern "C" fn core1_startup<F: FnOnce() -> bad::Never>(
122 _: u64,
123 _: u64,
124 entry: &mut ManuallyDrop<F>,
125 stack_bottom: *mut usize,
126 ) -> ! {
127 core1_setup(stack_bottom);
128 let entry = unsafe { ManuallyDrop::take(entry) };
129 // Signal that it's safe for core 0 to get rid of the original value now.
130 fifo_write(1);
131
132 IS_CORE1_INIT.store(true, Ordering::Release);
133 // Enable fifo interrupt on CORE1 for `pause` functionality.
134 let irq = unsafe { interrupt::SIO_IRQ_PROC1::steal() };
135 irq.enable();
136
137 entry()
138 }
139
140 // Reset the core
141 unsafe {
142 let psm = pac::PSM;
143 psm.frce_off().modify(|w| w.set_proc1(true));
144 while !psm.frce_off().read().proc1() {
145 cortex_m::asm::nop();
146 }
147 psm.frce_off().modify(|w| w.set_proc1(false));
148 }
149
150 let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack.mem.len() / 4) };
151
152 // Set up the stack
153 let mut stack_ptr = unsafe { mem.as_mut_ptr().add(mem.len()) };
154
155 // We don't want to drop this, since it's getting moved to the other core.
156 let mut entry = ManuallyDrop::new(entry);
157
158 // Push the arguments to `core1_startup` onto the stack.
159 unsafe {
160 // Push `stack_bottom`.
161 stack_ptr = stack_ptr.sub(1);
162 stack_ptr.cast::<*mut usize>().write(mem.as_mut_ptr());
163
164 // Push `entry`.
165 stack_ptr = stack_ptr.sub(1);
166 stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry);
167 }
168
169 // Make sure the compiler does not reorder the stack writes after to after the
170 // below FIFO writes, which would result in them not being seen by the second
171 // core.
172 //
173 // From the compiler perspective, this doesn't guarantee that the second core
174 // actually sees those writes. However, we know that the RP2040 doesn't have
175 // memory caches, and writes happen in-order.
176 compiler_fence(Ordering::Release);
177
178 let p = unsafe { cortex_m::Peripherals::steal() };
179 let vector_table = p.SCB.vtor.read();
180
181 // After reset, core 1 is waiting to receive commands over FIFO.
182 // This is the sequence to have it jump to some code.
183 let cmd_seq = [
184 0,
185 0,
186 1,
187 vector_table as usize,
188 stack_ptr as usize,
189 core1_startup::<F> as usize,
190 ];
191
192 let mut seq = 0;
193 let mut fails = 0;
194 loop {
195 let cmd = cmd_seq[seq] as u32;
196 if cmd == 0 {
197 fifo_drain();
198 cortex_m::asm::sev();
199 }
200 fifo_write(cmd);
201
202 let response = fifo_read();
203 if cmd == response {
204 seq += 1;
205 } else {
206 seq = 0;
207 fails += 1;
208 if fails > 16 {
209 // The second core isn't responding, and isn't going to take the entrypoint
210 panic!("CORE1 not responding");
211 }
212 }
213 if seq >= cmd_seq.len() {
214 break;
215 }
216 }
217
218 // Wait until the other core has copied `entry` before returning.
219 fifo_read();
220}
221
222/// Pause execution on CORE1.
223pub fn pause_core1() {
224 if IS_CORE1_INIT.load(Ordering::Acquire) {
225 fifo_write(PAUSE_TOKEN);
226 // Wait for CORE1 to signal it has paused execution.
227 while fifo_read() != PAUSE_TOKEN {}
228 }
229}
230
231/// Resume CORE1 execution.
232pub fn resume_core1() {
233 if IS_CORE1_INIT.load(Ordering::Acquire) {
234 fifo_write(RESUME_TOKEN);
235 // Wait for CORE1 to signal it has resumed execution.
236 while fifo_read() != RESUME_TOKEN {}
237 }
238}
239
240// Push a value to the inter-core FIFO, block until space is available
241#[inline(always)]
242fn fifo_write(value: u32) {
243 unsafe {
244 let sio = pac::SIO;
245 // Wait for the FIFO to have enough space
246 while !sio.fifo().st().read().rdy() {
247 cortex_m::asm::nop();
248 }
249 sio.fifo().wr().write_value(value);
250 }
251 // Fire off an event to the other core.
252 // This is required as the other core may be `wfe` (waiting for event)
253 cortex_m::asm::sev();
254}
255
256// Pop a value from inter-core FIFO, block until available
257#[inline(always)]
258fn fifo_read() -> u32 {
259 unsafe {
260 let sio = pac::SIO;
261 // Wait until FIFO has data
262 while !sio.fifo().st().read().vld() {
263 cortex_m::asm::nop();
264 }
265 sio.fifo().rd().read()
266 }
267}
268
269// Pop a value from inter-core FIFO, `wfe` until available
270#[inline(always)]
271fn fifo_read_wfe() -> u32 {
272 unsafe {
273 let sio = pac::SIO;
274 // Wait until FIFO has data
275 while !sio.fifo().st().read().vld() {
276 cortex_m::asm::wfe();
277 }
278 sio.fifo().rd().read()
279 }
280}
281
282// Drain inter-core FIFO
283#[inline(always)]
284fn fifo_drain() {
285 unsafe {
286 let sio = pac::SIO;
287 while sio.fifo().st().read().vld() {
288 let _ = sio.fifo().rd().read();
289 }
290 }
291}
292
293// https://github.com/nvzqz/bad-rs/blob/master/src/never.rs
294mod bad {
295 pub(crate) type Never = <F as HasOutput>::Output;
296
297 pub trait HasOutput {
298 type Output;
299 }
300
301 impl<O> HasOutput for fn() -> O {
302 type Output = O;
303 }
304
305 type F = fn() -> !;
306}
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml
index b07c471af..dfbc26426 100644
--- a/examples/rp/Cargo.toml
+++ b/examples/rp/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
9embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } 9embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] }
10embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } 10embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
11embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } 11embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
12embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } 12embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio", "critical-section-impl"] }
13embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } 13embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
14embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } 14embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
15embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } 15embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
@@ -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..376b2b61e
--- /dev/null
+++ b/examples/rp/src/bin/multicore.rs
@@ -0,0 +1,60 @@
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::multicore::{spawn_core1, Stack};
10use embassy_rp::peripherals::PIN_25;
11use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
12use embassy_sync::channel::Channel;
13use embassy_time::{Duration, Timer};
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 spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
32 let executor1 = EXECUTOR1.init(Executor::new());
33 executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led))));
34 });
35
36 let executor0 = EXECUTOR0.init(Executor::new());
37 executor0.run(|spawner| unwrap!(spawner.spawn(core0_task())));
38}
39
40#[embassy_executor::task]
41async fn core0_task() {
42 info!("Hello from core 0");
43 loop {
44 CHANNEL.send(LedState::On).await;
45 Timer::after(Duration::from_millis(100)).await;
46 CHANNEL.send(LedState::Off).await;
47 Timer::after(Duration::from_millis(400)).await;
48 }
49}
50
51#[embassy_executor::task]
52async fn core1_task(mut led: Output<'static, PIN_25>) {
53 info!("Hello from core 1");
54 loop {
55 match CHANNEL.recv().await {
56 LedState::On => led.set_high(),
57 LedState::Off => led.set_low(),
58 }
59 }
60}
diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml
index a07b479e4..572a9ce88 100644
--- a/tests/rp/Cargo.toml
+++ b/tests/rp/Cargo.toml
@@ -8,13 +8,13 @@ license = "MIT OR Apache-2.0"
8embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } 8embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] }
9embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } 9embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
10embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } 10embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] }
11embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver"] } 11embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl"] }
12embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } 12embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
13 13
14defmt = "0.3.0" 14defmt = "0.3.0"
15defmt-rtt = "0.4" 15defmt-rtt = "0.4"
16 16
17cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } 17cortex-m = { version = "0.7.6" }
18cortex-m-rt = "0.7.0" 18cortex-m-rt = "0.7.0"
19embedded-hal = "0.2.6" 19embedded-hal = "0.2.6"
20embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } 20embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" }
diff --git a/tests/rp/src/bin/multicore.rs b/tests/rp/src/bin/multicore.rs
new file mode 100644
index 000000000..da78e887a
--- /dev/null
+++ b/tests/rp/src/bin/multicore.rs
@@ -0,0 +1,47 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use defmt::{info, unwrap};
6use embassy_executor::Executor;
7use embassy_executor::_export::StaticCell;
8use embassy_rp::multicore::{spawn_core1, Stack};
9use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
10use embassy_sync::channel::Channel;
11use {defmt_rtt as _, panic_probe as _};
12
13static mut CORE1_STACK: Stack<1024> = Stack::new();
14static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
15static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
16static CHANNEL0: Channel<CriticalSectionRawMutex, bool, 1> = Channel::new();
17static CHANNEL1: Channel<CriticalSectionRawMutex, bool, 1> = Channel::new();
18
19#[cortex_m_rt::entry]
20fn main() -> ! {
21 let p = embassy_rp::init(Default::default());
22 spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
23 let executor1 = EXECUTOR1.init(Executor::new());
24 executor1.run(|spawner| unwrap!(spawner.spawn(core1_task())));
25 });
26 let executor0 = EXECUTOR0.init(Executor::new());
27 executor0.run(|spawner| unwrap!(spawner.spawn(core0_task())));
28}
29
30#[embassy_executor::task]
31async fn core0_task() {
32 info!("CORE0 is running");
33 let ping = true;
34 CHANNEL0.send(ping).await;
35 let pong = CHANNEL1.recv().await;
36 assert_eq!(ping, pong);
37
38 info!("Test OK");
39 cortex_m::asm::bkpt();
40}
41
42#[embassy_executor::task]
43async fn core1_task() {
44 info!("CORE1 is running");
45 let ping = CHANNEL0.recv().await;
46 CHANNEL1.send(ping).await;
47}