公開日: 2024年7月16日

μT-Kernel 3.0 BSP の boot2 セクションはどこからやって来た? (前編)

Raspberry Pico (以下 Pico) 向け μT-Kernel 3.0 BSP のリンカスクリプトを眺めると、外部フラッシュメモリの先頭に boot2 というセクションが配置されています。その実体を探ると、boot_2nd.c というファイルで定義されているboot2[]という配列に格納された、数値の羅列であることがわかります。これが一体どこから来たのか?を調べました。

本稿を読むうえでの約束事

はじめに結論

順を追っていくと長くなるので、先に結論を書いておきます。
  1. boot_2nd.c の配列boot2[]は、Pico SDK のセカンド・ステージ・ブート・ローダのオブジェクトコードを格納したもの。
  2. セカンド・ステージ・ブート・ローダのソースコードは Pico SDK の pico-sdk/src/rp2_common/boot_stage2/boot2_w25q080.S

セカンド・ステージ・ブート・ローダってなに?という方は [1][2] などを参照してください。外部フラッシュメモリの先頭 256 バイトに格納されており、ブートシーケンスのフラッシュ・セカンド・ステージにて Quad SPI 通信を使って読み出されます。

1. について本当にそうなのか確認してみましょう。Pico Examples に含まれる blink をビルドし、.boot2セクションのデータをダンプしてみます。

% arm-none-eabi-objdump -s --section=.boot2 blink.elf

blink.elf:     file format elf32-littlearm

Contents of section .boot2:
 10000000 00b5324b 21205860 98680221 88439860  ..2K! X`.h.!.C.`
 10000010 d8601861 58612e4b 00219960 02215961  .`.aXa.K.!.`.!Ya
 10000020 0121f022 99502b49 19600121 99603520  .!.".P+I.`.!.`5
 ...
 100000f0 00000000 00000000 00000000 74b24e7a  ............t.Nz

外部フラッシュメモリの先頭 (0x10000000 番地) から0x100000ff 番地まで、256 バイト分のデータが表示されています。エンディアンに注意して眺めれば、boot_2nd.c の配列とデータが一致していることがわかると思います。

2. については、このあと長々と説明します。

概要

説明に入る前に、Pico Examples と Pico SDK のうち、本稿で話す範囲を図 1 に示しておきます。背景に灰色で色をつけたあたりを今回やります。それ以外のところは次回やります。

図 1
図 1

当該ソースファイルの目星をつける

Pico に搭載されている外部フラッシュ・メモリは、Pico のデータシート [3] Appendix B の回路図から W25Q16JVUXIQ であることがわかります。そこで、「W25WQ16」などのキーワードで Pico SDK のソースコードを grep すれば、pico-sdk/src/rp2_common/boot_stage2/boot2_w25q080.S がヒットします。このファイルの先頭のコメントから、これがセカンド・ステージ・ブート・ローダのソースコードとみて間違いないでしょう。

ところが、じゃあ boot2_w25q080.S がアセンブルされてリンクされるんだなと思って、ビルドを行ったディレクトリでオブジェクトファイルを探しても、boot2_w25q080.o とか boot2_w25q080.obj みたいなファイルは見つかりません。代わりに、boot2_w25q080.S と同じディレクトリにあった compile_time_choice.S からアセンブルされたと思われる compile_time_choice.S.obj が見つかります (場所は pico-examples/build/pico-sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default.dir)。

どうやら compile_time_choice.S がその名のとおり、ビルド時にコンパイルするソースファイルを boot2_*.S の中から選んでいるようです。実際、compile_time_choice.S を覗いてみると、マクロPICO_BOOT_STAGE2_ASMにセットされたファイルをインクルードするようになっています。というわけでPICO_BOOT_STAGE2_ASMにセットされるのはどのファイルなのか?を探ることにします。もちろん、それが boot2_w25q080.S であることを期待しています。

ユーザー・アプリケーション (ここでは Pico Examples の blink) が Pico SDK を呼び出すところから出発しましょう。Pico SDK を使用するための設定が、Pico SDK README.md の Quick-start your own project 第 2 項に書かれています。ここには 4 パターンの方法が挙げられていますが、 Pico Examples では 1 つ目のやり方でやっています。となると、まず読むべきは Pico Examples のトップディレクトリにある CMakeLists.txt です。

