rp2040_panic_usb_boot/
lib.rs

1#![no_std]
2
3use core::fmt::Write;
4use core::panic::PanicInfo;
5
6struct Cursor<'a> {
7    buf: &'a mut [u8],
8    pos: usize,
9}
10
11impl<'a> Cursor<'a> {
12    fn new(buf: &'a mut [u8]) -> Cursor<'a> {
13        Cursor { buf, pos: 0 }
14    }
15}
16
17impl core::fmt::Write for Cursor<'_> {
18    fn write_str(&mut self, s: &str) -> core::fmt::Result {
19        let len = s.len();
20        if len < self.buf.len() - self.pos {
21            self.buf[self.pos..self.pos + len].clone_from_slice(s.as_bytes());
22            self.pos += len;
23            Ok(())
24        } else {
25            Err(core::fmt::Error)
26        }
27    }
28}
29
30#[inline(never)]
31#[panic_handler]
32fn panic(info: &PanicInfo) -> ! {
33    cortex_m::interrupt::disable();
34    // disable XIP cache so cache ram becomes usable
35    disable_xip_cache();
36
37    // write panic message to XIP RAM
38    let buf: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(0x15000000 as *mut u8, 0x4000) };
39    let written = {
40        let mut cur = Cursor::new(buf);
41        write!(&mut cur, "{}\n\0", info).ok();
42        cur.pos
43    };
44
45    // Clear the rest of XIP RAM so it's not full of garbage when dumped
46    buf[written..0x4000].fill(0);
47
48    // For usb_boot to work, XOSC needs to be running
49    if !xosc_is_running() {
50        xosc_start_delay((12_000 /*kHz*/ + 128) / 256);
51        xosc_enable(true);
52        while !(xosc_is_running()) {}
53    }
54
55    // jump to usb. the unwrap here should never occur unless ROM is faulty.
56    (ROMFuncs::load().unwrap().reset_to_usb_boot)(0, 0);
57    loop {}
58}
59
60// find_func and ROMFuncs impls borrowed from rp-rs/flash-algo
61// used here instead of rp2040-hal romfuncs to avoid coupling this crate to a specific hal
62fn find_func<T>(tag: [u8; 2]) -> Option<T> {
63    let tag = u16::from_le_bytes(tag) as u32;
64    type RomTableLookupFn = unsafe extern "C" fn(table: *const u16, code: u32) -> usize;
65    /// This location in flash holds a 16-bit truncated pointer for the ROM lookup function
66    const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _;
67    /// This location in flash holds a 16-bit truncated pointer for the ROM function table
68    /// (there's also a ROM data table which we don't need)
69    const FUNC_TABLE: *const u16 = 0x0000_0014 as _;
70    unsafe {
71        let lookup_func = ROM_TABLE_LOOKUP_PTR.read() as usize;
72        let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func);
73        let table = FUNC_TABLE.read() as usize;
74        let result = lookup_func(table as *const u16, tag);
75        if result == 0 {
76            return None;
77        }
78        Some(core::mem::transmute_copy(&result))
79    }
80}
81
82struct ROMFuncs {
83    reset_to_usb_boot: extern "C" fn(gpio_activity_pin_mask: u32, disable_interface_mask: u32),
84}
85
86impl ROMFuncs {
87    fn load() -> Option<Self> {
88        Some(ROMFuncs {
89            reset_to_usb_boot: find_func(*b"UB")?,
90        })
91    }
92}
93
94// implement basic register access layer to avoid depending on a PAC
95// we only need a few registers, it's not so bad to write it by hand
96
97struct Reg {
98    address: *mut u32,
99}
100
101impl Reg {
102    const fn new(address: u32) -> Self {
103        Self {
104            address: address as *mut u32,
105        }
106    }
107
108    fn read(&self) -> u32 {
109        unsafe { self.address.read_volatile() }
110    }
111
112    fn write(&self, value: u32) {
113        unsafe {
114            self.address.write_volatile(value);
115        }
116    }
117}
118
119/// XIP_CTRL
120///
121/// bit 3 POWER_DOWN - when 1, cache is powered down - it retains state but cannot be accessed.
122/// bit 1 ERR_BADWRITE - when 1, writes to any alias other than 0x0 will produce a bus fault
123/// bit 0 EN - when 1, enable the cache.
124const XIP_CTRL: Reg = Reg::new(0x1400_0000);
125
126/// XOSC_CTRL
127///
128/// 23:12 ENABLE - on powerup this field is initialsed to DISABLE and the chip runs from the ROSC
129///                Enumerated values: 0xd1e -> DISABLE, 0xfab -> ENABLE
130/// 11:0 FREQ_RANGE: Frequency range. This resets to 0xAA0 and cannot be changed.
131const XOSC_CTRL: Reg = Reg::new(0x4002_4000);
132
133/// XOSC: STATUS Register
134///
135/// 31:31 STABLE - Oscillator is running and stable
136/// 24:24 BADWRITE - An invalid value has been written to CTRL_ENABLE
137/// 12:12 ENABLED - Oscillator is enabled but not necessarily running and stable
138/// 1:0 FREQ_RANGE - The current frequency range, always reads 0
139const XOSC_STATUS: Reg = Reg::new(0x4002_4004);
140
141/// XOSC: STARTUP Register
142///
143/// 20:20 X4: Multiplies the startup_delay by 4
144/// 13:0 DELAY: in multiples of 256*xtal_period.
145const XOSC_STARTUP: Reg = Reg::new(0x4002_400c);
146
147// helper functions to make logicavoid direct register access in panic handler
148
149fn disable_xip_cache() {
150    // not POWER_DOWN, not ERR_BADWRITE, not EN
151    XIP_CTRL.write(0);
152}
153
154fn xosc_is_running() -> bool {
155    // return true if STABLE bit is set
156    (XOSC_STATUS.read() & (1 << 31)) == (1 << 31)
157}
158
159fn xosc_start_delay(delay: u32) {
160    // delay is the low 14 bits (13:0) of the register.
161    debug_assert!(delay < (1 << 14));
162    let delay = delay & (1 << 14);
163    XOSC_STARTUP.write(delay);
164}
165
166fn xosc_enable(enable: bool) {
167    // There's only one valid frequency range, so set that
168    let freq_range = 0xaa0;
169    let enable_val = match enable {
170        true => 0xfab,
171        false => 0xd1e,
172    };
173    XOSC_CTRL.write(freq_range | (enable_val << 12));
174}