diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index a99aec1ef..d86b17703 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -17,7 +17,6 @@ pub fn build(b: *std.Build) void { const specific_examples: []const Example = &.{ // RaspberryPi Boards: .{ .target = raspberrypi.pico, .name = "pico_board_blinky", .file = "src/board_blinky.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/blinky_core1.zig" }, @@ -35,7 +34,6 @@ pub fn build(b: *std.Build) void { .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_freertos-multitask-demo", .file = "src/freertos/multitask_demo.zig" }, .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, - .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_flash-program", .file = "src/rp2040_only/flash_program.zig" }, .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, @@ -108,6 +106,7 @@ pub fn build(b: *std.Build) void { .{ .name = "net-tcp_client", .file = "src/net/tcp_client.zig" }, .{ .name = "net-tcp_server", .file = "src/net/tcp_server.zig" }, .{ .name = "board-id", .file = "src/board_id.zig" }, + .{ .name = "flash-program", .file = "src/flash_program.zig" }, }; var available_examples: std.array_list.Managed(Example) = .init(b.allocator); diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/flash_program.zig b/examples/raspberrypi/rp2xxx/src/flash_program.zig similarity index 97% rename from examples/raspberrypi/rp2xxx/src/rp2040_only/flash_program.zig rename to examples/raspberrypi/rp2xxx/src/flash_program.zig index 50ddbb611..4416ebf24 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/flash_program.zig +++ b/examples/raspberrypi/rp2xxx/src/flash_program.zig @@ -3,9 +3,7 @@ const microzig = @import("microzig"); const rp2xxx = microzig.hal; const flash = rp2xxx.flash; -const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const clocks = rp2xxx.clocks; const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); diff --git a/port/raspberrypi/rp2xxx/build.zig b/port/raspberrypi/rp2xxx/build.zig index c966e7a0f..438629544 100644 --- a/port/raspberrypi/rp2xxx/build.zig +++ b/port/raspberrypi/rp2xxx/build.zig @@ -202,8 +202,6 @@ pub fn init(dep: *std.Build.Dependency) Self { .name = "RaspberryPi Pico (ram image)", .url = "https://www.raspberrypi.com/products/raspberry-pi-pico/", .root_source_file = b.path("src/boards/raspberry_pi_pico.zig"), - // needed by the flash hal - .imports = rp2040_bootrom_imports, }, }), .pico2_arm = chip_rp2350_arm.derive(.{ diff --git a/port/raspberrypi/rp2xxx/src/boards/raspberry_pi_pico.zig b/port/raspberrypi/rp2xxx/src/boards/raspberry_pi_pico.zig index 1ea66fcb7..c418c464a 100644 --- a/port/raspberrypi/rp2xxx/src/boards/raspberry_pi_pico.zig +++ b/port/raspberrypi/rp2xxx/src/boards/raspberry_pi_pico.zig @@ -4,15 +4,13 @@ const microzig = @import("microzig"); const hal = microzig.hal; const pins = hal.pins; -pub const bootrom = @import("shared/rp2040_bootrom.zig"); - -comptime { - _ = bootrom; -} - pub const pin_config = pins.GlobalConfiguration{ .GPIO25 = .{ .name = "led", .function = .SIO, }, }; + +comptime { + _ = @import("shared/rp2040_bootrom.zig"); +} diff --git a/port/raspberrypi/rp2xxx/src/boards/shared/rp2040_bootrom.zig b/port/raspberrypi/rp2xxx/src/boards/shared/rp2040_bootrom.zig index 78af1135d..1515053a3 100644 --- a/port/raspberrypi/rp2xxx/src/boards/shared/rp2040_bootrom.zig +++ b/port/raspberrypi/rp2xxx/src/boards/shared/rp2040_bootrom.zig @@ -4,40 +4,41 @@ const chip = microzig.hal.compatibility.chip; comptime { if (chip == .RP2040 and !microzig.config.ram_image) { - _ = bootloader_data; + _ = BootromData.bootloader_data; } } -/// Raw stage2 bootrom -pub const stage2_rom: []const u8 = @embedFile("bootloader"); +const BootromData = struct { + fn prepare_boot_sector(comptime stage2_rom: []const u8) [256]u8 { + @setEvalBranchQuota(10_000); -pub export const bootloader_data: [256]u8 linksection(".boot2") = blk: { - @setEvalBranchQuota(10_000); + var bootrom: [256]u8 = @splat(0xFF); + @memcpy(bootrom[0..stage2_rom.len], stage2_rom); - var bootrom: [256]u8 = @splat(0xFF); - @memcpy(bootrom[0..stage2_rom.len], stage2_rom); + // 2.8.1.3.1. Checksum + // The last four bytes of the image loaded from flash (which we hope is a valid flash second stage) are a CRC32 checksum + // of the first 252 bytes. The parameters of the checksum are: + // • Polynomial: 0x04c11db7 + // • Input reflection: no + // • Output reflection: no + // • Initial value: 0xffffffff + // • Final XOR: 0x00000000 + // • Checksum value appears as little-endian integer at end of image + // The Bootrom makes 128 attempts of approximately 4ms each for a total of approximately 0.5 seconds before giving up + // and dropping into USB code to load and checksum the second stage with varying SPI parameters. If it sees a checksum + // pass it will immediately jump into the 252-byte payload which contains the flash second stage. + const Hash = std.hash.crc.Crc(u32, .{ + .polynomial = 0x04c11db7, + .initial = 0xffffffff, + .reflect_input = false, + .reflect_output = false, + .xor_output = 0x00000000, + }); - // 2.8.1.3.1. Checksum - // The last four bytes of the image loaded from flash (which we hope is a valid flash second stage) are a CRC32 checksum - // of the first 252 bytes. The parameters of the checksum are: - // • Polynomial: 0x04c11db7 - // • Input reflection: no - // • Output reflection: no - // • Initial value: 0xffffffff - // • Final XOR: 0x00000000 - // • Checksum value appears as little-endian integer at end of image - // The Bootrom makes 128 attempts of approximately 4ms each for a total of approximately 0.5 seconds before giving up - // and dropping into USB code to load and checksum the second stage with varying SPI parameters. If it sees a checksum - // pass it will immediately jump into the 252-byte payload which contains the flash second stage. - const Hash = std.hash.crc.Crc(u32, .{ - .polynomial = 0x04c11db7, - .initial = 0xffffffff, - .reflect_input = false, - .reflect_output = false, - .xor_output = 0x00000000, - }); + std.mem.writeInt(u32, bootrom[252..256], Hash.hash(bootrom[0..252]), .little); - std.mem.writeInt(u32, bootrom[252..256], Hash.hash(bootrom[0..252]), .little); + return bootrom; + } - break :blk bootrom; + export const bootloader_data: [256]u8 linksection(".boot2") = prepare_boot_sector(@embedFile("bootloader")); }; diff --git a/port/raspberrypi/rp2xxx/src/hal/flash.zig b/port/raspberrypi/rp2xxx/src/hal/flash.zig index 9e582fe95..c84da021f 100644 --- a/port/raspberrypi/rp2xxx/src/hal/flash.zig +++ b/port/raspberrypi/rp2xxx/src/hal/flash.zig @@ -1,12 +1,12 @@ -//! See [rp2040 docs](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf), page 136. -const rom = @import("rom.zig"); - +/// Based on https://github.com/raspberrypi/pico-sdk/blob/a1438dff1d38bd9c65dbd693f0e5db4b9ae91779/src/rp2_common/hardware_flash/flash.c#L41 const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; - const IO_QSPI = peripherals.IO_QSPI; const XIP_SSI = peripherals.SSI; +const rom = @import("rom.zig"); +const compatibility = @import("compatibility.zig"); + pub const Command = enum(u8) { block_erase = 0xd8, ruid_cmd = 0x4b, @@ -16,15 +16,24 @@ pub const PAGE_SIZE = 256; pub const SECTOR_SIZE = 4096; pub const BLOCK_SIZE = 65536; -/// Bus reads to a 16MB memory window start at this address +/// 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 +/// After the bootrom enters the user application, a copy of the flash XIP +/// setup function is found here on RP2350. +pub const BOOTRAM_BASE = 0x400e0000; + +/// Infrastructure for reentering XIP mode after exiting for programming. +/// pub const boot2 = if (!microzig.config.ram_image) struct { - /// Size of the second stage bootloader in words const BOOT2_SIZE_WORDS = 64; - /// Buffer for the second stage bootloader + var copyout: [BOOT2_SIZE_WORDS]u32 = undefined; + var copyout_valid: bool = false; + + /// Copies the XIP setup function into RAM: + /// + /// - On RP2040 this *is* the second stage bootloader /// /// 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) @@ -34,51 +43,51 @@ pub const boot2 = if (!microzig.config.ram_image) struct { /// `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 + /// - On RP2350 it is found at BOOTRAM_BASE. + /// + /// ** From RP2350 datasheet section 4.3 ** + /// It is physically impossible to execute code from boot RAM, regardless + /// of MPU configuration, as it is on the APB peripheral bus segment, which + /// is not wired to the processor instruction fetch ports. + /// + /// Therefore, we must make a copy of it first. /// - /// 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(".ram_text") void { if (copyout_valid) return; - const bootloader = @as([*]u32, @ptrFromInt(XIP_BASE)); + const bootloader = @as([*]u32, @ptrFromInt(switch (compatibility.chip) { + .RP2040 => XIP_BASE, + .RP2350 => BOOTRAM_BASE, + })); var i: usize = 0; while (i < BOOT2_SIZE_WORDS) : (i += 1) { copyout[i] = bootloader[i]; } + asm volatile ("" ::: .{ .memory = true }); // memory barrier copyout_valid = true; } - /// Configure the SSI and the external flash for XIP by calling the second - /// stage bootloader that was copied out to `copyout`. + /// Configure the SSI and the external flash for XIP using the XIP setup + /// function that was copied out to `copyout`. pub export fn flash_enable_xip() linksection(".ram_text") void { - // The bootloader is in thumb mode - asm volatile ( - \\adds r0, #1 - \\blx r0 - : - : [copyout] "{r0}" (@intFromPtr(©out)), - : .{ .r0 = true, .r14 = true }); + + // Calling boot2 as a function works because it accepts a return vector in + // LR (and doesn't trash r4-r7). Bootrom passes NULL in LR, instructing + // boot2 to enter flash vector table's reset handler. + + const thumb_bit = switch (compatibility.arch) { + .arm => 1, + .riscv => 0, + }; + @as(*const fn () callconv(.c) void, @ptrFromInt(@intFromPtr(©out) + thumb_bit))(); } } else struct { // no op pub inline fn flash_init() linksection(".ram_text") void {} - /// Configure the SSI and the external flash for XIP by calling the second - /// stage bootloader embedded into RAM. + // Fallback. This is a very slow XIP configuration, but is very widely + // supported. pub inline fn flash_enable_xip() linksection(".ram_text") void { - // The bootloader is in thumb mode - asm volatile ( - \\adds r0, #1 - \\blx r0 - : - : [copyout] "{r0}" (@intFromPtr(microzig.board.bootrom.stage2_rom.ptr)), - : .{ .r0 = true, .r14 = true }); + rom.flash_enter_cmd_xip(); } }; @@ -86,7 +95,7 @@ pub const boot2 = if (!microzig.config.ram_image) struct { /// /// The offset must be aligned to a 4096-byte sector, and count must be a /// multiple of 4096 bytes! -pub inline fn range_erase(offset: u32, count: u32) void { +pub inline fn range_erase(offset: u33, count: u32) void { // Do not inline `_range_erase`! @call(.never_inline, _range_erase, .{ offset, count }); }