August 31, 2023

Raspberry Pi Pico - Second stage Boot

The first stage is in permanent ROM in the RP2040 chip. What it will do is to scan the flash memory, looking for something it recognizes as a second stage boot. When and if it finds that, it loads it into SRAM and runs it.

The job of the second stage boot is not what I would have expected (namely to load more stuff into SRAM). Instead, its job is to set up the XIP hardware to run code directly out of flash. So, it is not a "loader" at all, and I need to break my habit of calling it such.

Why couldn't the first stage boot do this? Well, it could (had they programmed it this way). Having the second stage boot on flash allows a different second stage for different flash chips.

The Pico ships with a Winbond W25Q16JV chip. This is a 16 megabit (2 megabyte) flash chip.

I spent some time studying the Pico SDK to learn more about the second stage boot. First of all, it fits into 256 bytes. The source can be found at:

pico-sdk/src/rp2_common/boot_stage2
If you look at this directory, you will find source files for several different (5 at this time) second stage boot files:
boot2_usb_blinky.S
boot2_generic_03h.S
boot2_at25sf128a.S
boot2_w25q080.S
boot2_is25lp080.S
boot2_w25x10cl.S
The first is not really a boot at all, but a demo that runs as second stage boot. The "generic" file is what I at first thought gets compiled for the pico. It is not -- the "at25sf128a" file is what is used (see below). This leaves us with 4 others, corresponding to other flash chips:
w25q16jv   -- 16 Mbit - the chip we have on the Pico
at25sf128a -- 128 Mbit SPI flash (16 Mbyte) Renesas electronis
w25q080    -- 8 Mbit (1 Mbyte) flash - Winbond
is25lp080  -- 8 Mbit (1 Mbyte) flash - ISSI
w25x10cl   -- 1 Mbit nor flash -- Winbond
Except for the 128 Mbit chip, these are all odd small chips that we don't expect to see.

The 03h (aka 0x03) command is apparently the flash read command.

Build process (from the blink example)

My reformatted and trimmed "paraphrase" of the process (with a variety of "fluff" such as "make depend" lines removed.

Two things ought to be noted. One is that this "compiles" something called "compile_time_choice.S". In our case this ends up being exactly "boot2_at25sf128a.S" via a complex tangle of conditional code. The "chooser" just includes one of the other files, as governed by compile time switches.

The second thing is the output, namely "bs2_default_padded_checksummed.S". This is a binary blob repackaged by the python script "pad_checksum" in an interesting way. The padding is done with zero bytes and room is left for a 4 byte checksum, which is appended. The original binary is 240 bytes, so this works out just fine.

The build products all end up in

/u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2

Of particular interest there is bs2_default.dis. Of course you should avail yourself of the original source code, but the buck stops here. The build process is complex and you may miss certain things, all of which will be found here.

Indeed, examining this corrected me with regard to the boot2_*.S assembly file in use. It is actually the at25sf128a.S file and not the generic_03h file like I first thought!!

cd /u1/Projects/rp2040/pico-examples/build

make  -f pico-sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default.dir/build.make
    pico-sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default.dir/build

cd /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2

/usr/bin/arm-none-eabi-gcc -DPICO_BOARD=\"pico\" -DPICO_BUILD=1 -DPICO_NO_HARDWARE=0 -DPICO_ON_DEVICE=1
    -I/u1/Projects/rp2040/pico-sdk/src/rp2_common/boot_stage2/asminclude
    -I/u1/Projects/rp2040/pico-sdk/src/rp2040/hardware_regs/include
    -I/u1/Projects/rp2040/pico-sdk/src/rp2_common/hardware_base/include
    -I/u1/Projects/rp2040/pico-sdk/src/common/pico_base/include
    -I/u1/Projects/rp2040/pico-examples/build/generated/pico_base
    -I/u1/Projects/rp2040/pico-sdk/src/boards/include
    -I/u1/Projects/rp2040/pico-sdk/src/rp2_common/pico_platform/include
    -I/u1/Projects/rp2040/pico-sdk/src/rp2_common/boot_stage2/include
    -mcpu=cortex-m0plus -mthumb -O3 -DNDEBUG
    -o CMakeFiles/bs2_default.dir/compile_time_choice.S.obj
    -c /u1/Projects/rp2040/pico-sdk/src/rp2_common/boot_stage2/compile_time_choice.S

/usr/bin/cmake -E cmake_link_script CMakeFiles/bs2_default.dir/link.txt --verbose=xxx
/usr/bin/arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb -O3 -DNDEBUG -Wl,--build-id=none --specs=nosys.specs -nostartfiles
    -Wl,--script=/u1/Projects/rp2040/pico-sdk/src/rp2_common/boot_stage2/boot_stage2.ld
    -Wl,-Map=bs2_default.elf.map
    CMakeFiles/bs2_default.dir/compile_time_choice.S.obj -o bs2_default.elf

/usr/bin/arm-none-eabi-objdump -h /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default.elf > bs2_default.dis
/usr/bin/arm-none-eabi-objdump -d /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default.elf >> bs2_default.dis

make  -f pico-sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default_padded_checksummed_asm.dir/build.make
    pico-sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default_padded_checksummed_asm.dir/build

cd /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2

/usr/bin/arm-none-eabi-objcopy -Obinary /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default.elf
    /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default.bin

/usr/bin/python3.11 /u1/Projects/rp2040/pico-sdk/src/rp2_common/boot_stage2/pad_checksum -s 0xffffffff
    /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default.bin
    /u1/Projects/rp2040/pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default_padded_checksummed.S


Have any comments? Questions? Drop me a line!

Tom's electronics pages / tom@mmto.org