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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user