μT-Kernel 3.0 BSP の boot2 セクションはどこからやって来た? (後編)
掲題の件に関して、前回提示した結論のその 2 「セカンド・ステージ・ブート・ローダのソースコードは Pico SDK の pico-sdk/src/rp2_common/boot_stage2/boot2_w25q080.S である」ことを導くべく、続きをやっていきます。
目次
概要
Pico Examples と Pico SDK のうち、本稿で話す範囲を図 1 に示しておきます (色のつけ方を除けば、前回と同じ図です)。背景に灰色で色をつけたあたりを今回やります。また、前回記事の「本稿を読むうえでの約束事」も一度目を通していただければと思います。
pico-sdk/src/rp2_common.cmake
前回は、pico-sdk/src ディレクトリにおいて CMakeLists.txt が rp2040.cmake をインクルードし、さらに rp2040.cmake が rp2_common.cmake をインクルードしています、というところで終わりました。今回はこの rp2_common.cmake から始めます。といってもこのファイルで注目したいのは 52 行目、pico-sdk/src/rp2_common ディレクトリをadd_subdirectory()
しているところだけです。よって次に読むファイルは pico-sdk/src/rp2_common/CMakeLists.txt となります。
pico-sdk/src/rp2_common ディレクトリ
その pico-sdk/src/rp2_common/CMakeLists.txt を見てみます。
pico-sdk/src/rp2_common/CMakeLists.txt より引用出典: raspberrypi/pico-sdk (GitHub)option(PICO_NO_FLASH "Default binaries to not not use flash") option(PICO_COPY_TO_RAM "Default binaries to Copy code to RAM when booting from flash") ... if (NOT PICO_BARE_METAL) # NOTE THE ORDERING HERE IS IMPORTANT AS SOME TARGETS CHECK ON EXISTENCE OF OTHER TARGETS pico_add_subdirectory(boot_stage2) ... pico_add_subdirectory(pico_stdlib) ... pico_add_subdirectory(pico_standard_link) ... endif() ...
※ 以下、括弧なしの行番号はオリジナル・ソースファイルの行番号、括弧ありの行番号は引用部分の行番号を表します。
1,2 行目で 2 つのユーザオプションを作成しています。
PICO_NO_FLASH
: 外部フラッシュ・メモリを使用しない (デフォルト:OFF
)。PICO_COPY_TO_RAM
: 外部フラッシュ・メモリからブートする際に、コードを RAM にコピーする (デフォルト:OFF
)。
option()
は、ブーリアン値をとるビルドオプションを作るための CMake のコマンドです [1]。初期値を与えなければデフォルトはOFF
となります。
36 行目 (5 行目) のif
文は、変数PICO_BARE_METAL
がデフォルトでは0
(偽) であるので、特に指定がなければ True パートに入ります。その True パートの中では、まず 38 行目 (7 行目) で boot_stage2 ディレクトリをpico_add_subdirectory()
しています (ついにセカンド・ステージの本丸っぽい名前のディレクトリが出てきました)。次節でより詳しく見ることにします。なお、pico_add_subdirectory()
はadd_subdirectory()
コマンドのラッパー関数であり、ここではadd_subdirectory()
コマンドとだいたい同じものと考えておいて差し支えありません。
それから、72 行目 (10 行目) で pico_stdlib ディレクトリを、75 行目 (13 行目) で pico_standard_link ディレクトリをpico_add_subdirectory()
しています。それぞれのディレクトリにある CMakeLists.txt の中で、他のインターフェース・ライブラリとの依存関係を定義しています。これらのディレクトリは、あとのほうでもう一度出てきます。
pico-sdk/src/rp2_common/boot_stage2 ディレクトリ
それではいよいよ、boot_stage2 ディレクトリの中を見ていきましょう。
CMakeLists.txt
まずは CMakeLists.txt です。いくつかの変数の初期設定を行い、このディレクトリにある include ディレクトリをインクルード・パスに追加したのち、pico_define_boot_stage2()
という名前の関数を定義しています。
pico-sdk/src/rp2_common/boot_stage2/CMakeLists.txt より引用出典: raspberrypi/pico-sdk (GitHub)... set(PICO_BOOT_STAGE2_COMPILE_TIME_CHOICE_NAME compile_time_choice) # local var if (NOT PICO_DEFAULT_BOOT_STAGE2_FILE) ... if (NOT DEFINED PICO_DEFAULT_BOOT_STAGE2) set(PICO_DEFAULT_BOOT_STAGE2 ${PICO_BOOT_STAGE2_COMPILE_TIME_CHOICE_NAME}) endif() set(PICO_DEFAULT_BOOT_STAGE2 "${PICO_DEFAULT_BOOT_STAGE2}" CACHE STRING "boot stage 2 short name" FORCE) set(PICO_DEFAULT_BOOT_STAGE2_FILE "${CMAKE_CURRENT_LIST_DIR}/${PICO_DEFAULT_BOOT_STAGE2}.S") endif() ... # needed by function below set(PICO_BOOT_STAGE2_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "") add_library(boot_stage2_headers INTERFACE) target_include_directories(boot_stage2_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) ... # by convention the first source file name without extension is used for the binary info name function(pico_define_boot_stage2 NAME SOURCES) ... endfunction() ...
12 ~ 23 行目 (3 ~ 12 行目) で、変数PICO_DEFAULT_BOOT_STAGE2
およびPICO_DEFAULT_BOOT_STAGE2_FILE
の値を決めています。デフォルトでは次の値がセットされます。
PICO_DEFAULT_BOOT_STAGE2
:compile_time_choice
PICO_DEFAULT_BOOT_STAGE2_FILE
:pico-sdk/src/rp2_common/boot_stage2/compile_time_choice.S
PICO_DEFAULT_BOOT_STAGE2_FILE
の値は、このあと出てくるpico_define_boot_stage2()
関数に渡されます。前回の最初のほうでちらっと出てきた compile_time_choice.S がここで登場しました。
31 行目 (16 行目) では、変数PICO_BOOT_STAGE2_DIR
に値をセットします。この変数には、compile_time_choice.S のビルドに使うリンカスクリプトや、パディング、CRC 付加を行うスクリプトファイルが収められているディレクトリを指定します。デフォルトでpico-sdk/src/rp2_common/boot_stage2
となります。
33,34 行目 (18,19 行目) では、インターフェース・ライブラリ boot_stage2_headers を作成し、Usage Requirements としてインクルードディレクトリ pico-sdk/src/rp2_common/boot_stage2/include をセットします。この記述により、compile_time_choice.S が pico-sdk/src/rp2_common/boot_stage2/include/boot_stage2/config.h をインクルードすることができるようになります。
37 ~ 90 行目 (23 ~ 26 行目) はpico_define_boot_stage2()
関数の定義です。次の節で説明します。
pico_define_boot_stage2() 関数
この関数を読むと、ビルド時に compile_time_choice.S になにが起こるのかがだいたいわかるようになります。
pico-sdk/src/rp2_common/boot_stage2/CMakeLists.txt より引用出典: raspberrypi/pico-sdk (GitHub)# by convention the first source file name without extension is used for the binary info name function(pico_define_boot_stage2 NAME SOURCES) add_executable(${NAME} ${SOURCES} ) ... target_link_libraries(${NAME} hardware_regs boot_stage2_headers) target_link_options(${NAME} PRIVATE "LINKER:--script=${PICO_BOOT_STAGE2_DIR}/boot_stage2.ld") ... set(ORIGINAL_BIN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.bin) set(PADDED_CHECKSUMMED_ASM ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_padded_checksummed.S) ... add_custom_target(${NAME}_bin DEPENDS ${ORIGINAL_BIN}) add_custom_command(OUTPUT ${ORIGINAL_BIN} DEPENDS ${NAME} COMMAND ${CMAKE_OBJCOPY} -Obinary $<TARGET_FILE:${NAME}> ${ORIGINAL_BIN} VERBATIM) add_custom_target(${NAME}_padded_checksummed_asm DEPENDS ${PADDED_CHECKSUMMED_ASM}) 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} VERBATIM) add_library(${NAME}_library INTERFACE) add_dependencies(${NAME}_library ${NAME}_padded_checksummed_asm) ... target_link_libraries(${NAME}_library INTERFACE ${PADDED_CHECKSUMMED_ASM}) ... endfunction()
この関数はNAME
、SOURCES
という 2 つの仮引数をとります。デフォルトでは、それぞれ次の値が入ります (同じファイルの 101 行目を参照)。
NAME
:bs2_default
SOURCES
:PICO_DEFAULT_BOOT_STAGE2_FILE
(=pico-sdk/src/rp2_common/boot_stage2/compile_time_choice.S
)
38 ~ 40 行目 (3 ~ 5 行目) で、compile_time_choice.S をソースとして実行可能ファイル bs2_default.elf を作成するターゲットをプロジェクトに加えています。実行可能ファイルの拡張子は、CMake が自動で判断して付加してくれます [2]。ビルドの際に使用するリンカスクリプトは 54 行目 (9 行目) で boot_stage2.ld を指定しています。
60 行目 (12 行目) で変数ORIGINAL_BIN
に${CMAKE_CURRENT_BINARY_DIR}/${NAME}.bin
(つまりpico-sdk/src/rp2_common/boot_stage2/bs2_default.bin
) をセットします。また、次の行で変数PADDED_CHECKSUMMED_ASM
に${CMAKE_CURRENT_BINARY_DIR}/${NAME}_padded_checksummed.S
(つまりpico-sdk/src/rp2_common/boot_stage2/bs2_default_padded_checksummed.S
) をセットします。このあとを読んでいくとわかるのですが、ORIGINAL_BIN
は bs2_default.elf を objcopy するときのコピー先ファイル名であり、PADDED_CHECKSUMMED_ASM
はORIGINAL_BIN
に対して 256 バイトのパディングと CRC 付加を行った結果を出力するファイル名です。
65 行目 (16 行目) は bs2_default.bin を単独でビルドするためのターゲットと思われます。blink のビルドには直接は関係ありません。66 行目 (17 行目) で bs2_default.bin を生成するためのカスタム・コマンドを定義しています。コマンドの実体は bs2_default.elf から bs2_default.bin への objcopy (クロス開発用の objcopy) です。
69 行目 (20 行目) で bs2_default_padded_checksummed.S を生成するためのターゲット bs2_default_padded_checksummed_asm をプロジェクトに追加しています。ここではとりあえず「このターゲットを実行すると bs2_default_padded_checksummed.S が生成される」という点だけ押さえておいてください。どこから実行されるのかは「blink の依存関係をたどる」の節で説明します。
70 行目 (21 行目) は bs2_default_padded_checksummed.S を生成するためのカスタム・コマンドです。コマンドの実体は、pico-sdk/src/rp2_common/boot_stage2 に収められている pad_checksum という名前の Python スクリプトです。bs2_default.bin に対して 256 バイトまでパディングを行い、末尾の 4 バイトに CRC を付加します。出力ファイル名は bs2_default_padded_checksummed.S です。ちなみに、DEPENDS
オプションにORIGINAL_BIN
(bs2_default.bin
) を指定していますが、これは 66 行目 (17 行目) のadd_custom_command()
でOUTPUT
に指定されていたファイルです。この場合、70 行目で定義したコマンドが実行される際、66 行目で作ったコマンドも実行されるように CMake が図ってくれます [3]。
74,75 行目 (25,26 行目) でインターフェース・ライブラリbs2_default_library
を新規作成し、それが依存するターゲットとして、69 行目 (20 行目) で作ったbs2_default_padded_checksummed_asm
を指定します。これにより、bs2_default_library
のビルドの前にbs2_default_padded_checksummed_asm
がビルドされることが保証されます。
77 行目 (28 行目) で、target_link_libraries()
コマンドを使ってbs2_default_library
とPADDED_CHECKSUMMED_ASM
(=bs2_default_padded_checksummed.S
) との間の依存関係を定義しています。この行のおかげで、blink に bs2_default_padded_checksummed.S がリンクされる (つまり pico-examples/build/blink/CMakeFiles/blink.dir/link.txt に bs2_default_padded_checksummed.S が追加される) ことになります。blink とbs2_default_library
の間に依存関係があることは「blink の依存関係をたどる」の節で説明します。
bs2_default_padded_checksummed.S の中身を確認しておきましょう。このファイルは、blink をビルドすることで、ビルドディレクトリ (pico-examples/build) の下の pico-sdk/src/rp2_common/boot_stage2 ディレクトリに生成されます。その中身は、次のように compile_time_choice.S をアセンブル、パディング、CRC 付加処理したオブジェクトコードを.byte
疑似命令により直接記述したものです。
...
.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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a
compile_time_choice.S を眺める
前回の記事で「PICO_BOOT_STAGE2_ASM
にセットされる値がboot2_w25q080.S
であることを期待している」と書きましたが、ようやくそれを確認するときがやって来ました。
pico-sdk/src/rp2_common/boot_stage2/compile_time_choice.S より引用出典: raspberrypi/pico-sdk (GitHub)#include "boot_stage2/config.h" #ifdef PICO_BUILD_BOOT_STAGE2_NAME ... #else // boot stage 2 is selected by board config header, and PICO_BOOT_STAGE2_ASM is set in boot_stage2/config.h #include PICO_BOOT_STAGE2_ASM #endif
11 行目 (1 行目) で pico-sdk/src/rp2_common/boot_stage2/include/boot_stage2/config.h をインクルードしています (pico-sdk/src/rp2_common/boot_stage2/CMakeLists.txt の 33,34 行目の記述により、それが可能)。この config.h の中でPICO_BOOT_STAGE2_ASM
を定義していますが、その前にこのファイルを最後まで読んでしまいましょう。
13 行目 (3 行目) の#if
文の条件であるPICO_BUILD_BOOT_STAGE2_NAME
はデフォルトで未定義です (pico-sdk/src/rp2_common/boot_stage2/CMakeLists.txt 80 ~ 89 行目により、pico_define_boot_stage2()
に compile_time_choice.S 以外のファイルが入力された場合のみ定義される)。よって、上記引用部分は#else
側に入り、18 行目 (7 行目) でPICO_BOOT_STAGE2_ASM
にセットされているヘッダファイルがインクルードされることになります。
それでは、config.h を開いてPICO_BOOT_STAGE2_ASM
にセットされている値を確認してみましょう。
pico-sdk/src/rp2_common/boot_stage2/include/boot_stage2/config.h より引用出典: raspberrypi/pico-sdk (GitHub)#ifdef PICO_BUILD_BOOT_STAGE2_NAME ... #else // boot stage 2 is selected by board config header, so we have to do some work #if PICO_BOOT_STAGE2_CHOOSE_IS25LP080 ... #elif PICO_BOOT_STAGE2_CHOOSE_W25Q080 #define _BOOT_STAGE2 boot2_w25q080 #elif PICO_BOOT_STAGE2_CHOOSE_W25X10CL ... #endif // we can't include cdefs in assembly, so define our own, but avoid conflict with real ones for c inclusion #define PICO_BOOT_STAGE2_NAME __PICO_XSTRING(_BOOT_STAGE2) #define PICO_BOOT_STAGE2_ASM __PICO_XSTRING(__PICO_CONCAT1(_BOOT_STAGE2,.S)) #endif
前出のようにPICO_BUILD_BOOT_STAGE2_NAME
は未定義となるので、67 行目 (1 行目) の#if
文は#else
側に入ります。#else
側では、PICO_BOOT_STAGE2_CHOOSE_*
の値に応じて_BOOT_STAGE2
の値を切り替えています。デフォルトではPICO_BOOT_STAGE2_CHOOSE_W25Q080
が1
になるので (その証拠はこのあと)、_BOOT_STAGE2
の値は 75 行目 (8 行目) でboot2_w25q080
となり、さらに 88,89 行目 (13,14 行目) で次のように確定します。
PICO_BOOT_STAGE2_NAME
:boot2_w25q080
PICO_BOOT_STAGE2_ASM
:boot2_w25q080.S
よって compile_time_choice.S の 18 行目でインクルードされるのは、デフォルトでは boot2_w25q080.S となることがわかります。
PICO_BOOT_STAGE2_CHOOSE_W25Q080
の値が1
であることを確認しましょう。compile_time_choice.S を起点にインクルードしているヘッダファイルをたどれば、順に compile_time_choice.S、pico-sdk/src/rp2_common/boot_stage2/include/boot_stage2/config.h、pico-sdk/src/common/pico_base/include/pico.h (*1)、pico-sdk/src/common/pico_base/include/pico/config.h、pico-examples/build/generated/pico_base/pico/config_autogen.h (*1)、pico-sdk/src/boards/include/boards/pico.h (*2) となります。
(*1) | pico-sdk/src/common/pico_base/CMakeLists.txt の 3 行目により pico-sdk/src/common/pico_base/include と ${CMAKE_BINARY_DIR}/generated/pico_base がインターフェース・ライブラリ pico_base_headers の Usage Requirements に加えられる。pico_base_headers の Usage Requirements は前回の「board_setup.cmake でやっていること」に記載した依存関係により blink まで伝播するので、これらのファイルをインクルードすることができる。 |
(*2) | 次節参照。 |
この pico.h というファイル、前回の「board_setup.cmake でやっていること」や「generic_board.cmake でやっていること」の節にフライング登場して保留になっていたファイルです。このファイルの 67 行目を見れば、探し求めていた「PICO_BOOT_STAGE2_CHOOSE_W25Q080
の値は1
である」との定義を見つけることができます。
ここまででわかったことを図に描いておきましょう (図 2)。
config_autogen.h: ビルド時に自動生成されるヘッダファイル
ちょっと脱線になりますが、config_autogen.h について少し触れておきます。config_autogen.h はビルド時に自動生成されるヘッダファイルです。pico-examples/build/generated/pico_base/pico に出力されます。
config_autogen.h の中身
config_autogen.h の中では、他のいくつかのヘッダファイルをインクルードしています。本稿に関係あるところでいえば、リストPICO_CONFIG_HEADER_FILES
に登録されたヘッダファイルです。このリストには、デフォルトではすぐ上で出てきた pico.h だけが登録されています (前回の「generic_board.cmake でやっていること」参照)。その結果、次のように出力されます。
...
// based on PICO_CONFIG_HEADER_FILES:
#include "/home/mijinco/pico/repo/pico-sdk/src/boards/include/boards/pico.h"
...
config_autogen.h を生成する処理
config_autogen.h の生成処理が書かれているのは generate_config_header.cmake というファイルです。
pico-sdk/src/common/pico_base/generate_config_header.cmake より引用出典: raspberrypi/pico-sdk (GitHub)... macro(add_header_content_from_var VAR) set(header_content "${header_content}\n\n// based on ${VAR}:\n") foreach(var IN LISTS ${VAR}) set(header_content "${header_content}\n#include \"${var}\"") endforeach() endmacro() # PICO_CMAKE_CONFIG: PICO_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for all platforms, type=list, default="", group=pico_base add_header_content_from_var(PICO_CONFIG_HEADER_FILES) ... file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/generated/pico_base/pico/config_autogen.h CONTENT "${header_content}" ) ...
このファイルでは、config_autogen.h に出力したい文字列を変数header_content
に連結していって、最後にそれをfile(GENERATE)
コマンドで出力します。
6 ~ 11 行目 (3 ~ 8 行目) のマクロadd_header_content_from_var()
では、引数で渡されたリストに登録されているヘッダファイルを#include
文とともにheader_content
に連結します。このマクロを実際に呼び出しているのは 14 行目 (11 行目) です。引数のPICO_CONFIG_HEADER_FILES
にセットされている値はpico-sdk/src/boards/include/boards/pico.h
でした (前回の「generic_board.cmake でやっていること」参照)。
20 ~ 23 行目 (14 ~ 17 行目) でheader_content
の値を config_autogen.h に出力するように設定しています。こうして前節に示したような中身をもつ config_autogen.h が生成されます。
generate_config_header.cmake の呼び出し
では generate_config_header.cmake はどこから呼び出されるのか?前回は軽く流しましたが、pico-sdk/CMakeLists.txt の 42 行目 (3 行目) で pico/sdk/src ディレクトリをadd_subdirectory()
していました。その 4 行あとの 46 行目 (7 行目) に注目してください。
pico-sdk/CMakeLists.txt から引用出典: raspberrypi/pico-sdk (GitHub)... add_subdirectory(src) # allow customization add_sub_list_dirs(PICO_SDK_POST_LIST_DIRS) add_sub_list_files(PICO_SDK_POST_LIST_FILES) ...
add_sub_list_files()
というのは pico-sdk/pico_sdk_init.cmake で定義されているマクロで、引数で渡されたリストに登録されている CMake のリストファイルを順にインクルードします。つまりPICO_SDK_POST_LIST_FILES
にリストファイルを登録しておけば、pico-sdk/src/CMakeLists.txt の処理が済んだあとでそれを実行してくれるようになっています。
そこで、PICO_SDK_POST_LIST_FILES
にファイルを登録している場所を探すと、pico-sdk/src/common/pico_base/CMakeLists.txt の 11 行目で generate_config_header.cmake を登録しているのが見つかります。つまり、generate_config_header.cmake は pico-sdk/src/CMakeLists.txt の後処理として実行されるわけです。
blink の依存関係をたどる
さて、上のほうでお茶を濁しておいた、bs2_default_padded_checksummed.S を生成するターゲットがどのように実行されるのか?を確認します。もう疲れてきたのでささっと行きますが、例のtarget_link_libraries()
コマンドなんかを使って、blink から bs2_default_padded_checksummed.S まで次のような依存関係が定義されています。
(*1) | pico-sdk/src/rp2_common/ からの相対パス。 |
(*2) | ここを読み解くには Generator Expressions の知識が必要ですが、なくても何となく読めると思います。Generator Expressions についてはリファレンス・マニュアル [4] を参照してください。 |
この関係を図に描いたのが図 1 です。こうして、blink がビルドされる前には bs2_default_padded_checksummed.S が生成されていることが保証されます。
ずいぶん誌面を費やしましたが、これでセカンド・ステージ・ブート・ローダのソースコードが boot2_w25q080.S であって、blink をビルドするとそれがアセンブル、リンクされる流れを全部説明できたのではないかと思います (抜けがあったらすみません)。
bs2_default_padded_checksummed.S の配置先
最後に、セカンド・ステージ・ブート・ローダのオブジェクトコードが外部フラッシュ・メモリの先頭に配置されることの確認、つまりリンカスクリプトの確認をやっておきましょう。
まず bs2_default_padded_checksummed.S (既出) の先頭のほうを見ると、「.section .boot2, "ax"
」と書かれていることから、そのあとに続くオブジェクトコードが .boot2 セクションに配置されることがわかります。
そこで、.boot2 セクションの配置先をリンカスクリプトで確認します。blink のリンカスクリプトを指定しているのは pico-sdk/src/rp2_common/pico_standard_link/CMakeLists.txt の 58 ~ 60 行目です。リンカスクリプトのファイル名に Generator Expressions [4] を使うことで、コンフィグレーションによって差し替えできるように記述されています。ここはPICO_TARGET_LINKER_SCRIPT
やPICO_TARGET_BINARY_TYPE
などのプロパティがデフォルトでは未定義になることを考慮すると、デフォルトで選択されるリンカスクリプトは memmap_${PICO_DEFAULT_BINARY_TYPE}.ld となり、46 ~ 54 行目の処理を考慮して展開すれば、最終的に memmap_default.ld となります。その memmap_default.ld には次のように書かれています。
pico-sdk/src/rp2_common/pico_standard_link/memmap_default.ld より引用出典: raspberrypi/pico-sdk (GitHub)... MEMORY { FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k ... } ... SECTIONS { ... .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") ... }
26 行目 (5 行目) では、MEMORY
コマンドで外部フラッシュ・メモリの領域を定義しています。先頭は 0x10000000 番地、サイズは 2048KB。"rx" は読み取り可能かつ実行可能であることを表しています。
41 ~ 43 行目 (14 ~ 16 行目) では、.flash_begin という名前でダミーセクションを定義し、外部フラッシュ・メモリの先頭を指すようにしています。実際には何のデータも配置されません。
45 行目 (18 行目) で .boot2 という名前のセクションを定義し、46,48 行目 (19,21 行目) でこのセクションの先頭と末尾の番地を取得しています ("." は「ロケーション・カウンタ」といい、現在の番地が格納されています [5])。ここで取得した番地を使って、51,52 行目 (24,25 行目) でセクションのサイズをチェックしています (セカンド・ステージ・ブート・ローダのサイズは 256 バイトでなければならない [6])。
47 行目 (20 行目) で、すべてのオブジェクトファイル ("*") の .boot2 セクションを 45 行目で定義した .boot2 セクションに集約します。KEEP()
は、このセクションをガベージコレクションの対象から外すためのコマンドです [7]。
最後に、49 行目 (22 行目) で .boot2 セクションを 26 行目で定義した FLASH 領域に配置します。結果として、.boot2 セクションが外部フラッシュ・メモリの先頭に配置されることになります。
以上です、お疲れさまでした。
おまけ: Generator Expressions のデバッグ方法
先ほどからまあまあ出てくる Generator Expressions。これは CMakeLists.txt の処理段階ではなく、ビルドシステム (Makefile など) を生成する段階で評価されるので、普通の変数のようにmessage()
コマンドで中身を表示させることができません。CMake のリファレンス・マニュアルには、その対策が 2 通り紹介されています [4]。ここではそのうちfile(GENERATE)
コマンドを使った方法を紹介します。
例えば前の節で出てきた、リンカスクリプトを選択する処理。
pico-sdk/src/rp2_common/pico_standard_link/CMakeLists.txt (58 ~ 60 行目) から引用出典: raspberrypi/pico-sdk (GitHub)target_link_options(pico_standard_link INTERFACE "LINKER:--script=$<IF:$<BOOL:$<TARGET_PROPERTY:PICO_TARGET_LINKER_SCRIPT>>,$<TARGET_PROPERTY:PICO_TARGET_LINKER_SCRIPT>,${CMAKE_CURRENT_LIST_DIR}/memmap_$<IF:$<STREQUAL:$<TARGET_PROPERTY:PICO_TARGET_BINARY_TYPE>,>,${PICO_DEFAULT_BINARY_TYPE},$<TARGET_PROPERTY:PICO_TARGET_BINARY_TYPE>>.ld>" )
ここにはだいたい次のようなことが Generator Expressions で書かれています。
PICO_TARGET_LINKER_SCRIPT
プロパティが指定されていれば、それをリンカスクリプトとする。PICO_TARGET_BINARY_TYPE
プロパティが空なら、memmap_${PICO_DEFAULT_BINARY_TYPE
}.ld をリンカスクリプトとする。- 1. でも 2. でもない場合は memmap_$<
PICO_TARGET_BINARY_TYPE
プロパティの値>.ld をリンカスクリプトとする。
というわけで、PICO_TARGET_LINKER_SCRIPT
、PICO_TARGET_BINARY_TYPE
、PICO_DEFAULT_BINARY_TYPE
などの値が知りたくなります。このうちPICO_DEFAULT_BINARY_TYPE
はただの変数なのでmessage()
コマンドでデバッグすることができますが、他の 2 つは Generator Expressions なので、正しく表示するにはfile(GENERATE)
コマンドを使う必要があります。
この場合、例えば次のようなデバッグ用のコードを、58 行目のtarget_link_options()
コマンドの手前あたりに挿入してみます。
string(CONCAT DBGSTR
"PICO_TARGET_LINKER_SCRIPT = $<TARGET_PROPERTY:pico_standard_link,PICO_TARGET_LINKER_SCRIPT>\n"
"PICO_TARGET_BINARY_TYPE = $<TARGET_PROPERTY:pico_standard_link,PICO_TARGET_BINARY_TYPE>\n"
"PICO_DEFAULT_BINARY_TYPE = ${PICO_DEFAULT_BINARY_TYPE}\n"
"LINKER:--script = $<IF:$<BOOL:$<TARGET_PROPERTY:pico_standard_link,PICO_TARGET_LINKER_SCRIPT>>,$<TARGET_PROPERTY:pico_standard_link,PICO_TARGET_LINKER_SCRIPT>,${CMAKE_CURRENT_LIST_DIR}/memmap_$<IF:$<STREQUAL:$<TARGET_PROPERTY:pico_standard_link,PICO_TARGET_BINARY_TYPE>,>,${PICO_DEFAULT_BINARY_TYPE},$<TARGET_PROPERTY:pico_standard_link,PICO_TARGET_BINARY_TYPE>>.ld>\n"
)
file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/debug.txt CONTENT ${DBGSTR})
ここでの注意点として、$<TARGET_PROPERTY:
なんちゃら>
構文を使うときは、59 行目にあるような$<TARGET_PROPERTY:prop>
ではなく$<TARGET_PROPERTY:tgt,prop>
を使って、プロパティの持ち主 (ターゲット) を指定する必要があります (59 行目でターゲットを省略できる理由は、target_link_options()
コマンドの中で使っているので、ターゲットが明らかなためだと思います)。こうすると、CMake を実行して Makefile が生成されるときに、ビルドディレクトリ (pico-examples/build) に debug.txt が出力されます。特に何も指定せず、デフォルトのままビルドしたのなら、次のような結果になるでしょう。
PICO_TARGET_LINKER_SCRIPT =
PICO_TARGET_BINARY_TYPE =
PICO_DEFAULT_BINARY_TYPE = default
LINKER:--script = /home/mijinco/pico/repo/pico-sdk/src/rp2_common/pico_standard_link/memmap_default.ld
こうして実動作からも memmap_default.ld が選択されていることを確認することができました。
参考資料
[1] | CMake Reference Manuals - option https://cmake.org/cmake/help/latest/command/option.html#option |
[2] | CMake Reference Manuals - add_executable https://cmake.org/cmake/help/latest/command/add_executable.html |
[3] | CMake Reference Manuals - add_custom_command https://cmake.org/cmake/help/latest/command/add_custom_command.html |
[4] | CMake Reference Manuals - cmake-generator-expressions(7) https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#debugging |
[5] | LD (Documentation for binutils 2.42) - The Location Counter https://sourceware.org/binutils/docs-2.42/ld/Location-Counter.html#index-location-counter |
[6] | RP2040 データシート 2.8.1 節 https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf |
[7] | LD (Documentation for binutils 2.42) - Input Section and Garbage Collection https://sourceware.org/binutils/docs-2.42/ld/Input-Section-Keep.html#index-KEEP |