diff options
| -rw-r--r-- | embassy-rp/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy-rp/src/multicore.rs | 237 | ||||
| -rw-r--r-- | examples/rp/src/bin/multicore.rs | 5 | ||||
| -rw-r--r-- | tests/rp/src/bin/multicore.rs | 47 |
4 files changed, 152 insertions, 139 deletions
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 2cd99f456..c6442c56e 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -106,6 +106,8 @@ embassy_hal_common::peripherals! { | |||
| 106 | FLASH, | 106 | FLASH, |
| 107 | 107 | ||
| 108 | ADC, | 108 | ADC, |
| 109 | |||
| 110 | CORE1, | ||
| 109 | } | 111 | } |
| 110 | 112 | ||
| 111 | #[link_section = ".boot2"] | 113 | #[link_section = ".boot2"] |
diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 3703fe819..2dfa215b5 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs | |||
| @@ -36,20 +36,13 @@ use core::sync::atomic::{compiler_fence, Ordering}; | |||
| 36 | use atomic_polyfill::AtomicBool; | 36 | use atomic_polyfill::AtomicBool; |
| 37 | 37 | ||
| 38 | use crate::interrupt::{Interrupt, InterruptExt}; | 38 | use crate::interrupt::{Interrupt, InterruptExt}; |
| 39 | use crate::peripherals::CORE1; | ||
| 39 | use crate::{interrupt, pac}; | 40 | use crate::{interrupt, pac}; |
| 40 | 41 | ||
| 41 | const PAUSE_TOKEN: u32 = 0xDEADBEEF; | 42 | const PAUSE_TOKEN: u32 = 0xDEADBEEF; |
| 42 | const RESUME_TOKEN: u32 = !0xDEADBEEF; | 43 | const RESUME_TOKEN: u32 = !0xDEADBEEF; |
| 43 | static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); | 44 | static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); |
| 44 | 45 | ||
| 45 | /// Errors for multicore operations. | ||
| 46 | #[derive(Debug)] | ||
| 47 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 48 | pub enum Error { | ||
| 49 | /// Core was unresponsive to commands. | ||
| 50 | Unresponsive, | ||
| 51 | } | ||
| 52 | |||
| 53 | #[inline(always)] | 46 | #[inline(always)] |
| 54 | fn install_stack_guard(stack_bottom: *mut usize) { | 47 | fn install_stack_guard(stack_bottom: *mut usize) { |
| 55 | let core = unsafe { cortex_m::Peripherals::steal() }; | 48 | let core = unsafe { cortex_m::Peripherals::steal() }; |
| @@ -81,44 +74,20 @@ fn core1_setup(stack_bottom: *mut usize) { | |||
| 81 | install_stack_guard(stack_bottom); | 74 | install_stack_guard(stack_bottom); |
| 82 | } | 75 | } |
| 83 | 76 | ||
| 84 | /// MultiCore execution management. | 77 | /// Data type for a properly aligned stack of N bytes |
| 85 | pub struct MultiCore { | ||
| 86 | pub cores: (Core0, Core1), | ||
| 87 | } | ||
| 88 | |||
| 89 | /// Data type for a properly aligned stack of N 32-bit (usize) words | ||
| 90 | #[repr(C, align(32))] | 78 | #[repr(C, align(32))] |
| 91 | pub struct Stack<const SIZE: usize> { | 79 | pub struct Stack<const SIZE: usize> { |
| 92 | /// Memory to be used for the stack | 80 | /// Memory to be used for the stack |
| 93 | pub mem: [usize; SIZE], | 81 | pub mem: [u8; SIZE], |
| 94 | } | 82 | } |
| 95 | 83 | ||
| 96 | impl<const SIZE: usize> Stack<SIZE> { | 84 | impl<const SIZE: usize> Stack<SIZE> { |
| 97 | /// Construct a stack of length SIZE, initialized to 0 | 85 | /// Construct a stack of length SIZE, initialized to 0 |
| 98 | pub const fn new() -> Stack<SIZE> { | 86 | pub const fn new() -> Stack<SIZE> { |
| 99 | Stack { mem: [0; SIZE] } | 87 | Stack { mem: [0_u8; SIZE] } |
| 100 | } | 88 | } |
| 101 | } | 89 | } |
| 102 | 90 | ||
| 103 | impl MultiCore { | ||
| 104 | /// Create a new |MultiCore| instance. | ||
| 105 | pub fn new() -> Self { | ||
| 106 | Self { | ||
| 107 | cores: (Core0 {}, Core1 {}), | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Get the available |Core| instances. | ||
| 112 | pub fn cores(&mut self) -> &mut (Core0, Core1) { | ||
| 113 | &mut self.cores | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// A handle for controlling a logical core. | ||
| 118 | pub struct Core0 {} | ||
| 119 | /// A handle for controlling a logical core. | ||
| 120 | pub struct Core1 {} | ||
| 121 | |||
| 122 | #[interrupt] | 91 | #[interrupt] |
| 123 | #[link_section = ".data.ram_func"] | 92 | #[link_section = ".data.ram_func"] |
| 124 | unsafe fn SIO_IRQ_PROC1() { | 93 | unsafe fn SIO_IRQ_PROC1() { |
| @@ -143,117 +112,113 @@ unsafe fn SIO_IRQ_PROC1() { | |||
| 143 | } | 112 | } |
| 144 | } | 113 | } |
| 145 | 114 | ||
| 146 | impl Core1 { | 115 | /// Spawn a function on this core |
| 147 | /// Spawn a function on this core | 116 | pub fn spawn_core1<F, const SIZE: usize>(_core1: CORE1, stack: &'static mut Stack<SIZE>, entry: F) |
| 148 | pub fn spawn<F>(&mut self, stack: &'static mut [usize], entry: F) -> Result<(), Error> | 117 | where |
| 149 | where | 118 | F: FnOnce() -> bad::Never + Send + 'static, |
| 150 | F: FnOnce() -> bad::Never + Send + 'static, | 119 | { |
| 151 | { | 120 | // The first two ignored `u64` parameters are there to take up all of the registers, |
| 152 | // The first two ignored `u64` parameters are there to take up all of the registers, | 121 | // which means that the rest of the arguments are taken from the stack, |
| 153 | // which means that the rest of the arguments are taken from the stack, | 122 | // where we're able to put them from core 0. |
| 154 | // where we're able to put them from core 0. | 123 | extern "C" fn core1_startup<F: FnOnce() -> bad::Never>( |
| 155 | extern "C" fn core1_startup<F: FnOnce() -> bad::Never>( | 124 | _: u64, |
| 156 | _: u64, | 125 | _: u64, |
| 157 | _: u64, | 126 | entry: &mut ManuallyDrop<F>, |
| 158 | entry: &mut ManuallyDrop<F>, | 127 | stack_bottom: *mut usize, |
| 159 | stack_bottom: *mut usize, | 128 | ) -> ! { |
| 160 | ) -> ! { | 129 | core1_setup(stack_bottom); |
| 161 | core1_setup(stack_bottom); | 130 | let entry = unsafe { ManuallyDrop::take(entry) }; |
| 162 | let entry = unsafe { ManuallyDrop::take(entry) }; | 131 | // Signal that it's safe for core 0 to get rid of the original value now. |
| 163 | // Signal that it's safe for core 0 to get rid of the original value now. | 132 | fifo_write(1); |
| 164 | fifo_write(1); | 133 | |
| 165 | 134 | IS_CORE1_INIT.store(true, Ordering::Release); | |
| 166 | IS_CORE1_INIT.store(true, Ordering::Release); | 135 | // Enable fifo interrupt on CORE1 for `pause` functionality. |
| 167 | // Enable fifo interrupt on CORE1 for `pause` functionality. | 136 | let irq = unsafe { interrupt::SIO_IRQ_PROC1::steal() }; |
| 168 | let irq = unsafe { interrupt::SIO_IRQ_PROC1::steal() }; | 137 | irq.enable(); |
| 169 | irq.enable(); | 138 | |
| 170 | 139 | entry() | |
| 171 | entry() | 140 | } |
| 172 | } | ||
| 173 | 141 | ||
| 174 | // Reset the core | 142 | // Reset the core |
| 175 | unsafe { | 143 | unsafe { |
| 176 | let psm = pac::PSM; | 144 | let psm = pac::PSM; |
| 177 | psm.frce_off().modify(|w| w.set_proc1(true)); | 145 | psm.frce_off().modify(|w| w.set_proc1(true)); |
| 178 | while !psm.frce_off().read().proc1() { | 146 | while !psm.frce_off().read().proc1() { |
| 179 | cortex_m::asm::nop(); | 147 | cortex_m::asm::nop(); |
| 180 | } | ||
| 181 | psm.frce_off().modify(|w| w.set_proc1(false)); | ||
| 182 | } | 148 | } |
| 149 | psm.frce_off().modify(|w| w.set_proc1(false)); | ||
| 150 | } | ||
| 183 | 151 | ||
| 184 | // Set up the stack | 152 | let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack.mem.len() / 4) }; |
| 185 | let mut stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len()) }; | ||
| 186 | 153 | ||
| 187 | // We don't want to drop this, since it's getting moved to the other core. | 154 | // Set up the stack |
| 188 | let mut entry = ManuallyDrop::new(entry); | 155 | let mut stack_ptr = unsafe { mem.as_mut_ptr().add(mem.len()) }; |
| 189 | 156 | ||
| 190 | // Push the arguments to `core1_startup` onto the stack. | 157 | // We don't want to drop this, since it's getting moved to the other core. |
| 191 | unsafe { | 158 | let mut entry = ManuallyDrop::new(entry); |
| 192 | // Push `stack_bottom`. | ||
| 193 | stack_ptr = stack_ptr.sub(1); | ||
| 194 | stack_ptr.cast::<*mut usize>().write(stack.as_mut_ptr()); | ||
| 195 | 159 | ||
| 196 | // Push `entry`. | 160 | // Push the arguments to `core1_startup` onto the stack. |
| 197 | stack_ptr = stack_ptr.sub(1); | 161 | unsafe { |
| 198 | stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry); | 162 | // Push `stack_bottom`. |
| 199 | } | 163 | stack_ptr = stack_ptr.sub(1); |
| 164 | stack_ptr.cast::<*mut usize>().write(mem.as_mut_ptr()); | ||
| 200 | 165 | ||
| 201 | // Make sure the compiler does not reorder the stack writes after to after the | 166 | // Push `entry`. |
| 202 | // below FIFO writes, which would result in them not being seen by the second | 167 | stack_ptr = stack_ptr.sub(1); |
| 203 | // core. | 168 | stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry); |
| 204 | // | 169 | } |
| 205 | // From the compiler perspective, this doesn't guarantee that the second core | 170 | |
| 206 | // actually sees those writes. However, we know that the RP2040 doesn't have | 171 | // Make sure the compiler does not reorder the stack writes after to after the |
| 207 | // memory caches, and writes happen in-order. | 172 | // below FIFO writes, which would result in them not being seen by the second |
| 208 | compiler_fence(Ordering::Release); | 173 | // core. |
| 209 | 174 | // | |
| 210 | let p = unsafe { cortex_m::Peripherals::steal() }; | 175 | // From the compiler perspective, this doesn't guarantee that the second core |
| 211 | let vector_table = p.SCB.vtor.read(); | 176 | // actually sees those writes. However, we know that the RP2040 doesn't have |
| 212 | 177 | // memory caches, and writes happen in-order. | |
| 213 | // After reset, core 1 is waiting to receive commands over FIFO. | 178 | compiler_fence(Ordering::Release); |
| 214 | // This is the sequence to have it jump to some code. | 179 | |
| 215 | let cmd_seq = [ | 180 | let p = unsafe { cortex_m::Peripherals::steal() }; |
| 216 | 0, | 181 | let vector_table = p.SCB.vtor.read(); |
| 217 | 0, | 182 | |
| 218 | 1, | 183 | // After reset, core 1 is waiting to receive commands over FIFO. |
| 219 | vector_table as usize, | 184 | // This is the sequence to have it jump to some code. |
| 220 | stack_ptr as usize, | 185 | let cmd_seq = [ |
| 221 | core1_startup::<F> as usize, | 186 | 0, |
| 222 | ]; | 187 | 0, |
| 223 | 188 | 1, | |
| 224 | let mut seq = 0; | 189 | vector_table as usize, |
| 225 | let mut fails = 0; | 190 | stack_ptr as usize, |
| 226 | loop { | 191 | core1_startup::<F> as usize, |
| 227 | let cmd = cmd_seq[seq] as u32; | 192 | ]; |
| 228 | if cmd == 0 { | 193 | |
| 229 | fifo_drain(); | 194 | let mut seq = 0; |
| 230 | cortex_m::asm::sev(); | 195 | let mut fails = 0; |
| 231 | } | 196 | loop { |
| 232 | fifo_write(cmd); | 197 | let cmd = cmd_seq[seq] as u32; |
| 233 | 198 | if cmd == 0 { | |
| 234 | let response = fifo_read(); | 199 | fifo_drain(); |
| 235 | if cmd == response { | 200 | cortex_m::asm::sev(); |
| 236 | seq += 1; | 201 | } |
| 237 | } else { | 202 | fifo_write(cmd); |
| 238 | seq = 0; | 203 | |
| 239 | fails += 1; | 204 | let response = fifo_read(); |
| 240 | if fails > 16 { | 205 | if cmd == response { |
| 241 | // The second core isn't responding, and isn't going to take the entrypoint, | 206 | seq += 1; |
| 242 | // so we have to drop it ourselves. | 207 | } else { |
| 243 | drop(ManuallyDrop::into_inner(entry)); | 208 | seq = 0; |
| 244 | return Err(Error::Unresponsive); | 209 | fails += 1; |
| 245 | } | 210 | if fails > 16 { |
| 246 | } | 211 | // The second core isn't responding, and isn't going to take the entrypoint |
| 247 | if seq >= cmd_seq.len() { | 212 | panic!("CORE1 not responding"); |
| 248 | break; | ||
| 249 | } | 213 | } |
| 250 | } | 214 | } |
| 251 | 215 | if seq >= cmd_seq.len() { | |
| 252 | // Wait until the other core has copied `entry` before returning. | 216 | break; |
| 253 | fifo_read(); | 217 | } |
| 254 | |||
| 255 | Ok(()) | ||
| 256 | } | 218 | } |
| 219 | |||
| 220 | // Wait until the other core has copied `entry` before returning. | ||
| 221 | fifo_read(); | ||
| 257 | } | 222 | } |
| 258 | 223 | ||
| 259 | /// Pause execution on CORE1. | 224 | /// Pause execution on CORE1. |
diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs index 53941da60..376b2b61e 100644 --- a/examples/rp/src/bin/multicore.rs +++ b/examples/rp/src/bin/multicore.rs | |||
| @@ -6,7 +6,7 @@ use defmt::*; | |||
| 6 | use embassy_executor::Executor; | 6 | use embassy_executor::Executor; |
| 7 | use embassy_executor::_export::StaticCell; | 7 | use embassy_executor::_export::StaticCell; |
| 8 | use embassy_rp::gpio::{Level, Output}; | 8 | use embassy_rp::gpio::{Level, Output}; |
| 9 | use embassy_rp::multicore::{MultiCore, Stack}; | 9 | use embassy_rp::multicore::{spawn_core1, Stack}; |
| 10 | use embassy_rp::peripherals::PIN_25; | 10 | use embassy_rp::peripherals::PIN_25; |
| 11 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | 11 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; |
| 12 | use embassy_sync::channel::Channel; | 12 | use embassy_sync::channel::Channel; |
| @@ -28,8 +28,7 @@ fn main() -> ! { | |||
| 28 | let p = embassy_rp::init(Default::default()); | 28 | let p = embassy_rp::init(Default::default()); |
| 29 | let led = Output::new(p.PIN_25, Level::Low); | 29 | let led = Output::new(p.PIN_25, Level::Low); |
| 30 | 30 | ||
| 31 | let mut mc = MultiCore::new(); | 31 | spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { |
| 32 | let _ = mc.cores.1.spawn(unsafe { &mut CORE1_STACK.mem }, move || { | ||
| 33 | let executor1 = EXECUTOR1.init(Executor::new()); | 32 | let executor1 = EXECUTOR1.init(Executor::new()); |
| 34 | executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); | 33 | executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); |
| 35 | }); | 34 | }); |
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 | |||
| 5 | use defmt::{info, unwrap}; | ||
| 6 | use embassy_executor::Executor; | ||
| 7 | use embassy_executor::_export::StaticCell; | ||
| 8 | use embassy_rp::multicore::{spawn_core1, Stack}; | ||
| 9 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 10 | use embassy_sync::channel::Channel; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | static mut CORE1_STACK: Stack<1024> = Stack::new(); | ||
| 14 | static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); | ||
| 15 | static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); | ||
| 16 | static CHANNEL0: Channel<CriticalSectionRawMutex, bool, 1> = Channel::new(); | ||
| 17 | static CHANNEL1: Channel<CriticalSectionRawMutex, bool, 1> = Channel::new(); | ||
| 18 | |||
| 19 | #[cortex_m_rt::entry] | ||
| 20 | fn 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] | ||
| 31 | async 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] | ||
| 43 | async fn core1_task() { | ||
| 44 | info!("CORE1 is running"); | ||
| 45 | let ping = CHANNEL0.recv().await; | ||
| 46 | CHANNEL1.send(ping).await; | ||
| 47 | } | ||