pico-examples/CMakeLists.txt より引用
...

# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)
...

# Initialize the SDK
pico_sdk_init()
...

# Add blink example
add_subdirectory(blink)
...
出典: raspberrypi/pico-examples (GitHub)

※ 以下、括弧なしの行番号はオリジナル・ソースファイルの行番号、括弧ありの行番号は引用部分の行番号を表します。

4 行目 (4 行目) で pico-examples/pico_sdk_import.cmake をインクルードします。CMake では、インクルードされたファイルはその時点で読み出されて実行されます。インクルードされた pico_sdk_import.cmake は、さらに pico-sdk/pico_sdk_init.cmake をインクルードしています。これによりpico_sdk_init()マクロを Pico Examples 側から呼び出すことが可能になります。

実際にpico_sdk_init() を呼び出しているのが 19 行目 (8 行目) です。これにより、環境変数PICO_SDK_PATHにセットされたディレクトリがadd_subdirectory()されます。CMake では、add_subdirectory()されたディレクトリはプロジェクトツリーに追加され、その中に置かれている CMakeLists.txt が直ちに実行されます。PICO_SDK_PATHは事前にセットしてあり、その値は../../pico-sdkです (前出の「本稿を読むうえでの約束事」を参照)。つまり、この時点で pico-sdk/CMakeLists.txt が実行されることになります。

(pico-sdk/CMakeLists.txt を処理したのち) 23 行目 (12 行目) で blink ディレクトリをadd_subdirectory()して、pico-examples/blink/CMakeLists.txt を実行します。blink の CMakeLists.txt では、blink が pico_stdlib というインターフェース・ライブラリに依存していることの記述があります。

pico-examples/blink/CMakeLists.txt より引用
add_executable(blink
        blink.c
        )

# pull in common dependencies
target_link_libraries(blink pico_stdlib)
...
出典: raspberrypi/pico-examples (GitHub)

ここでのポイントは次の 2 点になります。

  • Pico Examples 側からpico_sdk_init()マクロが呼ばれている。
  • blink が Pico SDK のインターフェース・ライブラリ pico_stdlib に依存している。

Pico SDK の初期化 (pico-sdk/pico_sdk_init.cmake)

先ほど登場した pico_sdk_init.cmake をもう少し詳しく見てみましょう。

pico-sdk/pico_sdk_init.cmake より引用
...

include(pico_pre_load_platform)
...

macro(pico_sdk_init)
    if (NOT CMAKE_PROJECT_NAME)
        message(WARNING "pico_sdk_init() should be called after the project is created (and languages added)")
    endif()
    add_subdirectory(${PICO_SDK_PATH} pico-sdk)
endmacro()
...
出典: raspberrypi/pico-sdk (GitHub)

45 行目 (3 行目) で pico-sdk/cmake/pico_pre_load_platform.cmake をインクルードしています。このファイルの中で、PICO_PLATFORMという変数に値をセットしています。デフォルトはrp2040です。次の節で出てくるので、頭の片隅に入れておきましょう。

ところで、CMake の変数にもスコープ (適用範囲) があります。本稿では割愛しますが、詳細は公式ドキュメント [4] を参照してください。また、pico-sdk/pico_sdk_init.cmake で定義されているpico_register_common_scope_var()およびpico_promote_common_scope_vars()という 2 つのマクロも参考になると思います。

51 ~ 56 行目 (6 ~ 11 行目) でpico_sdk_init()マクロを定義しています。やっていることは簡単で、環境変数PICO_SDK_PATHで指定されたディレクトリをadd_subdirectory()コマンドでプロジェクトに追加しています。前の節でも触れたとおり、環境変数PICO_SDK_PATHには、すでに../../pico-sdkがセットされています。これで、blink から Pico SDK を使用する準備が整いました。

Pico SDK に突入!

ここからは Pico SDK の説明に入ります。まず、先ほどpico_sdk_init()マクロを実行したことで pico-sdk ディレクトリがadd_subdirectory()され、pico-sdk/CMakeLists.txt が読み込まれます。その 42 行目では、さらに pico-sdk/src ディレクトリをadd_subdirectory()しています。というわけで pico-sdk/src/CMakeLists.txt を見てみましょう。

