diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-12-13 14:55:34 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-12-13 14:55:34 +0000 |
| commit | 47747d3b73f392e53ead8ff49cd09fd017df3215 (patch) | |
| tree | 9fb66fa2bb865d224ba2d98c5f1fa3bdf9e34b26 | |
| parent | 36639e5262a79df6a3c5acf5b083d51943ca5f97 (diff) | |
| parent | c4d8f3579e4927446421730d52a409d724f2800c (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.toml | 3 | ||||
| -rw-r--r-- | embassy-rp/src/critical_section_impl.rs | 142 | ||||
| -rw-r--r-- | embassy-rp/src/flash.rs | 29 | ||||
| -rw-r--r-- | embassy-rp/src/lib.rs | 6 | ||||
| -rw-r--r-- | embassy-rp/src/multicore.rs | 306 | ||||
| -rw-r--r-- | examples/rp/Cargo.toml | 5 | ||||
| -rw-r--r-- | examples/rp/src/bin/multicore.rs | 60 | ||||
| -rw-r--r-- | tests/rp/Cargo.toml | 4 | ||||
| -rw-r--r-- | tests/rp/src/bin/multicore.rs | 47 |
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] |
| 16 | defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"] | 16 | defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"] |
| 17 | 17 | ||
| 18 | # critical section that is safe for multicore use | ||
| 19 | critical-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 @@ | |||
| 1 | use core::sync::atomic::{AtomicU8, Ordering}; | ||
| 2 | |||
| 3 | use crate::pac; | ||
| 4 | |||
| 5 | struct RpSpinlockCs; | ||
| 6 | critical_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 | ||
| 11 | const 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 | ||
| 16 | static 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` | ||
| 24 | const LOCK_ALREADY_OWNED: u8 = 2; | ||
| 25 | |||
| 26 | unsafe 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 | |||
| 36 | impl 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 | |||
| 95 | pub struct Spinlock<const N: usize>(core::marker::PhantomData<()>) | ||
| 96 | where | ||
| 97 | Spinlock<N>: SpinlockValid; | ||
| 98 | |||
| 99 | impl<const N: usize> Spinlock<N> | ||
| 100 | where | ||
| 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 | |||
| 130 | impl<const N: usize> Drop for Spinlock<N> | ||
| 131 | where | ||
| 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 | |||
| 140 | pub(crate) type Spinlock31 = Spinlock<31>; | ||
| 141 | pub trait SpinlockValid {} | ||
| 142 | impl 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 | ||
| 9 | use crate::pac; | ||
| 9 | use crate::peripherals::FLASH; | 10 | use crate::peripherals::FLASH; |
| 10 | 11 | ||
| 11 | pub const FLASH_BASE: usize = 0x10000000; | 12 | pub 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. |
| 6 | pub(crate) mod fmt; | 6 | pub(crate) mod fmt; |
| 7 | 7 | ||
| 8 | #[cfg(feature = "critical-section-impl")] | ||
| 9 | mod critical_section_impl; | ||
| 10 | |||
| 8 | mod intrinsics; | 11 | mod intrinsics; |
| 9 | 12 | ||
| 10 | pub mod adc; | 13 | pub mod adc; |
| @@ -31,6 +34,7 @@ pub mod usb; | |||
| 31 | 34 | ||
| 32 | pub mod clocks; | 35 | pub mod clocks; |
| 33 | pub mod flash; | 36 | pub mod flash; |
| 37 | pub mod multicore; | ||
| 34 | mod reset; | 38 | mod 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 | |||
| 31 | use core::mem::ManuallyDrop; | ||
| 32 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 33 | |||
| 34 | use atomic_polyfill::AtomicBool; | ||
| 35 | |||
| 36 | use crate::interrupt::{Interrupt, InterruptExt}; | ||
| 37 | use crate::peripherals::CORE1; | ||
| 38 | use crate::{interrupt, pac}; | ||
| 39 | |||
| 40 | const PAUSE_TOKEN: u32 = 0xDEADBEEF; | ||
| 41 | const RESUME_TOKEN: u32 = !0xDEADBEEF; | ||
| 42 | static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); | ||
| 43 | |||
| 44 | #[inline(always)] | ||
| 45 | fn 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)] | ||
| 71 | fn 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))] | ||
| 77 | pub struct Stack<const SIZE: usize> { | ||
| 78 | /// Memory to be used for the stack | ||
| 79 | pub mem: [u8; SIZE], | ||
| 80 | } | ||
| 81 | |||
| 82 | impl<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"] | ||
| 91 | unsafe 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 | ||
| 114 | pub fn spawn_core1<F, const SIZE: usize>(_core1: CORE1, stack: &'static mut Stack<SIZE>, entry: F) | ||
| 115 | where | ||
| 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. | ||
| 223 | pub 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. | ||
| 232 | pub 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)] | ||
| 242 | fn 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)] | ||
| 258 | fn 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)] | ||
| 271 | fn 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)] | ||
| 284 | fn 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 | ||
| 294 | mod 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" | |||
| 9 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } | 9 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } |
| 10 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } | 10 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } |
| 11 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } | 11 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } |
| 12 | embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } | 12 | embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio", "critical-section-impl"] } |
| 13 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | 13 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } |
| 14 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } | 14 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } |
| 15 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } | 15 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } |
| @@ -18,7 +18,8 @@ embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } | |||
| 18 | defmt = "0.3" | 18 | defmt = "0.3" |
| 19 | defmt-rtt = "0.4" | 19 | defmt-rtt = "0.4" |
| 20 | 20 | ||
| 21 | cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } | 21 | #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } |
| 22 | cortex-m = { version = "0.7.6" } | ||
| 22 | cortex-m-rt = "0.7.0" | 23 | cortex-m-rt = "0.7.0" |
| 23 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 24 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 24 | futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } | 25 | futures = { 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 | |||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Executor; | ||
| 7 | use embassy_executor::_export::StaticCell; | ||
| 8 | use embassy_rp::gpio::{Level, Output}; | ||
| 9 | use embassy_rp::multicore::{spawn_core1, Stack}; | ||
| 10 | use embassy_rp::peripherals::PIN_25; | ||
| 11 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 12 | use embassy_sync::channel::Channel; | ||
| 13 | use embassy_time::{Duration, Timer}; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | static mut CORE1_STACK: Stack<4096> = Stack::new(); | ||
| 17 | static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); | ||
| 18 | static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); | ||
| 19 | static CHANNEL: Channel<CriticalSectionRawMutex, LedState, 1> = Channel::new(); | ||
| 20 | |||
| 21 | enum LedState { | ||
| 22 | On, | ||
| 23 | Off, | ||
| 24 | } | ||
| 25 | |||
| 26 | #[cortex_m_rt::entry] | ||
| 27 | fn 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] | ||
| 41 | async 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] | ||
| 52 | async 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" | |||
| 8 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } | 8 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } |
| 9 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } | 9 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } |
| 10 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } | 10 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } |
| 11 | embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver"] } | 11 | embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl"] } |
| 12 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } | 12 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } |
| 13 | 13 | ||
| 14 | defmt = "0.3.0" | 14 | defmt = "0.3.0" |
| 15 | defmt-rtt = "0.4" | 15 | defmt-rtt = "0.4" |
| 16 | 16 | ||
| 17 | cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } | 17 | cortex-m = { version = "0.7.6" } |
| 18 | cortex-m-rt = "0.7.0" | 18 | cortex-m-rt = "0.7.0" |
| 19 | embedded-hal = "0.2.6" | 19 | embedded-hal = "0.2.6" |
| 20 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } | 20 | embedded-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 | |||
| 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 | } | ||
