Merge pull request #1880 from phire/rp_bootsel
rp2040: BOOTSEL button support
This commit is contained in:
commit
9c6a2d9cbd
83
embassy-rp/src/bootsel.rs
Normal file
83
embassy-rp/src/bootsel.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! 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;
|
||||
|
||||
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");
|
||||
|
||||
// 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!()
|
||||
}
|
||||
}
|
@ -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<u32, Error> {
|
||||
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 {}
|
||||
|
@ -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;
|
||||
@ -193,6 +194,7 @@ embassy_hal_internal::peripherals! {
|
||||
PIO1,
|
||||
|
||||
WATCHDOG,
|
||||
BOOTSEL,
|
||||
}
|
||||
|
||||
macro_rules! select_bootloader {
|
||||
|
26
tests/rp/src/bin/bootsel.rs
Normal file
26
tests/rp/src/bin/bootsel.rs
Normal file
@ -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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user