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の起動手順を見てみます。おおまかには以下の通りです。

  1. 電源投入してリセット解除
  2. CPUは0番地のNORフラッシュから実行開始
  3. DRAMコントローラなど基本的なハードウェアの初期化
  4. NORフラッシュ内のU-bootのコードをDRAM上にコピー
  5. 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番地からのコード

partner_uboot008

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) ..

partner_uboot009

これでGoすると、以下のようにstart_armbootで止まります。

partner_uboot010

もう一つの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 のコアの様子を見ることができます。

partner_uboot011



トラックバックURL

コメントする

名前
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード