rp2040_flash/
lib.rs

1#![no_std]
2
3pub mod flash {
4    use core::marker::PhantomData;
5    use rp2040_hal::rom_data;
6
7    #[repr(C)]
8    struct FlashFunctionPointers<'a> {
9        connect_internal_flash: unsafe extern "C" fn() -> (),
10        flash_exit_xip: unsafe extern "C" fn() -> (),
11        flash_range_erase: Option<
12            unsafe extern "C" fn(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> (),
13        >,
14        flash_range_program:
15            Option<unsafe extern "C" fn(addr: u32, data: *const u8, count: usize) -> ()>,
16        flash_flush_cache: unsafe extern "C" fn() -> (),
17        flash_enter_cmd_xip: unsafe extern "C" fn() -> (),
18        phantom: PhantomData<&'a ()>,
19    }
20
21    #[allow(unused)]
22    fn flash_function_pointers(erase: bool, write: bool) -> FlashFunctionPointers<'static> {
23        FlashFunctionPointers {
24            connect_internal_flash: rom_data::connect_internal_flash::ptr(),
25            flash_exit_xip: rom_data::flash_exit_xip::ptr(),
26            flash_range_erase: if erase {
27                Some(rom_data::flash_range_erase::ptr())
28            } else {
29                None
30            },
31            flash_range_program: if write {
32                Some(rom_data::flash_range_program::ptr())
33            } else {
34                None
35            },
36            flash_flush_cache: rom_data::flash_flush_cache::ptr(),
37            flash_enter_cmd_xip: rom_data::flash_enter_cmd_xip::ptr(),
38            phantom: PhantomData,
39        }
40    }
41
42    #[allow(unused)]
43    /// # Safety
44    ///
45    /// `boot2` must contain a valid 2nd stage boot loader which can be called to re-initialize XIP mode
46    unsafe fn flash_function_pointers_with_boot2(
47        erase: bool,
48        write: bool,
49        boot2: &[u32; 64],
50    ) -> FlashFunctionPointers {
51        let boot2_fn_ptr = (boot2 as *const u32 as *const u8).offset(1);
52        let boot2_fn: unsafe extern "C" fn() -> () = core::mem::transmute(boot2_fn_ptr);
53        FlashFunctionPointers {
54            connect_internal_flash: rom_data::connect_internal_flash::ptr(),
55            flash_exit_xip: rom_data::flash_exit_xip::ptr(),
56            flash_range_erase: if erase {
57                Some(rom_data::flash_range_erase::ptr())
58            } else {
59                None
60            },
61            flash_range_program: if write {
62                Some(rom_data::flash_range_program::ptr())
63            } else {
64                None
65            },
66            flash_flush_cache: rom_data::flash_flush_cache::ptr(),
67            flash_enter_cmd_xip: boot2_fn,
68            phantom: PhantomData,
69        }
70    }
71
72    /// Erase a flash range starting at `addr` with length `len`.
73    ///
74    /// `addr` and `len` must be multiples of 4096.
75    ///
76    /// `addr` is relative to the beginning of the flash area,
77    /// and must be smaller than 0x01000000.
78    ///
79    /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader
80    /// is used to re-initialize the XIP engine after flashing.
81    ///
82    /// # Safety
83    ///
84    /// Nothing must access flash while this is running.
85    /// Usually this means:
86    ///   - interrupts must be disabled
87    ///   - 2nd core must be running code from RAM or ROM with interrupts disabled
88    ///   - DMA must not access flash memory
89    ///
90    /// `addr` and `len` parameters must be valid and are not checked.
91    pub unsafe fn flash_range_erase(addr: u32, len: u32, use_boot2: bool) {
92        assert!(addr < 0x1000000);
93        let mut boot2 = [0u32; 256 / 4];
94        let ptrs = if use_boot2 {
95            rom_data::memcpy44(&mut boot2 as *mut _, 0x10000000 as *const _, 256);
96            flash_function_pointers_with_boot2(true, false, &boot2)
97        } else {
98            flash_function_pointers(true, false)
99        };
100        write_flash_inner(addr, len, None, &ptrs as *const FlashFunctionPointers);
101    }
102
103    /// Erase and rewrite a flash range starting at `addr` with data `data`.
104    ///
105    /// `addr` and `data.len()` must be multiples of 4096.
106    ///
107    /// `addr` is relative to the beginning of the flash area,
108    /// and must be smaller than 0x01000000.
109    ///
110    /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader
111    /// is used to re-initialize the XIP engine after flashing.
112    ///
113    /// # Safety
114    ///
115    /// Nothing must access flash while this is running.
116    /// Usually this means:
117    ///   - interrupts must be disabled
118    ///   - 2nd core must be running code from RAM or ROM with interrupts disabled
119    ///   - DMA must not access flash memory
120    ///
121    /// `addr` and `len` parameters must be valid and are not checked.
122    pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8], use_boot2: bool) {
123        assert!(addr < 0x1000000);
124        let mut boot2 = [0u32; 256 / 4];
125        let ptrs = if use_boot2 {
126            rom_data::memcpy44(&mut boot2 as *mut _, 0x10000000 as *const _, 256);
127            flash_function_pointers_with_boot2(true, true, &boot2)
128        } else {
129            flash_function_pointers(true, true)
130        };
131        write_flash_inner(
132            addr,
133            data.len() as u32,
134            Some(data),
135            &ptrs as *const FlashFunctionPointers,
136        );
137    }
138
139    /// Write a flash range starting at `addr` with data `data`.
140    ///
141    /// `addr` and `data.len()` must be multiples of 256.
142    ///
143    /// `addr` is relative to the beginning of the flash area,
144    /// and must be smaller than 0x01000000.
145    ///
146    /// If `use_boot2` is `true`, a copy of the 2nd stage boot loader
147    /// is used to re-initialize the XIP engine after flashing.
148    ///
149    /// # Safety
150    ///
151    /// Nothing must access flash while this is running.
152    /// Usually this means:
153    ///   - interrupts must be disabled
154    ///   - 2nd core must be running code from RAM or ROM with interrupts disabled
155    ///   - DMA must not access flash memory
156    ///
157    /// `addr` and `len` parameters must be valid and are not checked.
158    pub unsafe fn flash_range_program(addr: u32, data: &[u8], use_boot2: bool) {
159        assert!(addr < 0x1000000);
160        let mut boot2 = [0u32; 256 / 4];
161        let ptrs = if use_boot2 {
162            rom_data::memcpy44(&mut boot2 as *mut _, 0x10000000 as *const _, 256);
163            flash_function_pointers_with_boot2(false, true, &boot2)
164        } else {
165            flash_function_pointers(false, true)
166        };
167        write_flash_inner(
168            addr,
169            data.len() as u32,
170            Some(data),
171            &ptrs as *const FlashFunctionPointers,
172        );
173    }
174
175    /// # Safety
176    ///
177    /// Nothing must access flash while this is running.
178    /// Usually this means:
179    ///   - interrupts must be disabled
180    ///   - 2nd core must be running code from RAM or ROM with interrupts disabled
181    ///   - DMA must not access flash memory
182    ///
183    /// Length of data must be a multiple of 4096
184    /// addr must be aligned to 4096
185    #[inline(never)]
186    #[link_section = ".data.ram_func"]
187    unsafe fn write_flash_inner(
188        addr: u32,
189        len: u32,
190        data: Option<&[u8]>,
191        ptrs: *const FlashFunctionPointers,
192    ) {
193        /*
194         Should be equivalent to:
195            rom_data::connect_internal_flash();
196            rom_data::flash_exit_xip();
197            rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected
198            rom_data::flash_range_program(addr, data as *const _, len); // if selected
199            rom_data::flash_flush_cache();
200            rom_data::flash_enter_cmd_xip();
201        */
202        core::arch::asm!(
203            "mov r8, r0",
204            "mov r9, r2",
205            "mov r10, r1",
206            "ldr r4, [{ptrs}, #0]",
207            "blx r4", // connect_internal_flash()
208
209            "ldr r4, [{ptrs}, #4]",
210            "blx r4", // flash_exit_xip()
211
212            "mov r0, r8", // r0 = addr
213            "mov r1, r10", // r1 = len
214            "movs r2, #1",
215            "lsls r2, r2, #31", // r2 = 1 << 31
216            "movs r3, #0", // r3 = 0
217            "ldr r4, [{ptrs}, #8]",
218            "cmp r4, #0",
219            "beq 1f",
220            "blx r4", // flash_range_erase(addr, len, 1 << 31, 0)
221            "1:",
222
223            "mov r0, r8", // r0 = addr
224            "mov r1, r9", // r0 = data
225            "mov r2, r10", // r2 = len
226            "ldr r4, [{ptrs}, #12]",
227            "cmp r4, #0",
228            "beq 1f",
229            "blx r4", // flash_range_program(addr, data, len);
230            "1:",
231
232            "ldr r4, [{ptrs}, #16]",
233            "blx r4", // flash_flush_cache();
234
235            "ldr r4, [{ptrs}, #20]",
236            "blx r4", // flash_enter_cmd_xip();
237            ptrs = in(reg) ptrs,
238            in("r0") addr,
239            in("r2") data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()),
240            in("r1") len,
241            out("r3") _,
242            out("r4") _,
243            // Registers r8-r10 are used to store values
244            // from r0-r2 in registers not clobbered by
245            // function calls.
246            // The values can't be passed in using r8-r10 directly
247            // due to https://github.com/rust-lang/rust/issues/99071
248            out("r8") _,
249            out("r9") _,
250            out("r10") _,
251            clobber_abi("C"),
252        );
253    }
254
255    #[repr(C)]
256    struct FlashCommand {
257        cmd_addr: *const u8,
258        cmd_addr_len: u32,
259        dummy_len: u32,
260        data: *mut u8,
261        data_len: u32,
262    }
263
264    /// Return SPI flash unique ID
265    ///
266    /// Not all SPI flashes implement this command, so check the JEDEC
267    /// ID before relying on it. The Winbond parts commonly seen on
268    /// RP2040 devboards (JEDEC=0xEF7015) support an 8-byte unique ID;
269    /// https://forums.raspberrypi.com/viewtopic.php?t=331949 suggests
270    /// that LCSC (Zetta) parts have a 16-byte unique ID (which is
271    /// *not* unique in just its first 8 bytes),
272    /// JEDEC=0xBA6015. Macronix and Spansion parts do not have a
273    /// unique ID.
274    ///
275    /// The returned bytes are relatively predictable and should be
276    /// salted and hashed before use if that is an issue (e.g. for MAC
277    /// addresses).
278    ///
279    /// # Safety
280    ///
281    /// Nothing must access flash while this is running.
282    /// Usually this means:
283    ///   - interrupts must be disabled
284    ///   - 2nd core must be running code from RAM or ROM with interrupts disabled
285    ///   - DMA must not access flash memory
286    pub unsafe fn flash_unique_id(out: &mut [u8], use_boot2: bool) {
287        let mut boot2 = [0u32; 256 / 4];
288        let ptrs = if use_boot2 {
289            rom_data::memcpy44(&mut boot2 as *mut _, 0x10000000 as *const _, 256);
290            flash_function_pointers_with_boot2(false, false, &boot2)
291        } else {
292            flash_function_pointers(false, false)
293        };
294        // 4B - read unique ID
295        let cmd = [0x4B];
296        read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers);
297    }
298
299    /// Return SPI flash JEDEC ID
300    ///
301    /// This is the three-byte manufacturer-and-model identifier
302    /// commonly used to check before using manufacturer-specific SPI
303    /// flash features, e.g. 0xEF7015 for Winbond W25Q16JV.
304    ///
305    /// # Safety
306    ///
307    /// Nothing must access flash while this is running.
308    /// Usually this means:
309    ///   - interrupts must be disabled
310    ///   - 2nd core must be running code from RAM or ROM with interrupts disabled
311    ///   - DMA must not access flash memory
312    pub unsafe fn flash_jedec_id(use_boot2: bool) -> u32 {
313        let mut boot2 = [0u32; 256 / 4];
314        let ptrs = if use_boot2 {
315            rom_data::memcpy44(&mut boot2 as *mut _, 0x10000000 as *const _, 256);
316            flash_function_pointers_with_boot2(false, false, &boot2)
317        } else {
318            flash_function_pointers(false, false)
319        };
320        let mut id = [0u8; 4];
321        // 9F - read JEDEC ID
322        let cmd = [0x9F];
323        read_flash(
324            &cmd[..],
325            0,
326            &mut id[1..4],
327            &ptrs as *const FlashFunctionPointers,
328        );
329        u32::from_be_bytes(id)
330    }
331
332    unsafe fn read_flash(
333        cmd_addr: &[u8],
334        dummy_len: u32,
335        out: &mut [u8],
336        ptrs: *const FlashFunctionPointers,
337    ) {
338        read_flash_inner(
339            FlashCommand {
340                cmd_addr: cmd_addr.as_ptr(),
341                cmd_addr_len: cmd_addr.len() as u32,
342                dummy_len,
343                data: out.as_mut_ptr(),
344                data_len: out.len() as u32,
345            },
346            ptrs,
347        );
348    }
349
350    /// Issue a generic SPI flash read command
351    ///
352    /// # Arguments
353    ///
354    /// * `cmd` - `FlashCommand` structure
355    /// * `ptrs` - Flash function pointers as per `write_flash_inner`
356    #[inline(never)]
357    #[link_section = ".data.ram_func"]
358    unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) {
359        core::arch::asm!(
360            // r6, r7 are LLVM-reserved and can't be marked as a clobber, so save/restore them manually
361            // (r6 is not actually used, but we need to push two words to maintain stack alignment)
362            "push {{r6, r7}}",
363
364            "mov r7, r0", // cmd
365            "mov r5, r1", // ptrs
366
367            "ldr r4, [r5, #0]",
368            "blx r4", // connect_internal_flash()
369
370            "ldr r4, [r5, #4]",
371            "blx r4", // flash_exit_xip()
372
373            "movs r4, #0x18",
374            "lsls r4, r4, #24", // 0x18000000, SSI, RP2040 datasheet 4.10.13
375
376            // Disable, write 0 to SSIENR
377            "movs r0, #0",
378            "str r0, [r4, #8]", // SSIENR
379
380            // Write ctrlr0
381            "movs r0, #0x3",
382            "lsls r0, r0, #8", // TMOD=0x300
383            "ldr r1, [r4, #0]", // CTRLR0
384            "orrs r1, r0",
385            "str r1, [r4, #0]",
386
387            // Write ctrlr1 with len-1
388            "ldr r0, [r7, #8]", // dummy_len
389            "ldr r1, [r7, #16]", // data_len
390            "add r0, r1",
391            "subs r0, #1",
392            "str r0, [r4, #0x04]", // CTRLR1
393
394            // Enable, write 1 to ssienr
395            "movs r0, #1",
396            "str r0, [r4, #8]", // SSIENR
397
398            // Write cmd/addr phase to DR
399            "mov r2, r4",
400            "adds r2, 0x60", // &DR
401            "ldr r0, [r7, #0]", // cmd_addr
402            "ldr r1, [r7, #4]", // cmd_addr_len
403            "10:",
404            "ldrb r3, [r0]",
405            "strb r3, [r2]", // DR
406            "adds r0, #1",
407            "subs r1, #1",
408            "bne 10b",
409
410            // Skip any dummy cycles
411            "ldr r1, [r7, #8]", // dummy_len
412            "cmp r1, #0",
413            "beq 9f",
414            "4:",
415            "ldr r3, [r4, #0x28]", // SR
416            "movs r2, #0x8",
417            "tst r3, r2", // SR.RFNE
418            "beq 4b",
419
420            "mov r2, r4",
421            "adds r2, 0x60", // &DR
422            "ldrb r3, [r2]", // DR
423            "subs r1, #1",
424            "bne 4b",
425
426            // Read RX fifo
427            "9:",
428            "ldr r0, [r7, #12]", // data
429            "ldr r1, [r7, #16]", // data_len
430
431            "2:",
432            "ldr r3, [r4, #0x28]", // SR
433            "movs r2, #0x8",
434            "tst r3, r2", // SR.RFNE
435            "beq 2b",
436
437            "mov r2, r4",
438            "adds r2, 0x60", // &DR
439            "ldrb r3, [r2]", // DR
440            "strb r3, [r0]",
441            "adds r0, #1",
442            "subs r1, #1",
443            "bne 2b",
444
445            // Disable, write 0 to ssienr
446            "movs r0, #0",
447            "str r0, [r4, #8]", // SSIENR
448
449            // Write 0 to CTRLR1 (returning to its default value)
450            //
451            // flash_enter_cmd_xip does NOT do this, and everything goes
452            // wrong unless we do it here
453            "str r0, [r4, #4]", // CTRLR1
454
455            "ldr r4, [r5, #20]",
456            "blx r4", // flash_enter_cmd_xip();
457
458            "pop {{r6, r7}}",
459
460            in("r0") &cmd as *const FlashCommand,
461            in("r1") ptrs,
462            out("r2") _,
463            out("r3") _,
464            out("r4") _,
465            out("r5") _,
466            clobber_abi("C"),
467        );
468    }
469}