pico-sdk/src/CMakeLists.txt より引用
if (NOT PICO_PLATFORM_CMAKE_FILE)
    set(PICO_PLATFORM_CMAKE_FILE ${CMAKE_CURRENT_LIST_DIR}/${PICO_PLATFORM}.cmake CACHE INTERNAL "")
endif ()
...

# Initialize board related build/compile settings
include(${CMAKE_CURRENT_LIST_DIR}/board_setup.cmake)
...

include(${PICO_PLATFORM_CMAKE_FILE})
...
出典: raspberrypi/pico-sdk (GitHub)

6 行目 (2 行目) のCMAKE_CURRENT_LIST_DIRは CMake の組込み変数で、今まさに処理している CMakeLists.txt が置かれているディレクトリ (この場合は pico-sdk/src) の絶対パスが格納されています。そのとなりの変数PICO_PLATFORMには、上で書いたとおりrp2040が格納されています。

これらのことを踏まえると、ここでやっていることは次の 2 点になります。

board_setup.cmake でやっていること

このファイルでやっていることは、使用するマイコンボードのボード名を変数PICO_BOARDにセットすることです。自分で選ぶ場合は、pico-sdk/src/boards/include/boards に収められているヘッダファイルの中から選んで、拡張子 ".h" を除いた文字列を環境変数PICO_BOARDにセットしておけばよいようです (2,3 行目)。何も指定しなければ、デフォルトのpicoになります (7 行目)。ここでセットされたファイルは、次に出てくる generic_board.cmake から参照されます。ちなみに、このディレクトリにある pico.h を見てみると、67 行目で次のように定義されており、フラッシュ・セカンド・ステージのソースコードが boot2_w25q080.S であろうことが感じられます。

pico-sdk/src/boards/include/boards/pico.h より引用
#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1
出典: raspberrypi/pico-sdk (GitHub)

21 ~ 29 行目では、ボードごとに専用の CMake スクリプトをインクルードしています。PICO_BOARD_CMAKE_DIRSで指定されたディレクトリ (デフォルトでは pico-sdk/src/boards) で ${PICO_BOARDS}.cmake をサーチし、存在すればそのファイルをインクルードします。存在しなければ、pico-sdk/src/boards/generic_board.cmake をインクルードします。

最後に 31 行目で ${CMAKE_CURRENT_LIST_DIR}/boards/include (つまり pico-sdk/src/boards/include) をリストPICO_INCLUDE_DIRSに加えています (リストについては CMake リファレンス・マニュアル [5] を参照)。PICO_INCLUDE_DIRSに登録されたディレクトリは、インターフェース・ライブラリ pico_base_headers の Usage Requirements として追加されます。

blink と pico_base_headers の間には、target_link_libraries()コマンド、あるいはpico_mirrored_target_link_libraries()関数を使って次のような依存関係が定義されています。

blink-dependencies

(*) pico-sdk/src/rp2_common/ からの相対パス

よって pico_base_headers の Usage Requirements が blink まで伝播して、blink のビルドの際に pico-sdk/src/boards/include がインクルード・パスに追加され、変数PICO_BOARDで指定したヘッダファイルをインクルードすることができるようになります。

generic_board.cmake でやっていること

このファイルのおもな目的は、board_setup.cmake で選択された ${PICO_BOARD}.h をサーチし、存在すればリストPICO_CONFIG_HEADER_FILESに追加することです (11 ~ 15 行目)。このリストに登録されたヘッダファイルは、ビルド時に生成される ${CMAKE_BINARY_DIR}/generated/pico_base/pico/config_autogen.h からインクルードされるのですが、その説明は次回に譲ります。

この先もまだまだ、重箱の隅をつつくような話が続くので、ここで一旦休憩とします。次回は pico-sdk/src/rp2040.cmake から先をやります。

参考資料

[1]CQ 出版『インターフェース』2023 年 7 月号 p.51
[2]RP2040 データシート 2.8.1 節
https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
[3]Raspberry Pi Pico データシート
https://datasheets.raspberrypi.com/pico/pico-datasheet.pdf
[4]CMake Reference Manuals - set
https://cmake.org/cmake/help/latest/command/set.html
[5]CMake Reference Manuals - List
https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#lists
Raspberry Pi Pico 実験室

広告