From c6d53e7bce9a9f04b2d479a150c4e1aee1bb4ea8 Mon Sep 17 00:00:00 2001 From: Scott Mansell Date: Sat, 23 Sep 2023 17:34:08 +1200 Subject: [PATCH 1/4] rp2040: move in_ram helper outside of Flash's impl Allow it to be called from other modules. --- embassy-rp/src/flash.rs | 78 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index 1c1c2449..8fb5542f 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 let len = to - from; - unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len))? }; + unsafe { in_ram(|| ram_helpers::flash_range_erase(from, len))? }; Ok(()) } @@ -156,7 +156,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI let unaligned_offset = offset as usize - start; - unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } } 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 if bytes.as_ptr() as usize >= 0x2000_0000 { let aligned_data = &bytes[start_padding..end_padding]; - unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } + unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } } else { for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { let mut ram_buf = [0xFF_u8; PAGE_SIZE]; ram_buf.copy_from_slice(chunk); - unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } + unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } aligned_offset += PAGE_SIZE; } } @@ -194,47 +194,15 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); - unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } } Ok(()) } - /// Make sure to uphold the contract points with rp2040-flash. - /// - interrupts must be disabled - /// - DMA must not access flash memory - unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> { - // Make sure we're running on CORE0 - let core_id: u32 = pac::SIO.cpuid().read(); - if core_id != 0 { - return Err(Error::InvalidCore); - } - - // Make sure CORE1 is paused during the entire duration of the RAM function - crate::multicore::pause_core1(); - - critical_section::with(|_| { - // Wait for all DMA channels in flash to finish before ram operation - const SRAM_LOWER: u32 = 0x2000_0000; - for n in 0..crate::dma::CHANNEL_COUNT { - let ch = crate::pac::DMA.ch(n); - while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} - } - // Wait for completion of any background reads - while pac::XIP_CTRL.stream_ctr().read().0 > 0 {} - - // Run our flash operation in RAM - operation(); - }); - - // Resume CORE1 execution - crate::multicore::resume_core1(); - Ok(()) - } - /// Read SPI flash unique ID pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { - unsafe { self.in_ram(|| ram_helpers::flash_unique_id(uid))? }; + unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; Ok(()) } @@ -242,7 +210,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI pub fn blocking_jedec_id(&mut self) -> Result { let mut jedec = None; unsafe { - self.in_ram(|| { + in_ram(|| { jedec.replace(ram_helpers::flash_jedec_id()); })?; }; @@ -871,6 +839,38 @@ mod ram_helpers { } } +/// Make sure to uphold the contract points with rp2040-flash. +/// - interrupts must be disabled +/// - DMA must not access flash memory +pub(crate) unsafe fn in_ram(operation: impl FnOnce()) -> Result<(), Error> { + // Make sure we're running on CORE0 + let core_id: u32 = pac::SIO.cpuid().read(); + if core_id != 0 { + return Err(Error::InvalidCore); + } + + // Make sure CORE1 is paused during the entire duration of the RAM function + crate::multicore::pause_core1(); + + critical_section::with(|_| { + // Wait for all DMA channels in flash to finish before ram operation + const SRAM_LOWER: u32 = 0x2000_0000; + for n in 0..crate::dma::CHANNEL_COUNT { + let ch = crate::pac::DMA.ch(n); + while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} + } + // Wait for completion of any background reads + while pac::XIP_CTRL.stream_ctr().read().0 > 0 {} + + // Run our flash operation in RAM + operation(); + }); + + // Resume CORE1 execution + crate::multicore::resume_core1(); + Ok(()) +} + mod sealed { pub trait Instance {} pub trait Mode {} From 3e054a6f0d3ba018315f7cb7f0a373221e15737a Mon Sep 17 00:00:00 2001 From: Scott Mansell Date: Sat, 23 Sep 2023 17:34:47 +1200 Subject: [PATCH 2/4] rp2040: implement BOOTSEL button support --- embassy-rp/src/bootsel.rs | 81 +++++++++++++++++++++++++++++++++++++++ embassy-rp/src/lib.rs | 1 + 2 files changed, 82 insertions(+) create mode 100644 embassy-rp/src/bootsel.rs diff --git a/embassy-rp/src/bootsel.rs b/embassy-rp/src/bootsel.rs new file mode 100644 index 00000000..69d620e8 --- /dev/null +++ b/embassy-rp/src/bootsel.rs @@ -0,0 +1,81 @@ +//! Boot Select button +//! +//! The RP2040 rom supports a BOOTSEL button that is used to enter the USB bootloader +//! if held during reset. To avoid wasting GPIO pins, the button is multiplexed onto +//! the CS pin of the QSPI flash, but that makes it somewhat expensive and complicated +//! to utilize outside of the rom's bootloader. +//! +//! This module provides functionality to poll BOOTSEL from an embassy application. + +use crate::flash::in_ram; + +/// Polls the BOOTSEL button. Returns true if the button is pressed. +/// +/// Polling isn't cheap, as this function waits for core 1 to finish it's current +/// task and for any DMAs from flash to complete +pub fn poll_bootsel() -> bool { + let mut cs_status = Default::default(); + + unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); + + // bootsel is active low, so invert + !cs_status.infrompad() +} + +mod ram_helpers { + use rp_pac::io::regs::GpioStatus; + + /// Temporally reconfigures the CS gpio and returns the GpioStatus. + + /// This function runs from RAM so it can disable flash XIP. + /// + /// # Safety + /// + /// The caller must ensure flash is idle and will remain idle. + /// This function must live in ram. It uses inline asm to avoid any + /// potential calls to ABI functions that might be in flash. + #[inline(never)] + #[link_section = ".data.ram_func"] + #[cfg(target_arch = "arm")] + pub unsafe fn read_cs_status() -> GpioStatus { + let result: u32; + + // Magic value, used as both OEOVER::DISABLE and delay loop counter + let magic = 0x2000; + + core::arch::asm!( + ".equiv GPIO_STATUS, 0x0", + ".equiv GPIO_CTRL, 0x4", + + "ldr {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + // The BOOTSEL pulls the flash's CS line low though a 1K resistor. + // this is weak enough to avoid disrupting normal operation. + // But, if we disable CS's output drive and allow it to float... + "str {val}, [{cs_gpio}, $GPIO_CTRL]", + + // ...then wait for the state to settle... + "1:", // ~4000 cycle delay loop + "subs {val}, #8", + "bne 1b", + + // ...we can read the current state of bootsel + "ldr {val}, [{cs_gpio}, $GPIO_STATUS]", + + // Finally, restore CS to normal operation so XIP can continue + "str {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + cs_gpio = in(reg) rp_pac::IO_QSPI.gpio(1).as_ptr(), + orig_ctrl = out(reg) _, + val = inout(reg) magic => result, + options(nostack), + ); + + core::mem::transmute(result) + } + + #[cfg(not(target_arch = "arm"))] + pub unsafe fn read_cs_status() -> GpioStatus { + unimplemented!() + } +} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index e8f818bc..fb918920 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -10,6 +10,7 @@ mod critical_section_impl; mod intrinsics; pub mod adc; +pub mod bootsel; pub mod clocks; pub mod dma; pub mod flash; From d4ed8e5779d5e09ea57728a48fc8654d93b786f8 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 7 Oct 2023 01:46:57 +0200 Subject: [PATCH 3/4] rp/bootsel: change it to a method on the peripheral. --- embassy-rp/src/bootsel.rs | 20 +++++++++++--------- embassy-rp/src/lib.rs | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/embassy-rp/src/bootsel.rs b/embassy-rp/src/bootsel.rs index 69d620e8..540255ae 100644 --- a/embassy-rp/src/bootsel.rs +++ b/embassy-rp/src/bootsel.rs @@ -9,17 +9,19 @@ use crate::flash::in_ram; -/// Polls the BOOTSEL button. Returns true if the button is pressed. -/// -/// Polling isn't cheap, as this function waits for core 1 to finish it's current -/// task and for any DMAs from flash to complete -pub fn poll_bootsel() -> bool { - let mut cs_status = Default::default(); +impl crate::peripherals::BOOTSEL { + /// Polls the BOOTSEL button. Returns true if the button is pressed. + /// + /// Polling isn't cheap, as this function waits for core 1 to finish it's current + /// task and for any DMAs from flash to complete + pub fn is_pressed(&mut self) -> bool { + let mut cs_status = Default::default(); - unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); + unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); - // bootsel is active low, so invert - !cs_status.infrompad() + // bootsel is active low, so invert + !cs_status.infrompad() + } } mod ram_helpers { diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index fb918920..2728395b 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -194,6 +194,7 @@ embassy_hal_internal::peripherals! { PIO1, WATCHDOG, + BOOTSEL, } macro_rules! select_bootloader { From b67b179933806f270465dcf5f246c605eba15dd9 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 7 Oct 2023 01:47:25 +0200 Subject: [PATCH 4/4] rp/bootsel: add test. --- tests/rp/src/bin/bootsel.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/rp/src/bin/bootsel.rs diff --git a/tests/rp/src/bin/bootsel.rs b/tests/rp/src/bin/bootsel.rs new file mode 100644 index 00000000..df1ed8d2 --- /dev/null +++ b/tests/rp/src/bin/bootsel.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after(Duration::from_millis(10)).await; + + assert_eq!(p.BOOTSEL.is_pressed(), false); + + info!("Test OK"); + cortex_m::asm::bkpt(); +}