diff options
| author | Dario Nieuwenhuis <[email protected]> | 2023-10-06 23:56:00 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-10-06 23:56:00 +0000 |
| commit | 9c6a2d9cbdc22aa82b3fd26f42eb2b37c919fef4 (patch) | |
| tree | 44fc4692b9fa3491b923840bf7c9c3c33212d4fe | |
| parent | 3bf8e4de5ffba9428752835b45a546a8e420a288 (diff) | |
| parent | b67b179933806f270465dcf5f246c605eba15dd9 (diff) | |
Merge pull request #1880 from phire/rp_bootsel
rp2040: BOOTSEL button support
| -rw-r--r-- | embassy-rp/src/bootsel.rs | 83 | ||||
| -rw-r--r-- | embassy-rp/src/flash.rs | 78 | ||||
| -rw-r--r-- | embassy-rp/src/lib.rs | 2 | ||||
| -rw-r--r-- | tests/rp/src/bin/bootsel.rs | 26 |
4 files changed, 150 insertions, 39 deletions
diff --git a/embassy-rp/src/bootsel.rs b/embassy-rp/src/bootsel.rs new file mode 100644 index 000000000..540255ae3 --- /dev/null +++ b/embassy-rp/src/bootsel.rs | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | //! Boot Select button | ||
| 2 | //! | ||
| 3 | //! The RP2040 rom supports a BOOTSEL button that is used to enter the USB bootloader | ||
| 4 | //! if held during reset. To avoid wasting GPIO pins, the button is multiplexed onto | ||
| 5 | //! the CS pin of the QSPI flash, but that makes it somewhat expensive and complicated | ||
| 6 | //! to utilize outside of the rom's bootloader. | ||
| 7 | //! | ||
| 8 | //! This module provides functionality to poll BOOTSEL from an embassy application. | ||
| 9 | |||
| 10 | use crate::flash::in_ram; | ||
| 11 | |||
| 12 | impl crate::peripherals::BOOTSEL { | ||
| 13 | /// Polls the BOOTSEL button. Returns true if the button is pressed. | ||
| 14 | /// | ||
| 15 | /// Polling isn't cheap, as this function waits for core 1 to finish it's current | ||
| 16 | /// task and for any DMAs from flash to complete | ||
| 17 | pub fn is_pressed(&mut self) -> bool { | ||
| 18 | let mut cs_status = Default::default(); | ||
| 19 | |||
| 20 | unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); | ||
| 21 | |||
| 22 | // bootsel is active low, so invert | ||
| 23 | !cs_status.infrompad() | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | mod ram_helpers { | ||
| 28 | use rp_pac::io::regs::GpioStatus; | ||
| 29 | |||
| 30 | /// Temporally reconfigures the CS gpio and returns the GpioStatus. | ||
| 31 | |||
| 32 | /// This function runs from RAM so it can disable flash XIP. | ||
| 33 | /// | ||
| 34 | /// # Safety | ||
| 35 | /// | ||
| 36 | /// The caller must ensure flash is idle and will remain idle. | ||
| 37 | /// This function must live in ram. It uses inline asm to avoid any | ||
| 38 | /// potential calls to ABI functions that might be in flash. | ||
| 39 | #[inline(never)] | ||
| 40 | #[link_section = ".data.ram_func"] | ||
| 41 | #[cfg(target_arch = "arm")] | ||
| 42 | pub unsafe fn read_cs_status() -> GpioStatus { | ||
| 43 | let result: u32; | ||
| 44 | |||
| 45 | // Magic value, used as both OEOVER::DISABLE and delay loop counter | ||
| 46 | let magic = 0x2000; | ||
| 47 | |||
| 48 | core::arch::asm!( | ||
| 49 | ".equiv GPIO_STATUS, 0x0", | ||
| 50 | ".equiv GPIO_CTRL, 0x4", | ||
| 51 | |||
| 52 | "ldr {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", | ||
| 53 | |||
| 54 | // The BOOTSEL pulls the flash's CS line low though a 1K resistor. | ||
| 55 | // this is weak enough to avoid disrupting normal operation. | ||
| 56 | // But, if we disable CS's output drive and allow it to float... | ||
| 57 | "str {val}, [{cs_gpio}, $GPIO_CTRL]", | ||
| 58 | |||
| 59 | // ...then wait for the state to settle... | ||
| 60 | "1:", // ~4000 cycle delay loop | ||
| 61 | "subs {val}, #8", | ||
| 62 | "bne 1b", | ||
| 63 | |||
| 64 | // ...we can read the current state of bootsel | ||
| 65 | "ldr {val}, [{cs_gpio}, $GPIO_STATUS]", | ||
| 66 | |||
| 67 | // Finally, restore CS to normal operation so XIP can continue | ||
| 68 | "str {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", | ||
| 69 | |||
| 70 | cs_gpio = in(reg) rp_pac::IO_QSPI.gpio(1).as_ptr(), | ||
| 71 | orig_ctrl = out(reg) _, | ||
| 72 | val = inout(reg) magic => result, | ||
| 73 | options(nostack), | ||
| 74 | ); | ||
| 75 | |||
| 76 | core::mem::transmute(result) | ||
| 77 | } | ||
| 78 | |||
| 79 | #[cfg(not(target_arch = "arm"))] | ||
| 80 | pub unsafe fn read_cs_status() -> GpioStatus { | ||
| 81 | unimplemented!() | ||
| 82 | } | ||
| 83 | } | ||
diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index 1c1c2449e..8fb5542f1 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs | |||
| @@ -131,7 +131,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI | |||
| 131 | 131 | ||
| 132 | let len = to - from; | 132 | let len = to - from; |
| 133 | 133 | ||
| 134 | unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len))? }; | 134 | unsafe { in_ram(|| ram_helpers::flash_range_erase(from, len))? }; |
| 135 | 135 | ||
| 136 | Ok(()) | 136 | Ok(()) |
| 137 | } | 137 | } |
| @@ -156,7 +156,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI | |||
| 156 | 156 | ||
| 157 | let unaligned_offset = offset as usize - start; | 157 | let unaligned_offset = offset as usize - start; |
| 158 | 158 | ||
| 159 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } | 159 | unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | let remaining_len = bytes.len() - start_padding; | 162 | let remaining_len = bytes.len() - start_padding; |
| @@ -174,12 +174,12 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI | |||
| 174 | if bytes.as_ptr() as usize >= 0x2000_0000 { | 174 | if bytes.as_ptr() as usize >= 0x2000_0000 { |
| 175 | let aligned_data = &bytes[start_padding..end_padding]; | 175 | let aligned_data = &bytes[start_padding..end_padding]; |
| 176 | 176 | ||
| 177 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } | 177 | unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } |
| 178 | } else { | 178 | } else { |
| 179 | for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { | 179 | for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { |
| 180 | let mut ram_buf = [0xFF_u8; PAGE_SIZE]; | 180 | let mut ram_buf = [0xFF_u8; PAGE_SIZE]; |
| 181 | ram_buf.copy_from_slice(chunk); | 181 | ram_buf.copy_from_slice(chunk); |
| 182 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } | 182 | unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } |
| 183 | aligned_offset += PAGE_SIZE; | 183 | aligned_offset += PAGE_SIZE; |
| 184 | } | 184 | } |
| 185 | } | 185 | } |
| @@ -194,47 +194,15 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI | |||
| 194 | 194 | ||
| 195 | let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); | 195 | let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); |
| 196 | 196 | ||
| 197 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } | 197 | unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } |
| 198 | } | 198 | } |
| 199 | 199 | ||
| 200 | Ok(()) | 200 | Ok(()) |
| 201 | } | 201 | } |
| 202 | 202 | ||
| 203 | /// Make sure to uphold the contract points with rp2040-flash. | ||
| 204 | /// - interrupts must be disabled | ||
| 205 | /// - DMA must not access flash memory | ||
| 206 | unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> { | ||
| 207 | // Make sure we're running on CORE0 | ||
| 208 | let core_id: u32 = pac::SIO.cpuid().read(); | ||
| 209 | if core_id != 0 { | ||
| 210 | return Err(Error::InvalidCore); | ||
| 211 | } | ||
| 212 | |||
| 213 | // Make sure CORE1 is paused during the entire duration of the RAM function | ||
| 214 | crate::multicore::pause_core1(); | ||
| 215 | |||
| 216 | critical_section::with(|_| { | ||
| 217 | // Wait for all DMA channels in flash to finish before ram operation | ||
| 218 | const SRAM_LOWER: u32 = 0x2000_0000; | ||
| 219 | for n in 0..crate::dma::CHANNEL_COUNT { | ||
| 220 | let ch = crate::pac::DMA.ch(n); | ||
| 221 | while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} | ||
| 222 | } | ||
| 223 | // Wait for completion of any background reads | ||
| 224 | while pac::XIP_CTRL.stream_ctr().read().0 > 0 {} | ||
| 225 | |||
| 226 | // Run our flash operation in RAM | ||
| 227 | operation(); | ||
| 228 | }); | ||
| 229 | |||
| 230 | // Resume CORE1 execution | ||
| 231 | crate::multicore::resume_core1(); | ||
| 232 | Ok(()) | ||
| 233 | } | ||
| 234 | |||
| 235 | /// Read SPI flash unique ID | 203 | /// Read SPI flash unique ID |
| 236 | pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { | 204 | pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { |
| 237 | unsafe { self.in_ram(|| ram_helpers::flash_unique_id(uid))? }; | 205 | unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; |
| 238 | Ok(()) | 206 | Ok(()) |
| 239 | } | 207 | } |
| 240 | 208 | ||
| @@ -242,7 +210,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI | |||
| 242 | pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> { | 210 | pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> { |
| 243 | let mut jedec = None; | 211 | let mut jedec = None; |
| 244 | unsafe { | 212 | unsafe { |
| 245 | self.in_ram(|| { | 213 | in_ram(|| { |
| 246 | jedec.replace(ram_helpers::flash_jedec_id()); | 214 | jedec.replace(ram_helpers::flash_jedec_id()); |
| 247 | })?; | 215 | })?; |
| 248 | }; | 216 | }; |
| @@ -871,6 +839,38 @@ mod ram_helpers { | |||
| 871 | } | 839 | } |
| 872 | } | 840 | } |
| 873 | 841 | ||
| 842 | /// Make sure to uphold the contract points with rp2040-flash. | ||
| 843 | /// - interrupts must be disabled | ||
| 844 | /// - DMA must not access flash memory | ||
| 845 | pub(crate) unsafe fn in_ram(operation: impl FnOnce()) -> Result<(), Error> { | ||
| 846 | // Make sure we're running on CORE0 | ||
| 847 | let core_id: u32 = pac::SIO.cpuid().read(); | ||
| 848 | if core_id != 0 { | ||
| 849 | return Err(Error::InvalidCore); | ||
| 850 | } | ||
| 851 | |||
| 852 | // Make sure CORE1 is paused during the entire duration of the RAM function | ||
| 853 | crate::multicore::pause_core1(); | ||
| 854 | |||
| 855 | critical_section::with(|_| { | ||
| 856 | // Wait for all DMA channels in flash to finish before ram operation | ||
| 857 | const SRAM_LOWER: u32 = 0x2000_0000; | ||
| 858 | for n in 0..crate::dma::CHANNEL_COUNT { | ||
| 859 | let ch = crate::pac::DMA.ch(n); | ||
| 860 | while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} | ||
| 861 | } | ||
| 862 | // Wait for completion of any background reads | ||
| 863 | while pac::XIP_CTRL.stream_ctr().read().0 > 0 {} | ||
| 864 | |||
| 865 | // Run our flash operation in RAM | ||
| 866 | operation(); | ||
| 867 | }); | ||
| 868 | |||
| 869 | // Resume CORE1 execution | ||
| 870 | crate::multicore::resume_core1(); | ||
| 871 | Ok(()) | ||
| 872 | } | ||
| 873 | |||
| 874 | mod sealed { | 874 | mod sealed { |
| 875 | pub trait Instance {} | 875 | pub trait Instance {} |
| 876 | pub trait Mode {} | 876 | pub trait Mode {} |
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index e8f818bcf..2728395b2 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -10,6 +10,7 @@ mod critical_section_impl; | |||
| 10 | mod intrinsics; | 10 | mod intrinsics; |
| 11 | 11 | ||
| 12 | pub mod adc; | 12 | pub mod adc; |
| 13 | pub mod bootsel; | ||
| 13 | pub mod clocks; | 14 | pub mod clocks; |
| 14 | pub mod dma; | 15 | pub mod dma; |
| 15 | pub mod flash; | 16 | pub mod flash; |
| @@ -193,6 +194,7 @@ embassy_hal_internal::peripherals! { | |||
| 193 | PIO1, | 194 | PIO1, |
| 194 | 195 | ||
| 195 | WATCHDOG, | 196 | WATCHDOG, |
| 197 | BOOTSEL, | ||
| 196 | } | 198 | } |
| 197 | 199 | ||
| 198 | macro_rules! select_bootloader { | 200 | macro_rules! select_bootloader { |
diff --git a/tests/rp/src/bin/bootsel.rs b/tests/rp/src/bin/bootsel.rs new file mode 100644 index 000000000..df1ed8d2e --- /dev/null +++ b/tests/rp/src/bin/bootsel.rs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | teleprobe_meta::target!(b"rpi-pico"); | ||
| 5 | |||
| 6 | use defmt::{assert_eq, *}; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_time::{Duration, Timer}; | ||
| 9 | use {defmt_rtt as _, panic_probe as _}; | ||
| 10 | |||
| 11 | #[embassy_executor::main] | ||
| 12 | async fn main(_spawner: Spawner) { | ||
| 13 | let mut p = embassy_rp::init(Default::default()); | ||
| 14 | info!("Hello World!"); | ||
| 15 | |||
| 16 | // add some delay to give an attached debug probe time to parse the | ||
| 17 | // defmt RTT header. Reading that header might touch flash memory, which | ||
| 18 | // interferes with flash write operations. | ||
| 19 | // https://github.com/knurling-rs/defmt/pull/683 | ||
| 20 | Timer::after(Duration::from_millis(10)).await; | ||
| 21 | |||
| 22 | assert_eq!(p.BOOTSEL.is_pressed(), false); | ||
| 23 | |||
| 24 | info!("Test OK"); | ||
| 25 | cortex_m::asm::bkpt(); | ||
| 26 | } | ||
