Skip to content

Commit cb6ca18

Browse files
authored
RP2xxx: add missing rom functions (#615)
1 parent 6af0a40 commit cb6ca18

File tree

1 file changed

+140
-90
lines changed
  • port/raspberrypi/rp2xxx/src/hal

1 file changed

+140
-90
lines changed
Lines changed: 140 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
//! Access to functions and data in the RP2040 bootrom
22
//!
3-
//! The Bootrom contains a number of public functions that provide useful RP2040 functionality that might be needed in
4-
//! the absence of any other code on the device, as well as highly optimized versions of certain key functionality that would
5-
//! otherwise have to take up space in most user binaries.
3+
//! The Bootrom contains a number of public functions that provide useful RP2040 functionality
4+
//! that might be needed in the absence of any other code on the device,
5+
//! as well as highly optimized versions of certain key functionality
6+
//! that would otherwise have to take up space in most user binaries.
67
//!
78
//! The functions include:
89
//! 1. Fast Bit Counting / Manipulation Functions
910
//! 2. Fast Bulk Memory Fill / Copy Functions
1011
//! 3. Flash Access Functions
11-
//! 4. Debugging Support Functions (TODO)
12-
//! 5. Miscellaneous Functions (TODO)
12+
//! 4. Debugging Support Functions
13+
//! 5. Miscellaneous Functions
1314

1415
const std = @import("std");
1516
const microzig = @import("microzig");
@@ -31,52 +32,94 @@ pub const Code = enum(u32) {
3132
flash_range_program = rom_table_code('R', 'P'),
3233
flash_flush_cache = rom_table_code('F', 'C'),
3334
flash_enter_cmd_xip = rom_table_code('C', 'X'),
35+
debug_trampoline = rom_table_code('D', 'T'),
36+
debug_trampoline_end = rom_table_code('D', 'E'),
37+
reset_to_usb_boot = rom_table_code('U', 'B'),
38+
wait_for_vector = rom_table_code('W', 'V'),
3439
};
3540

3641
/// Signatures of all public bootrom functions
3742
pub const signatures = struct {
3843
/// Returns the 32 bit pointer into the ROM if found or NULL otherwise
39-
const rom_table_lookup = fn (table: *u16, code: u32) *anyopaque;
44+
const rom_table_lookup = fn (table: [*]const u16, code: u32) callconv(.C) *anyopaque;
4045
/// Signature for popcount32: Return a count of the number of 1 bits in value
41-
const popcount32 = fn (value: u32) u32;
46+
const popcount32 = fn (value: u32) callconv(.C) u32;
4247
/// Signature for reverse32: Return the bits of value in the reverse order
43-
const reverse32 = fn (value: u32) u32;
48+
const reverse32 = fn (value: u32) callconv(.C) u32;
4449
/// Signature for clz32: Return the number of consecutive high order 0 bits of value
45-
const clz32 = fn (value: u32) u32;
50+
const clz32 = fn (value: u32) callconv(.C) u32;
4651
/// Signature for ctz32: Return the number of consecutive low order 0 bits of value
47-
const ctz32 = fn (value: u32) u32;
52+
const ctz32 = fn (value: u32) callconv(.C) u32;
4853
/// Signature of memset: Sets n bytes start at ptr to the value c and returns ptr
49-
const memset = fn (ptr: [*]u8, c: u8, n: u32) [*]u8;
50-
/// Signature of memset4: Sets n bytes start at ptr to the value c and returns ptr; must be word (32-bit) aligned!
51-
const memset4 = fn (ptr: [*]u32, c: u8, n: u32) [*]u32;
52-
/// Signature of memcpy: Copies n bytes starting at src to dest and returns dest. The results are undefined if the regions overlap.
53-
const memcpy = fn (dest: [*]u8, src: [*]const u8, n: u32) [*]u8;
54-
/// Signature of memcpy44: Copies n bytes starting at src to dest and returns dest; must be word (32-bit) aligned!
55-
const memcpy44 = fn (dest: [*]u32, src: [*]const u32, n: u32) [*]u8;
56-
/// Signature of connect_internal_flash: Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads
57-
const connect_internal_flash = fn () void;
58-
/// Signature of flash_exit_xip: First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence described in
59-
/// Section 2.8.1.2. Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be
60-
/// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This function
61-
/// configures the SSI with a fixed SCK clock divisor of /6.
62-
const flash_exit_xip = fn () void;
63-
/// Signature of flash_range_erase: Erase a count bytes, starting at addr (offset from start of flash). Optionally, pass a block erase command
64-
/// e.g. D8h block erase, and the size of the block erased by this command — this function will use the larger
65-
/// block erase where possible, for much higher erase speed. addr must be aligned to a 4096-byte sector, and
66-
/// count must be a multiple of 4096 bytes.
67-
const flash_range_erase = fn (addr: u32, count: usize, block_size: u32, block_cmd: u8) void;
68-
/// Signature of flash_range_program: Program data to a range of flash addresses starting at addr (offset from the start of flash) and count bytes
69-
/// in size. addr must be aligned to a 256-byte boundary, and count must be a multiple of 256.
70-
const flash_range_program = fn (addr: u32, data: [*]const u8, count: usize) void;
71-
/// Signature of flash_flush_cache: Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can drive the
72-
/// flash chip select as normal.
73-
const flash_flush_cache = fn () void;
74-
/// Signature of flash_enter_cmd_xip: Configure the SSI to generate a standard 03h serial read command, with 24 address bits, upon each XIP
75-
/// access. This is a very slow XIP configuration, but is very widely supported. The debugger calls this
76-
/// function after performing a flash erase/programming operation, so that the freshly-programmed code
77-
/// and data is visible to the debug host, without having to know exactly what kind of flash device is
78-
/// connected.
79-
const flash_enter_cmd_xip = fn () void;
54+
const memset = fn (ptr: [*]u8, c: u8, n: u32) callconv(.C) [*]u8;
55+
/// Signature of memset4: Sets n bytes start at ptr to the value c and returns ptr;
56+
/// must be word (32-bit) aligned!
57+
const memset4 = fn (ptr: [*]u32, c: u8, n: u32) callconv(.C) [*]u32;
58+
/// Signature of memcpy: Copies n bytes starting at src to dest and returns dest.
59+
/// The results are undefined if the regions overlap.
60+
const memcpy = fn (dest: [*]u8, src: [*]const u8, n: u32) callconv(.C) [*]u8;
61+
/// Signature of memcpy44: Copies n bytes starting at src to dest and returns dest;
62+
/// must be word (32-bit) aligned!
63+
const memcpy44 = fn (dest: [*]u32, src: [*]const u32, n: u32) callconv(.C) [*]u8;
64+
/// Signature of connect_internal_flash: Restore all QSPI pad controls to their
65+
/// default state, and connect the SSI to the QSPI pads
66+
const connect_internal_flash = fn () callconv(.C) void;
67+
/// Signature of flash_exit_xip: First set up the SSI for serial-mode operations,
68+
/// then issue the fixed XIP exit sequence described in Section 2.8.1.2. Note that
69+
/// the bootrom code uses the IO forcing logic to drive the CS pin, which must be
70+
/// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache).
71+
/// This function configures the SSI with a fixed SCK clock divisor of /6.
72+
const flash_exit_xip = fn () callconv(.C) void;
73+
/// Signature of flash_range_erase: Erase a count bytes, starting at addr (offset from
74+
/// start of flash). Optionally, pass a block erase command e.g. D8h block erase,
75+
/// and the size of the block erased by this command — this function will use the
76+
/// larger block erase where possible, for much higher erase speed. addr must be aligned
77+
/// to a 4096-byte sector, and count must be a multiple of 4096 bytes.
78+
const flash_range_erase = fn (
79+
addr: u32,
80+
count: usize,
81+
block_size: u32,
82+
block_cmd: u8,
83+
) callconv(.C) void;
84+
/// Signature of flash_range_program: Program data to a range of flash addresses
85+
/// starting at addr (offset from the start of flash) and count bytes in size.
86+
/// addr must be aligned to a 256-byte boundary, and count must be a multiple of 256.
87+
const flash_range_program = fn (addr: u32, data: [*]const u8, count: usize) callconv(.C) void;
88+
/// Signature of flash_flush_cache: Flush and enable the XIP cache. Also clears the IO
89+
/// forcing on QSPI CSn, so that the SSI can drive the flash chip select as normal.
90+
const flash_flush_cache = fn () callconv(.C) void;
91+
/// Signature of flash_enter_cmd_xip: Configure the SSI to generate
92+
/// a standard 03h serial read command, with 24 address bits, upon each XIP access.
93+
/// This is a very slow XIP configuration, but is very widely supported.
94+
/// The debugger calls this function after performing a flash erase/programming operation,
95+
/// so that the freshly-programmed code and data is visible to the debug host,
96+
/// without having to know exactly what kind of flash device is connected.
97+
const flash_enter_cmd_xip = fn () callconv(.C) void;
98+
/// Signature of debug_trampoline: Simple debugger trampoline for break-on-return.
99+
/// This methods helps the debugger call ROM routines without setting hardware breakpoints.
100+
/// The function address is passed in r7 and args are passed through r0 … r3 as per ABI.
101+
/// This method does not return but executes a BKPT #0 at the end
102+
const debug_trampoline = fn () callconv(.C) void;
103+
/// Signature of debug_trampoline_end: This is the address of the final BKPT #0 instruction
104+
/// of debug_trampoline. This can be compared with the program counter to detect
105+
/// completion of the debug_trampoline call.
106+
const debug_trampoline_end = fn () callconv(.C) void;
107+
/// Signature of reset_to_usb_boot: Resets the RP2040 and uses the watchdog facility
108+
/// to re-start in BOOTSEL mode:
109+
/// - gpio_activity_pin_mask is provided to enable an "activity light" via
110+
/// GPIO attached LED for the USB Mass Storage Device:
111+
/// - 0 No pins are used as per a cold boot.
112+
/// - Otherwise a single bit set indicating which GPIO pin should be set to output and raised
113+
/// whenever there is mass storage activity from the host.
114+
/// - disable_interface_mask may be used to control the exposed USB interfaces:
115+
/// - 0 To enable both interfaces (as per a cold boot)
116+
/// - 1 To disable the USB Mass Storage Interface (see Section 2.8.4)
117+
/// - 2 To disable the USB PICOBOOT Interface (see Section 2.8.5)
118+
const reset_to_usb_boot = fn (gpio_activity_mask: u32, disable_interface_mask: u32) callconv(.C) noreturn;
119+
/// Signature of wait_for_vector: This is the method that is entered by core 1 on reset to wait
120+
/// to be launched by core 0. There are few cases where you should call this method (resetting
121+
/// core 1 is much better). This method does not return and should only ever be called on core 1.
122+
const wait_for_vector = fn () callconv(.C) noreturn;
80123
};
81124

82125
/// Return a bootrom lookup code based on two ASCII characters
@@ -91,7 +134,7 @@ pub const signatures = struct {
91134
///
92135
/// A 32 bit address pointing into bootrom
93136
pub fn rom_table_code(c1: u8, c2: u8) u32 {
94-
return @as(u32, @intCast(c1)) | (@as(u32, @intCast(c2)) << 8);
137+
return @as(u32, c1) | (@as(u32, c2) << 8);
95138
}
96139

97140
/// Convert a 16 bit pointer stored at the given rom address into a pointer
@@ -103,8 +146,8 @@ pub fn rom_table_code(c1: u8, c2: u8) u32 {
103146
///
104147
/// The converted pointer
105148
pub inline fn rom_hword_as_ptr(rom_addr: u32) *anyopaque {
106-
const ptr_to_ptr = @as(*u16, @ptrFromInt(rom_addr));
107-
return @as(*anyopaque, @ptrFromInt(@as(usize, @intCast(ptr_to_ptr.*))));
149+
const ptr_to_ptr: *const u16 = @ptrFromInt(rom_addr);
150+
return @ptrFromInt(ptr_to_ptr.*);
108151
}
109152

110153
/// Lookup a bootrom function by code (inline)
@@ -115,9 +158,9 @@ pub inline fn rom_hword_as_ptr(rom_addr: u32) *anyopaque {
115158
/// # Returns
116159
///
117160
/// A anyopaque pointer to the function; must be cast by the caller
118-
pub inline fn _rom_func_lookup(code: Code) *anyopaque {
119-
const rom_table_lookup = @as(*signatures.rom_table_lookup, @alignCast(@ptrCast(rom_hword_as_ptr(0x18))));
120-
const func_table = @as(*u16, @ptrCast(@alignCast(rom_hword_as_ptr(0x14))));
161+
pub inline fn _rom_func_lookup(code: Code) *const anyopaque {
162+
const rom_table_lookup: *const signatures.rom_table_lookup = @alignCast(@ptrCast(rom_hword_as_ptr(0x18)));
163+
const func_table: [*]const u16 = @ptrCast(@alignCast(rom_hword_as_ptr(0x14)));
121164
return rom_table_lookup(func_table, @intFromEnum(code));
122165
}
123166

@@ -129,7 +172,7 @@ pub inline fn _rom_func_lookup(code: Code) *anyopaque {
129172
/// # Returns
130173
///
131174
/// A anyopaque pointer to the function; must be cast by the caller
132-
pub fn rom_func_lookup(code: Code) *anyopaque {
175+
pub fn rom_func_lookup(code: Code) *const anyopaque {
133176
return _rom_func_lookup(code);
134177
}
135178

@@ -145,10 +188,10 @@ pub fn popcount32(value: u32) u32 {
145188
}
146189

147190
const S = struct {
148-
var f: ?*signatures.popcount32 = null;
191+
var f: ?*const signatures.popcount32 = null;
149192
};
150193

151-
if (S.f == null) S.f = @as(*signatures.popcount32, @ptrCast(_rom_func_lookup(Code.popcount32)));
194+
if (S.f == null) S.f = @ptrCast(_rom_func_lookup(Code.popcount32));
152195
return S.f.?(value);
153196
}
154197

@@ -160,10 +203,10 @@ pub fn reverse32(value: u32) u32 {
160203
}
161204

162205
const S = struct {
163-
var f: ?*signatures.reverse32 = null;
206+
var f: ?*const signatures.reverse32 = null;
164207
};
165208

166-
if (S.f == null) S.f = @as(*signatures.reverse32, @ptrCast(_rom_func_lookup(Code.reverse32)));
209+
if (S.f == null) S.f = @ptrCast(_rom_func_lookup(Code.reverse32));
167210
return S.f.?(value);
168211
}
169212

@@ -175,10 +218,10 @@ pub fn clz32(value: u32) u32 {
175218
}
176219

177220
const S = struct {
178-
var f: ?*signatures.clz32 = null;
221+
var f: ?*const signatures.clz32 = null;
179222
};
180223

181-
if (S.f == null) S.f = @as(*signatures.clz32, @ptrCast(_rom_func_lookup(Code.clz32)));
224+
if (S.f == null) S.f = @ptrCast(_rom_func_lookup(Code.clz32));
182225
return S.f.?(value);
183226
}
184227

@@ -190,10 +233,10 @@ pub fn ctz32(value: u32) u32 {
190233
}
191234

192235
const S = struct {
193-
var f: ?*signatures.ctz32 = null;
236+
var f: ?*const signatures.ctz32 = null;
194237
};
195238

196-
if (S.f == null) S.f = @as(*signatures.ctz32, @ptrCast(_rom_func_lookup(Code.ctz32)));
239+
if (S.f == null) S.f = @ptrCast(_rom_func_lookup(Code.ctz32));
197240
return S.f.?(value);
198241
}
199242

@@ -209,10 +252,10 @@ pub fn memset(dest: []u8, c: u8) []u8 {
209252
}
210253

211254
const S = struct {
212-
var f: ?*signatures.memset = null;
255+
var f: ?*const signatures.memset = null;
213256
};
214257

215-
if (S.f == null) S.f = @as(*signatures.memset, @ptrCast(_rom_func_lookup(Code.memset)));
258+
if (S.f == null) S.f = @ptrCast(_rom_func_lookup(Code.memset));
216259
return S.f.?(dest.ptr, c, dest.len)[0..dest.len];
217260
}
218261

@@ -225,12 +268,12 @@ pub fn memcpy(dest: []u8, src: []const u8) []u8 {
225268
}
226269

227270
const S = struct {
228-
var f: ?*signatures.memcpy = null;
271+
var f: ?*const signatures.memcpy = null;
229272
};
230273

231274
const n = if (dest.len <= src.len) dest.len else src.len;
232275

233-
if (S.f == null) S.f = @as(*signatures.memcpy, @alignCast(@ptrCast(_rom_func_lookup(Code.memcpy))));
276+
if (S.f == null) S.f = @alignCast(@ptrCast(_rom_func_lookup(Code.memcpy)));
234277
return S.f.?(dest.ptr, src.ptr, n)[0..n];
235278
}
236279

@@ -239,54 +282,39 @@ pub fn memcpy(dest: []u8, src: []const u8) []u8 {
239282
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
240283

241284
/// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads
242-
pub inline fn connect_internal_flash() *signatures.connect_internal_flash {
243-
return @as(
244-
*signatures.connect_internal_flash,
245-
@alignCast(@ptrCast(_rom_func_lookup(Code.connect_internal_flash))),
246-
);
285+
pub inline fn connect_internal_flash() *const signatures.connect_internal_flash {
286+
return @alignCast(@ptrCast(_rom_func_lookup(Code.connect_internal_flash)));
247287
}
248288

249289
/// First set up the SSI for serial-mode operations, then issue the fixed XIP exit
250290
/// sequence described in Section 2.8.1.2. Note that the bootrom code uses the IO
251291
/// forcing logic to drive the CS pin, which must be cleared before returning the
252292
/// SSI to XIP mode (e.g. by a call to _flash_flush_cache). This function configures
253293
/// the SSI with a fixed SCK clock divisor of /6.
254-
pub inline fn flash_exit_xip() *signatures.flash_exit_xip {
255-
return @as(
256-
*signatures.flash_exit_xip,
257-
@alignCast(@ptrCast(_rom_func_lookup(Code.flash_exit_xip))),
258-
);
294+
pub inline fn flash_exit_xip() *const signatures.flash_exit_xip {
295+
return @alignCast(@ptrCast(_rom_func_lookup(Code.flash_exit_xip)));
259296
}
260297

261298
/// Erase a count bytes, starting at addr (offset from start of flash). Optionally,
262299
/// pass a block erase command e.g. D8h block erase, and the size of the block
263300
/// erased by this command — this function will use the larger block erase where
264301
/// possible, for much higher erase speed. addr must be aligned to a 4096-byte sector,
265302
/// and count must be a multiple of 4096 bytes.
266-
pub inline fn flash_range_erase() *signatures.flash_range_erase {
267-
return @as(
268-
*signatures.flash_range_erase,
269-
@alignCast(@ptrCast(_rom_func_lookup(Code.flash_range_erase))),
270-
);
303+
pub inline fn flash_range_erase() *const signatures.flash_range_erase {
304+
return @alignCast(@ptrCast(_rom_func_lookup(Code.flash_range_erase)));
271305
}
272306

273307
/// Program data to a range of flash addresses starting at addr (offset from the
274308
/// start of flash) and count bytes in size. addr must be aligned to a 256-byte
275309
/// boundary, and the length of data must be a multiple of 256.
276-
pub inline fn flash_range_program() *signatures.flash_range_program {
277-
return @as(
278-
*signatures.flash_range_program,
279-
@alignCast(@ptrCast(_rom_func_lookup(Code.flash_range_program))),
280-
);
310+
pub inline fn flash_range_program() *const signatures.flash_range_program {
311+
return @alignCast(@ptrCast(_rom_func_lookup(Code.flash_range_program)));
281312
}
282313

283314
/// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that
284315
/// the SSI can drive the flash chip select as normal.
285-
pub inline fn flash_flush_cache() *signatures.flash_flush_cache {
286-
return @as(
287-
*signatures.flash_flush_cache,
288-
@alignCast(@ptrCast(_rom_func_lookup(Code.flash_flush_cache))),
289-
);
316+
pub inline fn flash_flush_cache() *const signatures.flash_flush_cache {
317+
return @alignCast(@ptrCast(_rom_func_lookup(Code.flash_flush_cache)));
290318
}
291319

292320
/// Configure the SSI to generate a standard 03h serial read command, with 24 address
@@ -295,9 +323,31 @@ pub inline fn flash_flush_cache() *signatures.flash_flush_cache {
295323
/// erase/programming operation, so that the freshly-programmed code and data is
296324
/// visible to the debug host, without having to know exactly what kind of flash
297325
/// device is connected.
298-
pub inline fn flash_enter_cmd_xip() *signatures.flash_enter_cmd_xip {
299-
return @as(
300-
*signatures.flash_enter_cmd_xip,
301-
@alignCast(@ptrCast(_rom_func_lookup(Code.flash_enter_cmd_xip))),
302-
);
326+
pub inline fn flash_enter_cmd_xip() *const signatures.flash_enter_cmd_xip {
327+
return @alignCast(@ptrCast(_rom_func_lookup(Code.flash_enter_cmd_xip)));
328+
}
329+
330+
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
331+
// Miscellaneous Functions (Datasheet p. 137)
332+
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
333+
334+
/// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode:
335+
/// - gpio_activity_pin_mask is provided to enable an "activity light" via
336+
/// GPIO attached LED for the USB Mass Storage Device:
337+
/// - 0 No pins are used as per a cold boot.
338+
/// - Otherwise a single bit set indicating which GPIO pin should be set to output
339+
/// and raised whenever there is mass storage activity from the host.
340+
/// - disable_interface_mask may be used to control the exposed USB interfaces:
341+
/// - 0 To enable both interfaces (as per a cold boot)
342+
/// - 1 To disable the USB Mass Storage Interface (see Section 2.8.4)
343+
/// - 2 To disable the USB PICOBOOT Interface (see Section 2.8.5)
344+
pub inline fn reset_to_usb_boot() *const signatures.reset_to_usb_boot {
345+
return @alignCast(@ptrCast(_rom_func_lookup(Code.reset_to_usb_boot)));
346+
}
347+
348+
/// This is the method that is entered by core 1 on reset to wait to be launched by core 0.
349+
/// There are few cases where you should call this method (resetting core 1 is much better).
350+
/// This method does not return and should only ever be called on core 1.
351+
pub inline fn wait_for_vector() *const signatures.wait_for_vector {
352+
return @alignCast(@ptrCast(_rom_func_lookup(Code.wait_for_vector)));
303353
}

0 commit comments

Comments
 (0)