raspberry pi pico の main() までの流れ
2022/03/01, last updated 2022/08/03 - ~3 Minutes
フラッシュプログラムへの分岐に関する疑問
raspberry pi pico sdk examples の blink は簡単に実行できるが、main にはどのように分岐しているのだろうか。また、例外ハンドラー(割り込みハンドラー)はどのようにセットされているのだろうか。
典型的には、メモリの先頭(0番地)に書かれているアドレスにジャンプする。起動時にはメモリの先頭にマイコンに内蔵されているフラッシュメモリ等が見えるので、0番地に書き込まれているアドレスにジャンプしてプログラムの実行が始まる。
ARM は先頭がスタックポインタで、その次がリセットベクター(リセット時に実行される番地)になっている。リセットされると、まず、bootrom(チップに書き込まれている?)が実行され、bootrom の最後でフラッシュに書き込まれたプログラムが起動するようだ。
bootrom の動作
電源 ON からの起動は以下に解説されている方がおられた。
RP2040 Datasheet の 2.7〜2.8 章にも説明がある。
bootrom が boot2 という小さなプログラムを sram に転送して実行し、boot2 からフラッシュプログラムに分岐している?(完全に追えていない)
boot2 からフラッシュプログラムへの分岐
bootrom のソースは、github の pico-bootrom を参照できる。このソースは pico-sdk も参照しているようだ。
辿っていくと、 pico-sdk/src/rp2_common/boot_stage2/asminclude/boot2_helpers/exit_from_boot2.S あたりが boot2 からフラッシュに書きこまれたプログラムに分岐するところだろう。
/* * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _BOOT2_HELPER_EXIT_FROM_BOOT2
#define _BOOT2_HELPER_EXIT_FROM_BOOT2
#include "hardware/regs/m0plus.h"
// If entered from the bootrom, lr (which we earlier pushed) will be 0,
// and we vector through the table at the start of the main flash image.
// Any regular function call will have a nonzero value for lr.
check_return:
pop {r0}
cmp r0, #0
beq vector_into_flash
bx r0
vector_into_flash:
ldr r0, =(XIP_BASE + 0x100)
ldr r1, =(PPB_BASE + M0PLUS_VTOR_OFFSET)
str r0, [r1]
ldmia r0, {r0, r1}
msr msp, r0
bx r1
#endif
XIP_BASE は 0x10000000 と定義されているので、blink.dis 10000100〜 を見てみる。
10000100 <__VECTOR_TABLE>:
10000100: 20042000 .word 0x20042000
10000104: 100001f7 .word 0x100001f7
10000108: 100001c3 .word 0x100001c3
1000010c: 100001c5 .word 0x100001c5
10000110: 100001c1 .word 0x100001c1
10000114: 100001c1 .word 0x100001c1
要約すると、
- ベクターテーブルを 0x10000100 にセット(RP2040 Datasheet M0PLUS: VTOR Register 参照)
- ベクターテーブルの先頭(0x10000100に書かれている内容)を msp(スタックポインタ)にセット
- その次のベクターテーブル(0x1000104に書かれている内容) に分岐 という動きになる。
しかし、0x1000104 〜の内容、なぜ奇数になっているのだろうか。
ARMv6-M Architecture Reference Manual によると、B1.5.3 の最後のほうに、
All other entries must have bit [0] set to 1,
とある。最下位ビットは 1 でなくてはならないそうだ。
そういうわけで、0x100001f6 に分岐することになる。0x100001f6 〜は blink.dis を見ると、
100001f6 <_reset_handler>:
となっているので、_reset_handler から実行される。
_reset_handler を下のほうに見ていくと、platform_entry というラベルが見つかる。
pico-sdk/src/rp2_common/pico_standard_link/crt0.S 内の platform_entry だろう。
platform_entry: // symbol for stack traces
// Use 32-bit jumps, in case these symbols are moved out of branch range
// (e.g. if main is in SRAM and crt0 in flash)
ldr r1, =runtime_init
blx r1
ldr r1, =main
blx r1
ldr r1, =exit
blx r1
// exit should not return. If it does, hang the core.
// (fall thru into our hang _exit impl
.weak _exit
.type _exit,%function
.thumb_func
_exit:
1: // separate label because _exit can be moved out of branch range
bkpt #0
b 1b
となっている。
- runtime_init をコール
- main をコール
- exit をコール
- exit はリターンしないが、戻ってきた場合は無限にブレーク
という流れだろうか。
まとめ
私の一番の疑問は、一般には、内蔵 ROM の先頭にある例外ハンドラーがどのように設定されるか、だった。
raspberry pi pico には、vector table offset register (VTOR) というものがあるので、 boot2 がフラッシュ上のアドレスを指すように VTOR を設定している、という理解で合っているだろうか。
昔、プログラム書き換え可能なフラッシュ上に割り込みを分岐させようとしたが、えらく苦労したので、なるほど、と思ってしまった。