みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Raspberry Pi Pico SDKでのFlashからのブートについて調べた

はじめに

私のベアメタルの心の師匠であるdwelch67さんが、さっそく何か作っていたのでパクる勉強するべく中身を眺めていた。

リンカスクリプトの下記の部分のLENGTH = 0xFCが引っかかったので調べてみた。

MEMORY
{
    flash : ORIGIN = 0x10000000, LENGTH = 0xFC
}

0xFCということは252バイトで、プログラムのサイズがこれしか書けないのだろうか?

READMEを読むと、252に関しては言及している。

This leaves 252 bytes for the second stage bootloader.

このサイズはthe second stage bootloaderがそうでなければならないということらしい。

最初の方をきちんと読むと下記のことがわかる。

  1. PicoはFlash-ROMから起動する場合先頭の256バイトをSRAMにコピーする。
  2. CRC32でデータをチェックし、正しい値が読めるまで繰り返す。
  3. 最終的に正しくCRCチェックが通らない場合は起動に失敗する。

RP2040のFlash-ROMブートシーケンス

RP2040 DatasheetFigure 15にブートシーケンスのチャートがある。

この部分が先ほどの記述の部分だとわかる。

f:id:mickey_happygolucky:20210210004214p:plain
CRCチェックのフロー

2.8.2.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.

CRCが最後の4バイトで、検査対象が先頭の252バイトということらしい。

dwelch67さんのこのコードはこのthe second stage bootloaderの部分でLチカをしてしまおうということっぽい(すごい)。

Pico SDKでの扱い

pico-examplesのblinkのように、Pico SDKでビルドされるアプリはどの様になっているのだろうか。

blinkのCMakeLists.txt

blinkのCMakeLists.txtを見てみる。

add_executable(blink
        blink.c
        )

# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(blink pico_stdlib)

# create map/bin/hex file etc.
pico_add_extra_outputs(blink)

# add url via pico_set_program_url
example_auto_set_url(blink)

なるほど。わからん。

そもそもスタートアップルーチンやらリンカスクリプトなどはこの層には公開されていない。

SDKリンカスクリプト

Pico SDKリンカスクリプトがないか探してみる。

$ $ find -name '*.ld' | head
./src/rp2_common/boot_stage2/boot_stage2.ld
./src/rp2_common/pico_standard_link/memmap_copy_to_ram.ld
./src/rp2_common/pico_standard_link/memmap_blocked_ram.ld
./src/rp2_common/pico_standard_link/memmap_default.ld
./src/rp2_common/pico_standard_link/memmap_no_flash.ld

関係ありそうなのはこの5つ。boot_stage2.ldthe second stage bootloaderのことのようだ。つまりSDK自体はこのローダのコードを持っている。

boot_stage2.ld

boot_stage2.ldの中身を見てみる。LENGTH = 252で、これがFlashの先頭に書かれるプログラムと見て良さそう。

MEMORY {
    /* We are loaded to the top 256 bytes of SRAM, which is above the bootrom
       stack. Note 4 bytes occupied by checksum. */
    SRAM(rx) : ORIGIN = 0x20041f00, LENGTH = 252
}

SECTIONS {
    . = ORIGIN(SRAM);
    .text : {
        *(.entry)
        *(.text)
    } >SRAM
}

memmap_default.ld

memmap_default.ldを見てみる。

SDKでアプリを作る時にデフォルトで使用されるリンカスクリプトはこれっぽい。

...(snip)...

MEMORY
{
    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
    RAM(rwx) : ORIGIN =  0x20000000, LENGTH = 256k
    SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
    SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
}

ENTRY(_entry_point)

