From 1cef56ad9d5df63af9c45f54a4983b36b0796f7f Mon Sep 17 00:00:00 2001 From: David Sugar Date: Mon, 31 Jul 2023 16:20:02 +0200 Subject: [PATCH] Enable XIP using stage 2 bootloader (#73) * flash enable xip calls stage two bootloader using inline assembly * flash erase/program now works in all modes (Debug, ReleaseSmall, ReleaseSafe, ReleaseFast) * further docs added --- examples/flash_program.zig | 2 +- rp2040.ld | 2 +- src/hal/flash.zig | 69 +++++++++++++++++++++++++++----------- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/examples/flash_program.zig b/examples/flash_program.zig index 48754e1..9fd13be 100644 --- a/examples/flash_program.zig +++ b/examples/flash_program.zig @@ -62,7 +62,7 @@ pub fn main() !void { // Note that a whole number of pages (256 bytes) must be written at a time std.log.info("Programming target region...", .{}); - flash.range_program(flash_target_offset, &data); + flash.range_program(flash_target_offset, data[0..]); std.log.info("Done. Read back target region:", .{}); std.log.info("data: {s}", .{flash_target_contents[0..flash.PAGE_SIZE]}); diff --git a/rp2040.ld b/rp2040.ld index cac0892..4517b36 100644 --- a/rp2040.ld +++ b/rp2040.ld @@ -43,7 +43,7 @@ SECTIONS .data : { microzig_data_start = .; - *(.time_critical*) + KEEP(*(.time_critical*)) *(.data*) microzig_data_end = .; } > ram0 AT> flash0 diff --git a/src/hal/flash.zig b/src/hal/flash.zig index 7ce3206..f10315e 100644 --- a/src/hal/flash.zig +++ b/src/hal/flash.zig @@ -1,3 +1,4 @@ +//! See [rp2040 docs](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf), page 136. const rom = @import("rom.zig"); pub const Command = enum(u8) { @@ -12,33 +13,51 @@ pub const BLOCK_SIZE = 65536; /// Bus reads to a 16MB memory window start at this address pub const XIP_BASE = 0x10000000; +/// Flash code related to the second stage boot loader pub const boot2 = struct { - /// Size of the second stage bootloader in bytes - const BOOT2_SIZE_BYTES = 64; + /// Size of the second stage bootloader in words + const BOOT2_SIZE_WORDS = 64; /// Buffer for the second stage bootloader - var copyout: [BOOT2_SIZE_BYTES]u32 = undefined; + /// + /// The only job of the second stage bootloader is to configure the SSI and + /// the external flash for the best possible execute-in-place (XIP) performance. + /// Until the SSI is correctly configured for the attached flash device, it's not + /// possible to access flash via the XIP address window, i.e., we have to copy + /// the bootloader into sram before calling `rom.flash_exit_xip`. This is required + /// if we want to erase and/or write to flash. + /// + /// At the end we can then just make a subroutine call to copyout, to configure + /// the SSI and flash. The second stage bootloader will return to the calling function + /// if a return address is provided in `lr`. + var copyout: [BOOT2_SIZE_WORDS]u32 = undefined; var copyout_valid: bool = false; /// Copy the 2nd stage bootloader into memory - pub fn flash_init() linksection(".time_critical") void { + /// + /// This is required by `_range_erase` and `_range_program` so we can later setup + /// XIP via the second stage bootloader. + pub export fn flash_init() linksection(".time_critical") void { if (copyout_valid) return; const bootloader = @as([*]u32, @ptrFromInt(XIP_BASE)); var i: usize = 0; - while (i < BOOT2_SIZE_BYTES) : (i += 1) { + while (i < BOOT2_SIZE_WORDS) : (i += 1) { copyout[i] = bootloader[i]; } copyout_valid = true; } - pub fn flash_enable_xip() linksection(".time_critical") void { - // TODO: use the second stage bootloader instead of cmd_xip - //const bootloader: []u32 = copyout[1..]; - - //const f = @ptrCast(*fn () void, bootloader.ptr); - //f(); - - rom.flash_enter_cmd_xip()(); + /// Configure the SSI and the external flash for XIP by calling the second stage + /// bootloader that was copied out to `copyout`. + pub export fn flash_enable_xip() linksection(".time_critical") void { + // The bootloader is in thumb mode + asm volatile ( + \\adds r0, #1 + \\blx r0 + : + : [copyout] "{r0}" (@intFromPtr(©out)), + : "r0", "lr" + ); } }; @@ -46,12 +65,17 @@ pub const boot2 = struct { /// /// The offset must be aligned to a 4096-byte sector, and count must /// be a multiple of 4096 bytes! -pub fn range_erase(offset: u32, count: u32) linksection(".time_critical") void { +pub inline fn range_erase(offset: u32, count: u32) void { + // Do not inline `_range_erase`! + @call(.never_inline, _range_erase, .{ offset, count }); +} + +export fn _range_erase(offset: u32, count: u32) linksection(".time_critical") void { // TODO: add sanity checks, e.g., offset + count < flash size - boot2.flash_init(); + asm volatile ("" ::: "memory"); // memory barrier - // TODO: __compiler_memory_barrier + boot2.flash_init(); rom.connect_internal_flash()(); rom.flash_exit_xip()(); @@ -65,16 +89,21 @@ pub fn range_erase(offset: u32, count: u32) linksection(".time_critical") void { /// /// The offset must be aligned to a 256-byte boundary, and the length of data /// must be a multiple of 256! -pub fn range_program(offset: u32, data: []const u8) linksection(".time_critical") void { +pub inline fn range_program(offset: u32, data: []const u8) void { + // Do not inline `_range_program`! + @call(.never_inline, _range_program, .{ offset, data.ptr, data.len }); +} + +export fn _range_program(offset: u32, data: [*]const u8, len: usize) linksection(".time_critical") void { // TODO: add sanity checks, e.g., offset + count < flash size - boot2.flash_init(); + asm volatile ("" ::: "memory"); // memory barrier - // TODO: __compiler_memory_barrier + boot2.flash_init(); rom.connect_internal_flash()(); rom.flash_exit_xip()(); - rom.flash_range_program()(offset, data.ptr, data.len); + rom.flash_range_program()(offset, data, len); rom.flash_flush_cache()(); boot2.flash_enable_xip();