diff options
| author | kalkyl <[email protected]> | 2022-12-13 04:02:28 +0100 |
|---|---|---|
| committer | kalkyl <[email protected]> | 2022-12-13 04:02:28 +0100 |
| commit | eb1d2e1295cfd2f0580355d69c93387898eaabd4 (patch) | |
| tree | e4eaee5da40fcb420d49d17c481fa985f2a6e8d3 /embassy-rp/src/multicore.rs | |
| parent | 96d6c7243b7b5f7f8c90dab666ded0ca0cf29c75 (diff) | |
Pause CORE1 execution during flash operations
Diffstat (limited to 'embassy-rp/src/multicore.rs')
| -rw-r--r-- | embassy-rp/src/multicore.rs | 333 |
1 files changed, 197 insertions, 136 deletions
diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index bf635db2d..09d40b2ef 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs | |||
| @@ -11,14 +11,19 @@ | |||
| 11 | use core::mem::ManuallyDrop; | 11 | use core::mem::ManuallyDrop; |
| 12 | use core::sync::atomic::{compiler_fence, Ordering}; | 12 | use core::sync::atomic::{compiler_fence, Ordering}; |
| 13 | 13 | ||
| 14 | use crate::pac; | 14 | use atomic_polyfill::AtomicBool; |
| 15 | |||
| 16 | use crate::interrupt::{Interrupt, InterruptExt}; | ||
| 17 | use crate::{interrupt, pac}; | ||
| 18 | |||
| 19 | const PAUSE_TOKEN: u32 = 0xDEADBEEF; | ||
| 20 | const RESUME_TOKEN: u32 = !0xDEADBEEF; | ||
| 21 | static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); | ||
| 15 | 22 | ||
| 16 | /// Errors for multicore operations. | 23 | /// Errors for multicore operations. |
| 17 | #[derive(Debug)] | 24 | #[derive(Debug)] |
| 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 25 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 19 | pub enum Error { | 26 | pub enum Error { |
| 20 | /// Operation is invalid on this core. | ||
| 21 | InvalidCore, | ||
| 22 | /// Core was unresponsive to commands. | 27 | /// Core was unresponsive to commands. |
| 23 | Unresponsive, | 28 | Unresponsive, |
| 24 | } | 29 | } |
| @@ -64,7 +69,7 @@ fn core1_setup(stack_bottom: *mut usize) { | |||
| 64 | 69 | ||
| 65 | /// MultiCore execution management. | 70 | /// MultiCore execution management. |
| 66 | pub struct MultiCore { | 71 | pub struct MultiCore { |
| 67 | pub cores: (Core, Core), | 72 | pub cores: (Core0, Core1), |
| 68 | } | 73 | } |
| 69 | 74 | ||
| 70 | /// Data type for a properly aligned stack of N 32-bit (usize) words | 75 | /// Data type for a properly aligned stack of N 32-bit (usize) words |
| @@ -85,169 +90,225 @@ impl MultiCore { | |||
| 85 | /// Create a new |MultiCore| instance. | 90 | /// Create a new |MultiCore| instance. |
| 86 | pub fn new() -> Self { | 91 | pub fn new() -> Self { |
| 87 | Self { | 92 | Self { |
| 88 | cores: (Core { id: CoreId::Core0 }, Core { id: CoreId::Core1 }), | 93 | cores: (Core0 {}, Core1 {}), |
| 89 | } | 94 | } |
| 90 | } | 95 | } |
| 91 | 96 | ||
| 92 | /// Get the available |Core| instances. | 97 | /// Get the available |Core| instances. |
| 93 | pub fn cores(&mut self) -> &mut (Core, Core) { | 98 | pub fn cores(&mut self) -> &mut (Core0, Core1) { |
| 94 | &mut self.cores | 99 | &mut self.cores |
| 95 | } | 100 | } |
| 96 | } | 101 | } |
| 97 | 102 | ||
| 98 | /// A handle for controlling a logical core. | 103 | /// A handle for controlling a logical core. |
| 99 | pub struct Core { | 104 | pub struct Core0 {} |
| 100 | pub id: CoreId, | 105 | /// A handle for controlling a logical core. |
| 106 | pub struct Core1 {} | ||
| 107 | |||
| 108 | #[interrupt] | ||
| 109 | #[link_section = ".data.ram_func"] | ||
| 110 | unsafe fn SIO_IRQ_PROC1() { | ||
| 111 | let sio = pac::SIO; | ||
| 112 | // Clear IRQ | ||
| 113 | sio.fifo().st().write(|w| w.set_wof(false)); | ||
| 114 | |||
| 115 | while sio.fifo().st().read().vld() { | ||
| 116 | // Pause CORE1 execution and disable interrupts | ||
| 117 | if fifo_read_wfe() == PAUSE_TOKEN { | ||
| 118 | cortex_m::interrupt::disable(); | ||
| 119 | // Signal to CORE0 that execution is paused | ||
| 120 | fifo_write(PAUSE_TOKEN); | ||
| 121 | // Wait for `resume` signal from CORE0 | ||
| 122 | while fifo_read_wfe() != RESUME_TOKEN { | ||
| 123 | cortex_m::asm::nop(); | ||
| 124 | } | ||
| 125 | cortex_m::interrupt::enable(); | ||
| 126 | // Signal to CORE0 that execution is resumed | ||
| 127 | fifo_write(RESUME_TOKEN); | ||
| 128 | } | ||
| 129 | } | ||
| 101 | } | 130 | } |
| 102 | 131 | ||
| 103 | impl Core { | 132 | impl Core1 { |
| 104 | /// Spawn a function on this core. | 133 | /// Spawn a function on this core |
| 105 | pub fn spawn<F>(&mut self, stack: &'static mut [usize], entry: F) -> Result<(), Error> | 134 | pub fn spawn<F>(&mut self, stack: &'static mut [usize], entry: F) -> Result<(), Error> |
| 106 | where | 135 | where |
| 107 | F: FnOnce() -> bad::Never + Send + 'static, | 136 | F: FnOnce() -> bad::Never + Send + 'static, |
| 108 | { | 137 | { |
| 109 | fn fifo_write(value: u32) { | 138 | // The first two ignored `u64` parameters are there to take up all of the registers, |
| 110 | unsafe { | 139 | // which means that the rest of the arguments are taken from the stack, |
| 111 | let sio = pac::SIO; | 140 | // where we're able to put them from core 0. |
| 112 | // Wait for the FIFO to have some space | 141 | extern "C" fn core1_startup<F: FnOnce() -> bad::Never>( |
| 113 | while !sio.fifo().st().read().rdy() { | 142 | _: u64, |
| 114 | cortex_m::asm::nop(); | 143 | _: u64, |
| 115 | } | 144 | entry: &mut ManuallyDrop<F>, |
| 116 | // Signal that it's safe for core 0 to get rid of the original value now. | 145 | stack_bottom: *mut usize, |
| 117 | sio.fifo().wr().write_value(value); | 146 | ) -> ! { |
| 118 | } | 147 | core1_setup(stack_bottom); |
| 119 | 148 | let entry = unsafe { ManuallyDrop::take(entry) }; | |
| 120 | // Fire off an event to the other core. | 149 | // Signal that it's safe for core 0 to get rid of the original value now. |
| 121 | // This is required as the other core may be `wfe` (waiting for event) | 150 | fifo_write(1); |
| 122 | cortex_m::asm::sev(); | 151 | |
| 152 | IS_CORE1_INIT.store(true, Ordering::Release); | ||
| 153 | // Enable fifo interrupt on CORE1 for `pause` functionality. | ||
| 154 | let irq = unsafe { interrupt::SIO_IRQ_PROC1::steal() }; | ||
| 155 | irq.enable(); | ||
| 156 | |||
| 157 | entry() | ||
| 123 | } | 158 | } |
| 124 | 159 | ||
| 125 | fn fifo_read() -> u32 { | 160 | // Reset the core |
| 126 | unsafe { | 161 | unsafe { |
| 127 | let sio = pac::SIO; | 162 | let psm = pac::PSM; |
| 128 | // Keep trying until FIFO has data | 163 | psm.frce_off().modify(|w| w.set_proc1(true)); |
| 129 | loop { | 164 | while !psm.frce_off().read().proc1() { |
| 130 | if sio.fifo().st().read().vld() { | 165 | cortex_m::asm::nop(); |
| 131 | return sio.fifo().rd().read(); | ||
| 132 | } else { | ||
| 133 | // We expect the sending core to `sev` on write. | ||
| 134 | cortex_m::asm::wfe(); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | } | 166 | } |
| 167 | psm.frce_off().modify(|w| w.set_proc1(false)); | ||
| 138 | } | 168 | } |
| 139 | 169 | ||
| 140 | fn fifo_drain() { | 170 | // Set up the stack |
| 141 | unsafe { | 171 | let mut stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len()) }; |
| 142 | let sio = pac::SIO; | 172 | |
| 143 | while sio.fifo().st().read().vld() { | 173 | // We don't want to drop this, since it's getting moved to the other core. |
| 144 | let _ = sio.fifo().rd().read(); | 174 | let mut entry = ManuallyDrop::new(entry); |
| 145 | } | 175 | |
| 146 | } | 176 | // Push the arguments to `core1_startup` onto the stack. |
| 177 | unsafe { | ||
| 178 | // Push `stack_bottom`. | ||
| 179 | stack_ptr = stack_ptr.sub(1); | ||
| 180 | stack_ptr.cast::<*mut usize>().write(stack.as_mut_ptr()); | ||
| 181 | |||
| 182 | // Push `entry`. | ||
| 183 | stack_ptr = stack_ptr.sub(1); | ||
| 184 | stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry); | ||
| 147 | } | 185 | } |
| 148 | 186 | ||
| 149 | match self.id { | 187 | // Make sure the compiler does not reorder the stack writes after to after the |
| 150 | CoreId::Core1 => { | 188 | // below FIFO writes, which would result in them not being seen by the second |
| 151 | // The first two ignored `u64` parameters are there to take up all of the registers, | 189 | // core. |
| 152 | // which means that the rest of the arguments are taken from the stack, | 190 | // |
| 153 | // where we're able to put them from core 0. | 191 | // From the compiler perspective, this doesn't guarantee that the second core |
| 154 | extern "C" fn core1_startup<F: FnOnce() -> bad::Never>( | 192 | // actually sees those writes. However, we know that the RP2040 doesn't have |
| 155 | _: u64, | 193 | // memory caches, and writes happen in-order. |
| 156 | _: u64, | 194 | compiler_fence(Ordering::Release); |
| 157 | entry: &mut ManuallyDrop<F>, | 195 | |
| 158 | stack_bottom: *mut usize, | 196 | let p = unsafe { cortex_m::Peripherals::steal() }; |
| 159 | ) -> ! { | 197 | let vector_table = p.SCB.vtor.read(); |
| 160 | core1_setup(stack_bottom); | 198 | |
| 161 | let entry = unsafe { ManuallyDrop::take(entry) }; | 199 | // After reset, core 1 is waiting to receive commands over FIFO. |
| 162 | // Signal that it's safe for core 0 to get rid of the original value now. | 200 | // This is the sequence to have it jump to some code. |
| 163 | fifo_write(1); | 201 | let cmd_seq = [ |
| 164 | entry() | 202 | 0, |
| 203 | 0, | ||
| 204 | 1, | ||
| 205 | vector_table as usize, | ||
| 206 | stack_ptr as usize, | ||
| 207 | core1_startup::<F> as usize, | ||
| 208 | ]; | ||
| 209 | |||
| 210 | let mut seq = 0; | ||
| 211 | let mut fails = 0; | ||
| 212 | loop { | ||
| 213 | let cmd = cmd_seq[seq] as u32; | ||
| 214 | if cmd == 0 { | ||
| 215 | fifo_drain(); | ||
| 216 | cortex_m::asm::sev(); | ||
| 217 | } | ||
| 218 | fifo_write(cmd); | ||
| 219 | |||
| 220 | let response = fifo_read(); | ||
| 221 | if cmd == response { | ||
| 222 | seq += 1; | ||
| 223 | } else { | ||
| 224 | seq = 0; | ||
| 225 | fails += 1; | ||
| 226 | if fails > 16 { | ||
| 227 | // The second core isn't responding, and isn't going to take the entrypoint, | ||
| 228 | // so we have to drop it ourselves. | ||
| 229 | drop(ManuallyDrop::into_inner(entry)); | ||
| 230 | return Err(Error::Unresponsive); | ||
| 165 | } | 231 | } |
| 232 | } | ||
| 233 | if seq >= cmd_seq.len() { | ||
| 234 | break; | ||
| 235 | } | ||
| 236 | } | ||
| 166 | 237 | ||
| 167 | // Reset the core | 238 | // Wait until the other core has copied `entry` before returning. |
| 168 | unsafe { | 239 | fifo_read(); |
| 169 | let psm = pac::PSM; | ||
| 170 | psm.frce_off().modify(|w| w.set_proc1(true)); | ||
| 171 | while !psm.frce_off().read().proc1() { | ||
| 172 | cortex_m::asm::nop(); | ||
| 173 | } | ||
| 174 | psm.frce_off().modify(|w| w.set_proc1(false)); | ||
| 175 | } | ||
| 176 | 240 | ||
| 177 | // Set up the stack | 241 | Ok(()) |
| 178 | let mut stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len()) }; | 242 | } |
| 243 | } | ||
| 179 | 244 | ||
| 180 | // We don't want to drop this, since it's getting moved to the other core. | 245 | /// Pause execution on CORE1. |
| 181 | let mut entry = ManuallyDrop::new(entry); | 246 | pub fn pause_core1() { |
| 247 | if IS_CORE1_INIT.load(Ordering::Acquire) { | ||
| 248 | fifo_write(PAUSE_TOKEN); | ||
| 249 | // Wait for CORE1 to signal it has paused execution. | ||
| 250 | while fifo_read() != PAUSE_TOKEN {} | ||
| 251 | } | ||
| 252 | } | ||
| 182 | 253 | ||
| 183 | // Push the arguments to `core1_startup` onto the stack. | 254 | /// Resume CORE1 execution. |
| 184 | unsafe { | 255 | pub fn resume_core1() { |
| 185 | // Push `stack_bottom`. | 256 | if IS_CORE1_INIT.load(Ordering::Acquire) { |
| 186 | stack_ptr = stack_ptr.sub(1); | 257 | fifo_write(RESUME_TOKEN); |
| 187 | stack_ptr.cast::<*mut usize>().write(stack.as_mut_ptr()); | 258 | // Wait for CORE1 to signal it has resumed execution. |
| 259 | while fifo_read() != RESUME_TOKEN {} | ||
| 260 | } | ||
| 261 | } | ||
| 188 | 262 | ||
| 189 | // Push `entry`. | 263 | // Push a value to the inter-core FIFO, block until space is available |
| 190 | stack_ptr = stack_ptr.sub(1); | 264 | #[inline(always)] |
| 191 | stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry); | 265 | fn fifo_write(value: u32) { |
| 192 | } | 266 | unsafe { |
| 267 | let sio = pac::SIO; | ||
| 268 | // Wait for the FIFO to have enough space | ||
| 269 | while !sio.fifo().st().read().rdy() { | ||
| 270 | cortex_m::asm::nop(); | ||
| 271 | } | ||
| 272 | sio.fifo().wr().write_value(value); | ||
| 273 | } | ||
| 274 | // Fire off an event to the other core. | ||
| 275 | // This is required as the other core may be `wfe` (waiting for event) | ||
| 276 | cortex_m::asm::sev(); | ||
| 277 | } | ||
| 193 | 278 | ||
| 194 | // Make sure the compiler does not reorder the stack writes after to after the | 279 | // Pop a value from inter-core FIFO, block until available |
| 195 | // below FIFO writes, which would result in them not being seen by the second | 280 | #[inline(always)] |
| 196 | // core. | 281 | fn fifo_read() -> u32 { |
| 197 | // | 282 | unsafe { |
| 198 | // From the compiler perspective, this doesn't guarantee that the second core | 283 | let sio = pac::SIO; |
| 199 | // actually sees those writes. However, we know that the RP2040 doesn't have | 284 | // Wait until FIFO has data |
| 200 | // memory caches, and writes happen in-order. | 285 | while !sio.fifo().st().read().vld() { |
| 201 | compiler_fence(Ordering::Release); | 286 | cortex_m::asm::nop(); |
| 202 | 287 | } | |
| 203 | let p = unsafe { cortex_m::Peripherals::steal() }; | 288 | sio.fifo().rd().read() |
| 204 | let vector_table = p.SCB.vtor.read(); | 289 | } |
| 205 | 290 | } | |
| 206 | // After reset, core 1 is waiting to receive commands over FIFO. | ||
| 207 | // This is the sequence to have it jump to some code. | ||
| 208 | let cmd_seq = [ | ||
| 209 | 0, | ||
| 210 | 0, | ||
| 211 | 1, | ||
| 212 | vector_table as usize, | ||
| 213 | stack_ptr as usize, | ||
| 214 | core1_startup::<F> as usize, | ||
| 215 | ]; | ||
| 216 | |||
| 217 | let mut seq = 0; | ||
| 218 | let mut fails = 0; | ||
| 219 | loop { | ||
| 220 | let cmd = cmd_seq[seq] as u32; | ||
| 221 | if cmd == 0 { | ||
| 222 | fifo_drain(); | ||
| 223 | cortex_m::asm::sev(); | ||
| 224 | } | ||
| 225 | fifo_write(cmd); | ||
| 226 | |||
| 227 | let response = fifo_read(); | ||
| 228 | if cmd == response { | ||
| 229 | seq += 1; | ||
| 230 | } else { | ||
| 231 | seq = 0; | ||
| 232 | fails += 1; | ||
| 233 | if fails > 16 { | ||
| 234 | // The second core isn't responding, and isn't going to take the entrypoint, | ||
| 235 | // so we have to drop it ourselves. | ||
| 236 | drop(ManuallyDrop::into_inner(entry)); | ||
| 237 | return Err(Error::Unresponsive); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | if seq >= cmd_seq.len() { | ||
| 241 | break; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | 291 | ||
| 245 | // Wait until the other core has copied `entry` before returning. | 292 | // Pop a value from inter-core FIFO, `wfe` until available |
| 246 | fifo_read(); | 293 | #[inline(always)] |
| 294 | fn fifo_read_wfe() -> u32 { | ||
| 295 | unsafe { | ||
| 296 | let sio = pac::SIO; | ||
| 297 | // Wait until FIFO has data | ||
| 298 | while !sio.fifo().st().read().vld() { | ||
| 299 | cortex_m::asm::wfe(); | ||
| 300 | } | ||
| 301 | sio.fifo().rd().read() | ||
| 302 | } | ||
| 303 | } | ||
| 247 | 304 | ||
| 248 | Ok(()) | 305 | // Drain inter-core FIFO |
| 249 | } | 306 | #[inline(always)] |
| 250 | _ => Err(Error::InvalidCore), | 307 | fn fifo_drain() { |
| 308 | unsafe { | ||
| 309 | let sio = pac::SIO; | ||
| 310 | while sio.fifo().st().read().vld() { | ||
| 311 | let _ = sio.fifo().rd().read(); | ||
| 251 | } | 312 | } |
| 252 | } | 313 | } |
| 253 | } | 314 | } |