SECTIONS
{
    /* Second stage bootloader is prepended to the image. It must be 256 bytes big
       and checksummed. It is usually built by the boot_stage2 target
       in the Raspberry Pi Pico SDK
    */

    .flash_begin : {
        __flash_binary_start = .;
    } > FLASH

    .boot2 : {
        __boot2_start__ = .;
        KEEP (*(.boot2))
        __boot2_end__ = .;
    } > FLASH

    ASSERT(__boot2_end__ - __boot2_start__ == 256,
        "ERROR: Pico second stage bootloader must be 256 bytes in size")

...(snip)...

Flashの先頭に.boot2セクションが置かれるようになっていて、このセクションの先頭から末尾までのサイズが256バイトじゃないとエラーになる。

ビルドしてelfを作った時に、.boot2セクションにthe second stage bootloaderのコードが入るようになっているようす。

.boot2セクションについて

この.boot2セクションはどこから来るのか。

$ grep -r '\.boot2' .
./src/rp2_common/boot_stage2/pad_checksum:50:        ofile.write(".section .boot2, \"ax\"\n\n")
./src/rp2_common/pico_standard_link/memmap_copy_to_ram.ld:45:    .boot2 : {
./src/rp2_common/pico_standard_link/memmap_copy_to_ram.ld:47:        KEEP (*(.boot2))
./src/rp2_common/pico_standard_link/memmap_blocked_ram.ld:45:    .boot2 : {
./src/rp2_common/pico_standard_link/memmap_blocked_ram.ld:47:        KEEP (*(.boot2))
./src/rp2_common/pico_standard_link/memmap_default.ld:45:    .boot2 : {
./src/rp2_common/pico_standard_link/memmap_default.ld:47:        KEEP (*(.boot2))

pad_checksumで埋め込んでいるっぽい。これの中身を確認する。

#!/usr/bin/env python3

...(snip)...
try:
    with open(args.ofile, "w") as ofile:
        ofile.write("// Padded and checksummed version of: {}\n\n".format(args.ifile))
        ofile.write(".cpu cortex-m0plus\n")
        ofile.write(".thumb\n\n")
        ofile.write(".section .boot2, \"ax\"\n\n")
        for offs in range(0, len(odata), 16):
            chunk = odata[offs:min(offs + 16, len(odata))]
            ofile.write(".byte {}\n".format(", ".join("0x{:02x}".format(b) for b in chunk)))

pythonスクリプトCRCチェックサムの計算をしている。ついでに、対象ファイルに.boot2セクションとしてローダのコードとCRCを埋めているようだ。

このスクリプトはどこから実行されるのか。

$ grep -r 'pad_checksum'  .
./src/rp2_common/boot_stage2/CMakeLists.txt:48:            COMMAND ${Python3_EXECUTABLE} ${PICO_BOOT_STAGE2_DIR}/pad_checksum -s 0xffffffff ${ORIGINAL_BIN} ${PADDED_CHECKSUMMED_ASM}

ローダのCMakeLists.txtで下記のようになっている。

...(snip)...

    set(PADDED_CHECKSUMMED_ASM ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_padded_checksummed.S)

...(snip)...

    add_custom_command(OUTPUT ${PADDED_CHECKSUMMED_ASM} DEPENDS ${ORIGINAL_BIN}
            COMMAND ${Python3_EXECUTABLE} ${PICO_BOOT_STAGE2_DIR}/pad_checksum -s 0xffffffff ${ORIGINAL_BIN} ${PADDED_CHECKSUMMED_ASM}
            )

...(snip)...

一度ビルドして、そのバイナリデータとCRCを含んだ.boot2のセクションの情報を${NAME}_padded_checksummed.Sとして出力している。

この出力結果を確認する。

// Padded and checksummed version of: /home/mickey/work/rpi_pico/pico/picoprobe/build/pico-sdk/src/rp2_common/boot_stage2/bs2_default.bin

.cpu cortex-m0plus
.thumb

.section .boot2, "ax"

.byte 0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60
.byte 0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b, 0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61
.byte 0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49, 0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20
.byte 0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42, 0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0
.byte 0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66, 0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0
.byte 0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e, 0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21
.byte 0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60, 0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60
.byte 0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21, 0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21
.byte 0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21, 0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60
.byte 0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28, 0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49
.byte 0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88, 0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20
.byte 0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42, 0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66
.byte 0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e, 0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40
.byte 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00
.byte 0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a

なるほど。これをアプリを作る時にリンクしている。

まとめ

Raspberry Pi PicoでFlashから起動する際の仕組みを調べた。

  • RP2040はFlashからプログラムを起動する場合、最初に先頭の256バイトを読み込む
  • その際にCRCチェックされる
  • 通常では、CRCを除く先頭の252バイトのプログラムが残りをロードするためのローダであることが期待される
  • Pico SDKではboot_stage2として実装されている
  • Flashから起動するアプリケーションをビルドする時に.boot2セクションにこのコードが埋め込まれる