はじめに
私のベアメタルの心の師匠である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
がそうでなければならないということらしい。
最初の方をきちんと読むと下記のことがわかる。
- PicoはFlash-ROMから起動する場合先頭の256バイトをSRAMにコピーする。
- CRC32でデータをチェックし、正しい値が読めるまで繰り返す。
- 最終的に正しくCRCチェックが通らない場合は起動に失敗する。
RP2040のFlash-ROMブートシーケンス
RP2040 DatasheetのFigure 15
にブートシーケンスのチャートがある。
この部分が先ほどの記述の部分だとわかる。
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のリンカスクリプト
$ $ 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.ld
がthe 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から起動する際の仕組みを調べた。