aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-10-06 23:56:00 +0000
committerGitHub <[email protected]>2023-10-06 23:56:00 +0000
commit9c6a2d9cbdc22aa82b3fd26f42eb2b37c919fef4 (patch)
tree44fc4692b9fa3491b923840bf7c9c3c33212d4fe
parent3bf8e4de5ffba9428752835b45a546a8e420a288 (diff)
parentb67b179933806f270465dcf5f246c605eba15dd9 (diff)
Merge pull request #1880 from phire/rp_bootsel
rp2040: BOOTSEL button support
-rw-r--r--embassy-rp/src/bootsel.rs83
-rw-r--r--embassy-rp/src/flash.rs78
-rw-r--r--embassy-rp/src/lib.rs2
-rw-r--r--tests/rp/src/bin/bootsel.rs26
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
10use crate::flash::in_ram;
11
12impl 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
27mod 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
845pub(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
874mod sealed { 874mod 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;
10mod intrinsics; 10mod intrinsics;
11 11
12pub mod adc; 12pub mod adc;
13pub mod bootsel;
13pub mod clocks; 14pub mod clocks;
14pub mod dma; 15pub mod dma;
15pub mod flash; 16pub 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
198macro_rules! select_bootloader { 200macro_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)]
4teleprobe_meta::target!(b"rpi-pico");
5
6use defmt::{assert_eq, *};
7use embassy_executor::Spawner;
8use embassy_time::{Duration, Timer};
9use {defmt_rtt as _, panic_probe as _};
10
11#[embassy_executor::main]
12async 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}