diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-10-28 12:19:56 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-10-28 12:19:56 +0000 |
| commit | e7fdd500d8354a03fcd105c8298cf7b4798a4107 (patch) | |
| tree | 8f0ccc58ed21a79a5b70fd38e57c8c2f30ebc00c | |
| parent | 1f246d0e37f9044d8949358081b75e3cca0c4800 (diff) | |
| parent | bc21b6efafe607e6ed582b048baedb7803483ee7 (diff) | |
Merge #951
951: (embassy-rp): Implementation of generic flash mutation access r=Dirbaio a=MathiasKoch
I have attempted to utilize the work done in `rp2040-flash` by implementing `embedded-storage` traits on top, for RP2040.
Concerns:
1. ~~Should the DMA be paused where I have put a FIXME note? `DMA_CHx.ctrl_trig().write(|w| { w.set_en(false) })`? If so, how to properly do that without have control over the peripheral for the DMA channels? And if so, I assume we should only re-enable/unpause the ones that were enabled before?~~
2. ~~Should I make sure core2 is halted as part of this code? I am not sure if https://github.com/jannic/rp2040-flash/blob/ea8ab1ac807a7ab2b28a18bb5ca2e42495bb744d/examples/flash_example.rs#L103-L109 is heavy/slow code to run?~~
3. ~~Any good way of making this configurable over `FLASH_SIZE`, `WRITE_SIZE` and `ERASE_SIZE` without doing it as generics or parameters, as those make it possible to do differing configs throughout the same program, which feels wrong? Preferably, a compile-time option?~~
**EDIT:**
I have implemented the flash API here under the assumption that all external QSPI nor flashes are infact `Multiwrite` capable, as this makes it possible to use the ROM function for writes of 1 bytes at a time.
I have also added a HIL test for this, but because HIL tests are running 100% from RAM and I wanted to make sure it still works when running from flash, I have also added an example testing erase/write cycles of entire sectors, as well as single bytes in multi-write style.
Ping `@Dirbaio`
Co-authored-by: Mathias <[email protected]>
Co-authored-by: Vincent Stakenburg <[email protected]>
Co-authored-by: Joakim Hulthe <[email protected]>
Co-authored-by: Alex Martens <[email protected]>
Co-authored-by: Ulf Lilleengen <[email protected]>
Co-authored-by: Dario Nieuwenhuis <[email protected]>
| -rw-r--r-- | embassy-rp/Cargo.toml | 1 | ||||
| -rw-r--r-- | embassy-rp/src/dma.rs | 2 | ||||
| -rw-r--r-- | embassy-rp/src/flash.rs | 463 | ||||
| -rw-r--r-- | embassy-rp/src/lib.rs | 3 | ||||
| -rw-r--r-- | examples/rp/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/rp/src/bin/flash.rs | 89 | ||||
| -rw-r--r-- | tests/rp/Cargo.toml | 1 | ||||
| -rw-r--r-- | tests/rp/src/bin/flash.rs | 54 |
8 files changed, 613 insertions, 1 deletions
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index d2196be76..04b0c13ce 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml | |||
| @@ -54,6 +54,7 @@ critical-section = "1.1" | |||
| 54 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 54 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 55 | chrono = { version = "0.4", default-features = false, optional = true } | 55 | chrono = { version = "0.4", default-features = false, optional = true } |
| 56 | embedded-io = { version = "0.3.1", features = ["async"], optional = true } | 56 | embedded-io = { version = "0.3.1", features = ["async"], optional = true } |
| 57 | embedded-storage = { version = "0.3" } | ||
| 57 | 58 | ||
| 58 | rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] } | 59 | rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] } |
| 59 | #rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] } | 60 | #rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] } |
diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index 410c48666..fd281fd5d 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs | |||
| @@ -191,7 +191,7 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { | |||
| 191 | } | 191 | } |
| 192 | } | 192 | } |
| 193 | 193 | ||
| 194 | const CHANNEL_COUNT: usize = 12; | 194 | pub(crate) const CHANNEL_COUNT: usize = 12; |
| 195 | const NEW_AW: AtomicWaker = AtomicWaker::new(); | 195 | const NEW_AW: AtomicWaker = AtomicWaker::new(); |
| 196 | static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; | 196 | static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; |
| 197 | 197 | ||
diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs new file mode 100644 index 000000000..d09cc62fb --- /dev/null +++ b/embassy-rp/src/flash.rs | |||
| @@ -0,0 +1,463 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_hal_common::Peripheral; | ||
| 4 | use embedded_storage::nor_flash::{ | ||
| 5 | check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, | ||
| 6 | ReadNorFlash, | ||
| 7 | }; | ||
| 8 | |||
| 9 | use crate::peripherals::FLASH; | ||
| 10 | |||
| 11 | pub const FLASH_BASE: usize = 0x10000000; | ||
| 12 | |||
| 13 | // **NOTE**: | ||
| 14 | // | ||
| 15 | // These limitations are currently enforced because of using the | ||
| 16 | // RP2040 boot-rom flash functions, that are optimized for flash compatibility | ||
| 17 | // rather than performance. | ||
| 18 | pub const PAGE_SIZE: usize = 256; | ||
| 19 | pub const WRITE_SIZE: usize = 1; | ||
| 20 | pub const READ_SIZE: usize = 1; | ||
| 21 | pub const ERASE_SIZE: usize = 4096; | ||
| 22 | |||
| 23 | /// Error type for NVMC operations. | ||
| 24 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
| 25 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 26 | pub enum Error { | ||
| 27 | /// Opration using a location not in flash. | ||
| 28 | OutOfBounds, | ||
| 29 | /// Unaligned operation or using unaligned buffers. | ||
| 30 | Unaligned, | ||
| 31 | Other, | ||
| 32 | } | ||
| 33 | |||
| 34 | impl From<NorFlashErrorKind> for Error { | ||
| 35 | fn from(e: NorFlashErrorKind) -> Self { | ||
| 36 | match e { | ||
| 37 | NorFlashErrorKind::NotAligned => Self::Unaligned, | ||
| 38 | NorFlashErrorKind::OutOfBounds => Self::OutOfBounds, | ||
| 39 | _ => Self::Other, | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | impl NorFlashError for Error { | ||
| 45 | fn kind(&self) -> NorFlashErrorKind { | ||
| 46 | match self { | ||
| 47 | Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, | ||
| 48 | Self::Unaligned => NorFlashErrorKind::NotAligned, | ||
| 49 | Self::Other => NorFlashErrorKind::Other, | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | pub struct Flash<'d, T: Instance, const FLASH_SIZE: usize>(PhantomData<&'d mut T>); | ||
| 55 | |||
| 56 | impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> { | ||
| 57 | pub fn new(_flash: impl Peripheral<P = T> + 'd) -> Self { | ||
| 58 | Self(PhantomData) | ||
| 59 | } | ||
| 60 | |||
| 61 | pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { | ||
| 62 | check_read(self, offset, bytes.len())?; | ||
| 63 | |||
| 64 | let flash_data = unsafe { core::slice::from_raw_parts((FLASH_BASE as u32 + offset) as *const u8, bytes.len()) }; | ||
| 65 | |||
| 66 | bytes.copy_from_slice(flash_data); | ||
| 67 | Ok(()) | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn capacity(&self) -> usize { | ||
| 71 | FLASH_SIZE | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { | ||
| 75 | check_erase(self, from, to)?; | ||
| 76 | |||
| 77 | trace!( | ||
| 78 | "Erasing from 0x{:x} to 0x{:x}", | ||
| 79 | FLASH_BASE as u32 + from, | ||
| 80 | FLASH_BASE as u32 + to | ||
| 81 | ); | ||
| 82 | |||
| 83 | let len = to - from; | ||
| 84 | |||
| 85 | unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len, true)) }; | ||
| 86 | |||
| 87 | Ok(()) | ||
| 88 | } | ||
| 89 | |||
| 90 | pub fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { | ||
| 91 | check_write(self, offset, bytes.len())?; | ||
| 92 | |||
| 93 | trace!("Writing {:?} bytes to 0x{:x}", bytes.len(), FLASH_BASE as u32 + offset); | ||
| 94 | |||
| 95 | let end_offset = offset as usize + bytes.len(); | ||
| 96 | |||
| 97 | let padded_offset = (offset as *const u8).align_offset(PAGE_SIZE); | ||
| 98 | let start_padding = core::cmp::min(padded_offset, bytes.len()); | ||
| 99 | |||
| 100 | // Pad in the beginning | ||
| 101 | if start_padding > 0 { | ||
| 102 | let start = PAGE_SIZE - padded_offset; | ||
| 103 | let end = start + start_padding; | ||
| 104 | |||
| 105 | let mut pad_buf = [0xFF_u8; PAGE_SIZE]; | ||
| 106 | pad_buf[start..end].copy_from_slice(&bytes[..start_padding]); | ||
| 107 | |||
| 108 | let unaligned_offset = offset as usize - start; | ||
| 109 | |||
| 110 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true)) } | ||
| 111 | } | ||
| 112 | |||
| 113 | let remaining_len = bytes.len() - start_padding; | ||
| 114 | let end_padding = start_padding + PAGE_SIZE * (remaining_len / PAGE_SIZE); | ||
| 115 | |||
| 116 | // Write aligned slice of length in multiples of 256 bytes | ||
| 117 | // If the remaining bytes to be written is more than a full page. | ||
| 118 | if remaining_len >= PAGE_SIZE { | ||
| 119 | let mut aligned_offset = if start_padding > 0 { | ||
| 120 | offset as usize + padded_offset | ||
| 121 | } else { | ||
| 122 | offset as usize | ||
| 123 | }; | ||
| 124 | |||
| 125 | if bytes.as_ptr() as usize >= 0x2000_0000 { | ||
| 126 | let aligned_data = &bytes[start_padding..end_padding]; | ||
| 127 | |||
| 128 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data, true)) } | ||
| 129 | } else { | ||
| 130 | for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { | ||
| 131 | let mut ram_buf = [0xFF_u8; PAGE_SIZE]; | ||
| 132 | ram_buf.copy_from_slice(chunk); | ||
| 133 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf, true)) } | ||
| 134 | aligned_offset += PAGE_SIZE; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | // Pad in the end | ||
| 140 | let rem_offset = (end_offset as *const u8).align_offset(PAGE_SIZE); | ||
| 141 | let rem_padding = remaining_len % PAGE_SIZE; | ||
| 142 | if rem_padding > 0 { | ||
| 143 | let mut pad_buf = [0xFF_u8; PAGE_SIZE]; | ||
| 144 | pad_buf[..rem_padding].copy_from_slice(&bytes[end_padding..]); | ||
| 145 | |||
| 146 | let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); | ||
| 147 | |||
| 148 | unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf, true)) } | ||
| 149 | } | ||
| 150 | |||
| 151 | Ok(()) | ||
| 152 | } | ||
| 153 | |||
| 154 | /// Make sure to uphold the contract points with rp2040-flash. | ||
| 155 | /// - interrupts must be disabled | ||
| 156 | /// - DMA must not access flash memory | ||
| 157 | unsafe fn in_ram(&mut self, operation: impl FnOnce()) { | ||
| 158 | let dma_status = &mut [false; crate::dma::CHANNEL_COUNT]; | ||
| 159 | |||
| 160 | // TODO: Make sure CORE1 is paused during the entire duration of the RAM function | ||
| 161 | |||
| 162 | critical_section::with(|_| { | ||
| 163 | // Pause all DMA channels for the duration of the ram operation | ||
| 164 | for (number, status) in dma_status.iter_mut().enumerate() { | ||
| 165 | let ch = crate::pac::DMA.ch(number as _); | ||
| 166 | *status = ch.ctrl_trig().read().en(); | ||
| 167 | if *status { | ||
| 168 | ch.ctrl_trig().modify(|w| w.set_en(false)); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | // Run our flash operation in RAM | ||
| 173 | operation(); | ||
| 174 | |||
| 175 | // Re-enable previously enabled DMA channels | ||
| 176 | for (number, status) in dma_status.iter().enumerate() { | ||
| 177 | let ch = crate::pac::DMA.ch(number as _); | ||
| 178 | if *status { | ||
| 179 | ch.ctrl_trig().modify(|w| w.set_en(true)); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | }); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | impl<'d, T: Instance, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, FLASH_SIZE> { | ||
| 187 | type Error = Error; | ||
| 188 | } | ||
| 189 | |||
| 190 | impl<'d, T: Instance, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, FLASH_SIZE> { | ||
| 191 | const READ_SIZE: usize = READ_SIZE; | ||
| 192 | |||
| 193 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||
| 194 | self.read(offset, bytes) | ||
| 195 | } | ||
| 196 | |||
| 197 | fn capacity(&self) -> usize { | ||
| 198 | self.capacity() | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | impl<'d, T: Instance, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, FLASH_SIZE> {} | ||
| 203 | |||
| 204 | impl<'d, T: Instance, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, FLASH_SIZE> { | ||
| 205 | const WRITE_SIZE: usize = WRITE_SIZE; | ||
| 206 | |||
| 207 | const ERASE_SIZE: usize = ERASE_SIZE; | ||
| 208 | |||
| 209 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 210 | self.erase(from, to) | ||
| 211 | } | ||
| 212 | |||
| 213 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||
| 214 | self.write(offset, bytes) | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | #[allow(dead_code)] | ||
| 219 | mod ram_helpers { | ||
| 220 | use core::marker::PhantomData; | ||
| 221 | |||
| 222 | use crate::rom_data; | ||
| 223 | |||
| 224 | #[repr(C)] | ||
| 225 | struct FlashFunctionPointers<'a> { | ||
| 226 | connect_internal_flash: unsafe extern "C" fn() -> (), | ||
| 227 | flash_exit_xip: unsafe extern "C" fn() -> (), | ||
| 228 | flash_range_erase: Option<unsafe extern "C" fn(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> ()>, | ||
| 229 | flash_range_program: Option<unsafe extern "C" fn(addr: u32, data: *const u8, count: usize) -> ()>, | ||
| 230 | flash_flush_cache: unsafe extern "C" fn() -> (), | ||
| 231 | flash_enter_cmd_xip: unsafe extern "C" fn() -> (), | ||
| 232 | phantom: PhantomData<&'a ()>, | ||
| 233 | } | ||
| 234 | |||
| 235 | #[allow(unused)] | ||
| 236 | fn flash_function_pointers(erase: bool, write: bool) -> FlashFunctionPointers<'static> { | ||
| 237 | FlashFunctionPointers { | ||
| 238 | connect_internal_flash: rom_data::connect_internal_flash::ptr(), | ||
| 239 | flash_exit_xip: rom_data::flash_exit_xip::ptr(), | ||
| 240 | flash_range_erase: if erase { | ||
| 241 | Some(rom_data::flash_range_erase::ptr()) | ||
| 242 | } else { | ||
| 243 | None | ||
| 244 | }, | ||
| 245 | flash_range_program: if write { | ||
| 246 | Some(rom_data::flash_range_program::ptr()) | ||
| 247 | } else { | ||
| 248 | None | ||
| 249 | }, | ||
| 250 | flash_flush_cache: rom_data::flash_flush_cache::ptr(), | ||
| 251 | flash_enter_cmd_xip: rom_data::flash_enter_cmd_xip::ptr(), | ||
| 252 | phantom: PhantomData, | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | #[allow(unused)] | ||
| 257 | /// # Safety | ||
| 258 | /// | ||
| 259 | /// `boot2` must contain a valid 2nd stage boot loader which can be called to re-initialize XIP mode | ||
| 260 | unsafe fn flash_function_pointers_with_boot2(erase: bool, write: bool, boot2: &[u32; 64]) -> FlashFunctionPointers { | ||
| 261 | let boot2_fn_ptr = (boot2 as *const u32 as *const u8).offset(1); | ||
| 262 | let boot2_fn: unsafe extern "C" fn() -> () = core::mem::transmute(boot2_fn_ptr); | ||
| 263 | FlashFunctionPointers { | ||
| 264 | connect_internal_flash: rom_data::connect_internal_flash::ptr(), | ||
| 265 | flash_exit_xip: rom_data::flash_exit_xip::ptr(), | ||
| 266 | flash_range_erase: if erase { | ||
| 267 | Some(rom_data::flash_range_erase::ptr()) | ||
| 268 | } else { | ||
| 269 | None | ||
| 270 | }, | ||
| 271 | flash_range_program: if write { | ||
| 272 | Some(rom_data::flash_range_program::ptr()) | ||
| 273 | } else { | ||
| 274 | None | ||
| 275 | }, | ||
| 276 | flash_flush_cache: rom_data::flash_flush_cache::ptr(), | ||
| 277 | flash_enter_cmd_xip: boot2_fn, | ||
| 278 | phantom: PhantomData, | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Erase a flash range starting at `addr` with length `len`. | ||
| 283 | /// | ||
| 284 | /// `addr` and `len` must be multiples of 4096 | ||
| 285 | /// | ||
| 286 | /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader | ||
| 287 | /// is used to re-initialize the XIP engine after flashing. | ||
| 288 | /// | ||
| 289 | /// # Safety | ||
| 290 | /// | ||
| 291 | /// Nothing must access flash while this is running. | ||
| 292 | /// Usually this means: | ||
| 293 | /// - interrupts must be disabled | ||
| 294 | /// - 2nd core must be running code from RAM or ROM with interrupts disabled | ||
| 295 | /// - DMA must not access flash memory | ||
| 296 | /// | ||
| 297 | /// `addr` and `len` parameters must be valid and are not checked. | ||
| 298 | pub unsafe fn flash_range_erase(addr: u32, len: u32, use_boot2: bool) { | ||
| 299 | let mut boot2 = [0u32; 256 / 4]; | ||
| 300 | let ptrs = if use_boot2 { | ||
| 301 | rom_data::memcpy44(&mut boot2 as *mut _, super::FLASH_BASE as *const _, 256); | ||
| 302 | flash_function_pointers_with_boot2(true, false, &boot2) | ||
| 303 | } else { | ||
| 304 | flash_function_pointers(true, false) | ||
| 305 | }; | ||
| 306 | |||
| 307 | core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); | ||
| 308 | |||
| 309 | write_flash_inner(addr, len, None, &ptrs as *const FlashFunctionPointers); | ||
| 310 | } | ||
| 311 | |||
| 312 | /// Erase and rewrite a flash range starting at `addr` with data `data`. | ||
| 313 | /// | ||
| 314 | /// `addr` and `data.len()` must be multiples of 4096 | ||
| 315 | /// | ||
| 316 | /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader | ||
| 317 | /// is used to re-initialize the XIP engine after flashing. | ||
| 318 | /// | ||
| 319 | /// # Safety | ||
| 320 | /// | ||
| 321 | /// Nothing must access flash while this is running. | ||
| 322 | /// Usually this means: | ||
| 323 | /// - interrupts must be disabled | ||
| 324 | /// - 2nd core must be running code from RAM or ROM with interrupts disabled | ||
| 325 | /// - DMA must not access flash memory | ||
| 326 | /// | ||
| 327 | /// `addr` and `len` parameters must be valid and are not checked. | ||
| 328 | pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8], use_boot2: bool) { | ||
| 329 | let mut boot2 = [0u32; 256 / 4]; | ||
| 330 | let ptrs = if use_boot2 { | ||
| 331 | rom_data::memcpy44(&mut boot2 as *mut _, super::FLASH_BASE as *const _, 256); | ||
| 332 | flash_function_pointers_with_boot2(true, true, &boot2) | ||
| 333 | } else { | ||
| 334 | flash_function_pointers(true, true) | ||
| 335 | }; | ||
| 336 | |||
| 337 | core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); | ||
| 338 | |||
| 339 | write_flash_inner( | ||
| 340 | addr, | ||
| 341 | data.len() as u32, | ||
| 342 | Some(data), | ||
| 343 | &ptrs as *const FlashFunctionPointers, | ||
| 344 | ); | ||
| 345 | } | ||
| 346 | |||
| 347 | /// Write a flash range starting at `addr` with data `data`. | ||
| 348 | /// | ||
| 349 | /// `addr` and `data.len()` must be multiples of 256 | ||
| 350 | /// | ||
| 351 | /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader | ||
| 352 | /// is used to re-initialize the XIP engine after flashing. | ||
| 353 | /// | ||
| 354 | /// # Safety | ||
| 355 | /// | ||
| 356 | /// Nothing must access flash while this is running. | ||
| 357 | /// Usually this means: | ||
| 358 | /// - interrupts must be disabled | ||
| 359 | /// - 2nd core must be running code from RAM or ROM with interrupts disabled | ||
| 360 | /// - DMA must not access flash memory | ||
| 361 | /// | ||
| 362 | /// `addr` and `len` parameters must be valid and are not checked. | ||
| 363 | pub unsafe fn flash_range_program(addr: u32, data: &[u8], use_boot2: bool) { | ||
| 364 | let mut boot2 = [0u32; 256 / 4]; | ||
| 365 | let ptrs = if use_boot2 { | ||
| 366 | rom_data::memcpy44(&mut boot2 as *mut _, super::FLASH_BASE as *const _, 256); | ||
| 367 | flash_function_pointers_with_boot2(false, true, &boot2) | ||
| 368 | } else { | ||
| 369 | flash_function_pointers(false, true) | ||
| 370 | }; | ||
| 371 | |||
| 372 | core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); | ||
| 373 | |||
| 374 | write_flash_inner( | ||
| 375 | addr, | ||
| 376 | data.len() as u32, | ||
| 377 | Some(data), | ||
| 378 | &ptrs as *const FlashFunctionPointers, | ||
| 379 | ); | ||
| 380 | } | ||
| 381 | |||
| 382 | /// # Safety | ||
| 383 | /// | ||
| 384 | /// Nothing must access flash while this is running. | ||
| 385 | /// Usually this means: | ||
| 386 | /// - interrupts must be disabled | ||
| 387 | /// - 2nd core must be running code from RAM or ROM with interrupts disabled | ||
| 388 | /// - DMA must not access flash memory | ||
| 389 | /// Length of data must be a multiple of 4096 | ||
| 390 | /// addr must be aligned to 4096 | ||
| 391 | #[inline(never)] | ||
| 392 | #[link_section = ".data.ram_func"] | ||
| 393 | unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { | ||
| 394 | /* | ||
| 395 | Should be equivalent to: | ||
| 396 | rom_data::connect_internal_flash(); | ||
| 397 | rom_data::flash_exit_xip(); | ||
| 398 | rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected | ||
| 399 | rom_data::flash_range_program(addr, data as *const _, len); // if selected | ||
| 400 | rom_data::flash_flush_cache(); | ||
| 401 | rom_data::flash_enter_cmd_xip(); | ||
| 402 | */ | ||
| 403 | #[cfg(target_arch = "arm")] | ||
| 404 | core::arch::asm!( | ||
| 405 | "mov r8, r0", | ||
| 406 | "mov r9, r2", | ||
| 407 | "mov r10, r1", | ||
| 408 | "ldr r4, [{ptrs}, #0]", | ||
| 409 | "blx r4", // connect_internal_flash() | ||
| 410 | |||
| 411 | "ldr r4, [{ptrs}, #4]", | ||
| 412 | "blx r4", // flash_exit_xip() | ||
| 413 | |||
| 414 | "mov r0, r8", // r0 = addr | ||
| 415 | "mov r1, r10", // r1 = len | ||
| 416 | "movs r2, #1", | ||
| 417 | "lsls r2, r2, #31", // r2 = 1 << 31 | ||
| 418 | "movs r3, #0", // r3 = 0 | ||
| 419 | "ldr r4, [{ptrs}, #8]", | ||
| 420 | "cmp r4, #0", | ||
| 421 | "beq 1f", | ||
| 422 | "blx r4", // flash_range_erase(addr, len, 1 << 31, 0) | ||
| 423 | "1:", | ||
| 424 | |||
| 425 | "mov r0, r8", // r0 = addr | ||
| 426 | "mov r1, r9", // r0 = data | ||
| 427 | "mov r2, r10", // r2 = len | ||
| 428 | "ldr r4, [{ptrs}, #12]", | ||
| 429 | "cmp r4, #0", | ||
| 430 | "beq 1f", | ||
| 431 | "blx r4", // flash_range_program(addr, data, len); | ||
| 432 | "1:", | ||
| 433 | |||
| 434 | "ldr r4, [{ptrs}, #16]", | ||
| 435 | "blx r4", // flash_flush_cache(); | ||
| 436 | |||
| 437 | "ldr r4, [{ptrs}, #20]", | ||
| 438 | "blx r4", // flash_enter_cmd_xip(); | ||
| 439 | ptrs = in(reg) ptrs, | ||
| 440 | // Registers r8-r15 are not allocated automatically, | ||
| 441 | // so assign them manually. We need to use them as | ||
| 442 | // otherwise there are not enough registers available. | ||
| 443 | in("r0") addr, | ||
| 444 | in("r2") data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()), | ||
| 445 | in("r1") len, | ||
| 446 | out("r3") _, | ||
| 447 | out("r4") _, | ||
| 448 | lateout("r8") _, | ||
| 449 | lateout("r9") _, | ||
| 450 | lateout("r10") _, | ||
| 451 | clobber_abi("C"), | ||
| 452 | ); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | |||
| 456 | mod sealed { | ||
| 457 | pub trait Instance {} | ||
| 458 | } | ||
| 459 | |||
| 460 | pub trait Instance: sealed::Instance {} | ||
| 461 | |||
| 462 | impl sealed::Instance for FLASH {} | ||
| 463 | impl Instance for FLASH {} | ||
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index e784399d4..f608f1768 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -20,6 +20,7 @@ pub mod uart; | |||
| 20 | pub mod usb; | 20 | pub mod usb; |
| 21 | 21 | ||
| 22 | mod clocks; | 22 | mod clocks; |
| 23 | pub mod flash; | ||
| 23 | mod reset; | 24 | mod reset; |
| 24 | 25 | ||
| 25 | // Reexports | 26 | // Reexports |
| @@ -95,6 +96,8 @@ embassy_hal_common::peripherals! { | |||
| 95 | USB, | 96 | USB, |
| 96 | 97 | ||
| 97 | RTC, | 98 | RTC, |
| 99 | |||
| 100 | FLASH, | ||
| 98 | } | 101 | } |
| 99 | 102 | ||
| 100 | #[link_section = ".boot2"] | 103 | #[link_section = ".boot2"] |
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index ec9896b77..31f688305 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml | |||
| @@ -30,6 +30,7 @@ byte-slice-cast = { version = "1.2.0", default-features = false } | |||
| 30 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } | 30 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } |
| 31 | embedded-hal-async = { version = "0.1.0-alpha.3" } | 31 | embedded-hal-async = { version = "0.1.0-alpha.3" } |
| 32 | embedded-io = { version = "0.3.1", features = ["async", "defmt"] } | 32 | embedded-io = { version = "0.3.1", features = ["async", "defmt"] } |
| 33 | embedded-storage = { version = "0.3" } | ||
| 33 | static_cell = "1.0.0" | 34 | static_cell = "1.0.0" |
| 34 | 35 | ||
| 35 | [profile.release] | 36 | [profile.release] |
diff --git a/examples/rp/src/bin/flash.rs b/examples/rp/src/bin/flash.rs new file mode 100644 index 000000000..8d6b379f4 --- /dev/null +++ b/examples/rp/src/bin/flash.rs | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; | ||
| 8 | use embassy_rp::peripherals::FLASH; | ||
| 9 | use embassy_time::{Duration, Timer}; | ||
| 10 | use {defmt_rtt as _, panic_probe as _}; | ||
| 11 | |||
| 12 | const ADDR_OFFSET: u32 = 0x100000; | ||
| 13 | const FLASH_SIZE: usize = 2 * 1024 * 1024; | ||
| 14 | |||
| 15 | #[embassy_executor::main] | ||
| 16 | async fn main(_spawner: Spawner) { | ||
| 17 | let p = embassy_rp::init(Default::default()); | ||
| 18 | info!("Hello World!"); | ||
| 19 | |||
| 20 | // add some delay to give an attached debug probe time to parse the | ||
| 21 | // defmt RTT header. Reading that header might touch flash memory, which | ||
| 22 | // interferes with flash write operations. | ||
| 23 | // https://github.com/knurling-rs/defmt/pull/683 | ||
| 24 | Timer::after(Duration::from_millis(10)).await; | ||
| 25 | |||
| 26 | let mut flash = embassy_rp::flash::Flash::<_, FLASH_SIZE>::new(p.FLASH); | ||
| 27 | erase_write_sector(&mut flash, 0x00); | ||
| 28 | |||
| 29 | multiwrite_bytes(&mut flash, ERASE_SIZE as u32); | ||
| 30 | |||
| 31 | loop {} | ||
| 32 | } | ||
| 33 | |||
| 34 | fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { | ||
| 35 | info!(">>>> [multiwrite_bytes]"); | ||
| 36 | let mut read_buf = [0u8; ERASE_SIZE]; | ||
| 37 | defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); | ||
| 38 | |||
| 39 | info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); | ||
| 40 | info!("Contents start with {=[u8]}", read_buf[0..4]); | ||
| 41 | |||
| 42 | defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); | ||
| 43 | |||
| 44 | defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); | ||
| 45 | info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); | ||
| 46 | if read_buf.iter().any(|x| *x != 0xFF) { | ||
| 47 | defmt::panic!("unexpected"); | ||
| 48 | } | ||
| 49 | |||
| 50 | defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &[0x01])); | ||
| 51 | defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 1, &[0x02])); | ||
| 52 | defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 2, &[0x03])); | ||
| 53 | defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 3, &[0x04])); | ||
| 54 | |||
| 55 | defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); | ||
| 56 | info!("Contents after write starts with {=[u8]}", read_buf[0..4]); | ||
| 57 | if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { | ||
| 58 | defmt::panic!("unexpected"); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { | ||
| 63 | info!(">>>> [erase_write_sector]"); | ||
| 64 | let mut buf = [0u8; ERASE_SIZE]; | ||
| 65 | defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); | ||
| 66 | |||
| 67 | info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); | ||
| 68 | info!("Contents start with {=[u8]}", buf[0..4]); | ||
| 69 | |||
| 70 | defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); | ||
| 71 | |||
| 72 | defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); | ||
| 73 | info!("Contents after erase starts with {=[u8]}", buf[0..4]); | ||
| 74 | if buf.iter().any(|x| *x != 0xFF) { | ||
| 75 | defmt::panic!("unexpected"); | ||
| 76 | } | ||
| 77 | |||
| 78 | for b in buf.iter_mut() { | ||
| 79 | *b = 0xDA; | ||
| 80 | } | ||
| 81 | |||
| 82 | defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &buf)); | ||
| 83 | |||
| 84 | defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); | ||
| 85 | info!("Contents after write starts with {=[u8]}", buf[0..4]); | ||
| 86 | if buf.iter().any(|x| *x != 0xDA) { | ||
| 87 | defmt::panic!("unexpected"); | ||
| 88 | } | ||
| 89 | } | ||
diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index e1a6dce41..069b7fb8c 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml | |||
| @@ -22,6 +22,7 @@ embedded-hal-async = { version = "=0.1.0-alpha.3" } | |||
| 22 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } | 22 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } |
| 23 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 23 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 24 | embedded-io = { version = "0.3.1", features = ["async"] } | 24 | embedded-io = { version = "0.3.1", features = ["async"] } |
| 25 | embedded-storage = { version = "0.3" } | ||
| 25 | 26 | ||
| 26 | [profile.dev] | 27 | [profile.dev] |
| 27 | debug = 2 | 28 | debug = 2 |
diff --git a/tests/rp/src/bin/flash.rs b/tests/rp/src/bin/flash.rs new file mode 100644 index 000000000..897e3804f --- /dev/null +++ b/tests/rp/src/bin/flash.rs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; | ||
| 8 | use embassy_time::{Duration, Timer}; | ||
| 9 | use {defmt_rtt as _, panic_probe as _}; | ||
| 10 | |||
| 11 | const ADDR_OFFSET: u32 = 0x4000; | ||
| 12 | |||
| 13 | #[embassy_executor::main] | ||
| 14 | async fn main(_spawner: Spawner) { | ||
| 15 | let p = embassy_rp::init(Default::default()); | ||
| 16 | info!("Hello World!"); | ||
| 17 | |||
| 18 | // add some delay to give an attached debug probe time to parse the | ||
| 19 | // defmt RTT header. Reading that header might touch flash memory, which | ||
| 20 | // interferes with flash write operations. | ||
| 21 | // https://github.com/knurling-rs/defmt/pull/683 | ||
| 22 | Timer::after(Duration::from_millis(10)).await; | ||
| 23 | |||
| 24 | let mut flash = embassy_rp::flash::Flash::<_, { 2 * 1024 * 1024 }>::new(p.FLASH); | ||
| 25 | |||
| 26 | let mut buf = [0u8; ERASE_SIZE]; | ||
| 27 | defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); | ||
| 28 | |||
| 29 | info!("Addr of flash block is {:x}", ADDR_OFFSET + FLASH_BASE as u32); | ||
| 30 | info!("Contents start with {=[u8]}", buf[0..4]); | ||
| 31 | |||
| 32 | defmt::unwrap!(flash.erase(ADDR_OFFSET, ADDR_OFFSET + ERASE_SIZE as u32)); | ||
| 33 | |||
| 34 | defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); | ||
| 35 | info!("Contents after erase starts with {=[u8]}", buf[0..4]); | ||
| 36 | if buf.iter().any(|x| *x != 0xFF) { | ||
| 37 | defmt::panic!("unexpected"); | ||
| 38 | } | ||
| 39 | |||
| 40 | for b in buf.iter_mut() { | ||
| 41 | *b = 0xDA; | ||
| 42 | } | ||
| 43 | |||
| 44 | defmt::unwrap!(flash.write(ADDR_OFFSET, &mut buf)); | ||
| 45 | |||
| 46 | defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); | ||
| 47 | info!("Contents after write starts with {=[u8]}", buf[0..4]); | ||
| 48 | if buf.iter().any(|x| *x != 0xDA) { | ||
| 49 | defmt::panic!("unexpected"); | ||
| 50 | } | ||
| 51 | |||
| 52 | info!("Test OK"); | ||
| 53 | cortex_m::asm::bkpt(); | ||
| 54 | } | ||
