2012年04月19日
PARTNER-JetでU-bootを追いかける(4)
U-bootの起動シーケンスを見てみます。
なお、今回の記事はKZM-A9-Dualボードで使用した2009年のU-bootに基づいています。最近のリリースのU-bootではファイル構成もリロケーションのしくみもかなり変っていました。これについては後日とりあげます。
NORフラッシュからのブート
KZM-A9-DualボードではSDカードやeMMCからも起動することができますが、今回は一番シンプルなNORフラッシュに焼いてあるU-Bootの起動手順を見てみます。おおまかには以下の通りです。
- 電源投入してリセット解除
- CPUは0番地のNORフラッシュから実行開始
- DRAMコントローラなど基本的なハードウェアの初期化
- NORフラッシュ内のU-bootのコードをDRAM上にコピー
- DRAM上のコードにジャンプ
U-Bootのリンク
U-Bootをビルドしたときに、System.mapというファイルができます。このファイルには各関数などのアドレスが記載されたテキストファイルです。
$ head System.map 41080000 T _start 41080020 t _undefined_instruction 41080024 t _software_interrupt 41080028 t _prefetch_abort 4108002c t _data_abort 41080030 t _not_used 41080034 t _irq 41080038 t _fiq 4108003c t _pad 41080040 T _end_vect
これはDRAMに転送後のアドレスです。
実際にはこれがNORフラッシュの0番地から格納されます。つまり、0番地からDRAM上のコードにジャンプするまでの間はリンクされたアドレスとは異なるアドレスに配置されたまま動くことになります。この部分はアセンブラで書かれていますが、どこに置かれても動くように絶対アドレスを使用せずに、全てPC相対のオフセットを使ってメモリを参照するようにしています。(= Position Independent Code)
(最近のU-bootのリリースではこのリンクとリロケーションの方法が変っています。)
0番地からのコード
cpu/arm_cortexa9/start.S
_start: b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq ... reset: /* * set the cpu to SVC32 mode */ mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr,r0 /* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif
cpu_init_critでクリティカルなCPUの初期化を行います。
この中からlowlevel_init が呼ばれます。
スタックポインタには初期化が不要な内蔵SRAMの領域がセットされます。そして、クロックジェネレータやDRAMコントローラの初期化、ピンコンフィグの設定などが行われます。
ここから先ではDRAMが使用可能です。
まず、U-bootのコード自身をDRAMにコピーします。memcpy(_TEXT_BASE, &start, _bss_start - _armboot_start) とほぼ同等です。
#ifndef CONFIG_SKIP_RELOCATE_UBOOT relocate: @ relocate U-Boot to RAM adr r0, _start @ r0 <- current position of code ldr r1, _TEXT_BASE @ test if we run from flash or RAM cmp r0, r1 @ don't reloc during debug beq stack_setup ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 @ r2 <- size of armboot add r2, r0, r2 @ r2 <- source end address copy_loop: @ copy 32 bytes at a time ldmia r0!, {r3 - r10} @ copy from source address [r0] stmia r1!, {r3 - r10} @ copy to target address [r1] cmp r0, r2 @ until source end addreee [r2] ble copy_loop #endif /* CONFIG_SKIP_RELOCATE_UBOOT */
DRAM上に本格的に使用するスタックエリアを確保して、そこにスタックポインタをセットします。U-bootのコードはDRAMの後ろの方に配置し、mallocエリア、bdinfoエリアをその前にとり、スタックポインタの初期値はさらにその前です。
/* Set up the stack */ stack_setup: ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ) #endif sub sp, r0, #12 @ leave 3 words for abort-stack and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
スタックポインタが8バイト境界になるように切り下げています。
次にBSS領域をゼロクリアします。memset(_bss_start, 0, _bss_end - _bss_start) とほぼ同等です。
/* Clear BSS (if any). Is below tx (watch load addr - need space) */ clear_bss: ldr r0, _bss_start @ find start of bss segment ldr r1, _bss_end @ stop here mov r2, #0x00000000 @ clear value clbss_l: str r2, [r0] @ clear BSS location cmp r0, r1 @ are we at the end yet add r0, r0, #4 @ increment clear index pointer bne clbss_l @ keep clearing till at end
DRAM上のコードにジャンプします。start_armbootがDRAM上で動く一番最初のコードで、C言語で記述されています。lib_arm/board.c にあります。
ldr pc, _start_armboot @ jump to C code _start_armboot: .word start_armboot
start_armbootは以前見たスタックトレースにも出てきました。
>k u-boot : : 41080724 start_armboot+1C4() u-boot :main.c : 467 : 4108F380 main_loop+F0() u-boot :main.c :1010 : 4108F0B4 readline_into_buffer+40(prompt=*41099AEA "KZM-A9-D..",buffer=*410A0380 "\0") u-boot :console.c : 254 : 4108C2B8 fgetc+20(file=E1030000) u-boot :serial.c : 213 : 41086F00 _serial_getc+14(port=E1030000) u-boot :ns16550.c : 65 : 410870CC NS16550_getc+4(com_port=*E1030000) >
これで電源ONからコマンドの入力待ちまでのルートがつながりました。
PARTNER-JetでDRAMで動く一番最初の関数でブレークさせるには
PARTNER-Jetでこのstart_armbootで止めるにはどうしたらよいでしょうか。
通常のブレークポイントはメモリ内にブレークポイント命令を埋め込みます。(ソフトウェアブレークポイント) しかしこれはその部分のコードが書き変わらないことが前提になっていますので、今のようにコードをRAMにコピーした直後では使用できません。代わりにハードウェアブレークポイントを使用します。
実行 > ハードウェアブレーク設定(H) ..
これでGoすると、以下のようにstart_armbootで止まります。
もう一つのCPUはどうしてる?
KZM-A9-DualはデュアルコアのCortex-A9です。リセットを解除したら2つのコアが0番地から走り始めるはずです。今まであまり意識していませんでしたが、もうひとつのCPUコアはどうしているのでしょう?
実は、lowlevel_initの関数の中で、CPU IDをチェックし、CPU ID==0ならば初期化処理を続行し、そうでなければ、WFI命令(Wait for Interrupt)を含むループの中で待つようになっています。
PARTNER-Jetでそれを確認することができます。
SMP > Core1
とするとCPU ID==1 のコアの様子を見ることができます。