1. 程式人生 > >imx6ul:uboot-2013.10啟動過程解析

imx6ul:uboot-2013.10啟動過程解析

1.原始碼結構分析

     首先一個問題,老版本的u-boot是沒有SPL這個檔案的,新版u-boot開始包含SPL檔案,原來u-boot啟動比如放到nand中,在cpu內部有一個stepping stone,可以拷貝nand中的u-boot到ram中執行,然後u-boot自己再啟動第二階段在對應記憶體中好到系統的image啟動。現在加了這個SPL之後,我的理解這是一個u-boot的loader。及cpu上電後,首先執行這個spl,然後通過這個spl再將u-boot放到對應的位置執行,之後的操作就和老版本基本一樣了。至於為什麼這樣做,暫時還不明白,後期再研究下。

編譯成功的u-boot-2013.10共有19個子目錄,大約15個有用的檔案,其中各個子目錄和重要檔案功能見下表:

名稱

型別

功能說明

api

通用

U-boot提供的一些介面函式

arch

平臺相關

當前U-BOOT重要的目錄,arch下每個子目錄代表一種處理器型別

board

平臺相關

裡面有很多支援的開發板型號,這裡關注具體開發板和config.mk

common

通用

主要跟U-BOOT的命令有關,cmd_xxx.c以及環境變數的處理程式碼env_xxx.c

spl

平臺相關

u-boot的第一階段相關,搬運第二階段程式碼到記憶體中

disk

通用

磁碟驅動的分割槽處理程式碼

doc

說明文件

可以用來做配置參考

drivers

通用

裝置的驅動程式,每種型別一個子目錄包括網絡卡,USB,LCD等

dts

通用

裝置樹的控制,主要是由於LINUX 3.X中去除了很多冗餘的程式碼,引入device tree,許多硬體細節可以直接傳遞給LINUX,是新的東西

examples

通用

一些示例程式

fs

通用

檔案系統支援

include

通用

標頭檔案和開發板的配製,configs子目錄重要

lib

通用

通用的庫檔案

Licenses

通用

可以忽略。。。

nand_spl

平臺相關

支援了部分平臺的nand啟動

net

通用

網路相關的程式碼,小型的協議棧

post

通用

加電自檢程式

scripts

通用

指令碼程式

test

通用

測試程式

tools

通用

工具,mkimage就在這裡

boards.cfg

檔案,平臺相關

修改新增開發板配置現在在此處

Makefile

MAKEALL

config.mk

rules.mk

mkconfig

檔案,通用

整個U-BOOT編譯過程的規則檔案

kbuild

mkconfig

檔案,通用

對Makefile功能的補充,使得編譯更加高效

其餘

檔案,通用

介紹文件以及其他

    移植工作主要集中在一些編譯規則檔案,還有board和arch目錄下。

2 Makefile分析

    u-boot的README裡面其實講的很清楚u-boot的移植過程,翻譯過來如下:

    第一步:在boards.cfg裡面新增自己的開發板,必須按照現有的規則新增。

    第二步:為自己的開發板建立一個目錄,在目錄下新增你需要的檔案,這目錄下必須要有以下幾個檔案,Makefile,<board>.c,flash.c和u-boot.lds

     第三步:為你的建立一個新的配置檔案“include/configs/<board>.h”

    第四步:輸入“make<board>_config”

    第五步:make

    第六步:除錯並解決出現的問題(當然,這一步遠比聽起來的難很多)

    Makefile的分析可以瞭解整個U-boot的程式碼結構是怎樣的,檔案是如何編譯、連結的。

2.1分析配置過程第一步

    在編譯的時候第一步是輸入:make wandboard_config,當輸入這個指令的時候,Makefile就會呼叫以下語句:

    %_config:: unconfig

         @$(MKCONFIG) -A $(@:_config=)

    %萬用字元匹配到執行xxx_config的時候,就呼叫下面的@$(MKCONFIG),這個MKCONFIG可以搜尋在以下定義:

    MKCONFIG := $(SRCTREE)/mkconfig

    export MKCONFIG

2.2分析mkconfig檔案

    mkconfig為$(SRCTREE)/目錄下的mkconfig檔案,就是我們原始碼目錄下的mkconfig檔案,也就是說,我們輸入了make wandboard_config之後,就執行了mkconfig。

    mkconfig裡面其實就是根據輸入的板子的型號,這裡是wandboard,呼叫boards.cfg檔案,將arch cpu soc vender board等資訊全部讀出來,然後解析這些資訊,進行通用標頭檔案和庫檔案的自動配置,比如arm平臺,很多lib庫和標頭檔案都是可以共用的。就在這一步生成標頭檔案和很多巨集,並將我們的板子的巨集配置進去,如下所示為boards.cfg和Imx6平臺相關的內容:

boards.cfg檔案與imx6相關配置 展開原碼

expand source?

cat << EOF >> config.h

#define CONFIG_BOARDDIR board/$BOARDDIR

#include <config_cmd_defaults.h>

#include <config_defaults.h>

#include <configs/${CONFIG_NAME}.h>

#include <asm/config.h> #include <config_fallbacks.h>

#include <config_uncmd_spl.h>

 

   這裡呼叫了boards.cfg檔案,切進去檢視該檔案,這個檔案裡面其實定義了u-boot可以支援的所有開發板,如下圖:

2.3分析建立軟連線過程

mkconfig建立軟連線 展開原碼

expand source?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

if "$SRCTREE" != "$OBJTREE" ] ; then 

mkdir -p ${OBJTREE}/include 

mkdir -p ${OBJTREE}/include2 

cd ${OBJTREE}/include2 

rm -f asm 

ln -s ${SRCTREE}/arch/${arch}/include/asm asm 

LNPREFIX=${SRCTREE}/arch/${arch}/include/asm/ 

cd ../include 

mkdir -p asm

else

cd ./include 

rm -f asm 

ln -s ../arch/${arch}/include/asm asm

fi

rm -f asm/arch

if [ -z "${soc}" ] ; then 

ln -s ${LNPREFIX}arch-${cpu} asm/arch

else

ln -s ${LNPREFIX}arch-${soc} asm/arch

fi

if "${arch}" "arm" ] ; then 

rm -f asm/proc 

ln -s ${LNPREFIX}proc-armv asm/proc

fi

 

    以上是建立軟連線的過程,if [ "$SRCTREE" != "$OBJTREE" ] ; then  表示判斷原始碼目錄是不是我們目標檔案生產的目錄,顯然是的,我們生成的目標檔案是在u-boot原始碼目錄下的,所以直接跳到else後面,執行下面的語句:

    cd ./include

    rm -f asm

    ln -s ../arch/${arch}/include/asm asm

    切換到原始碼目錄的include目錄下,刪除asm軟連線,然後將上一級目錄下arch/arm/include/asm目錄連結到這個目錄來,這是建立了第一個軟連線。可以看得到:

    接著rm -f asm/arch刪除asm目錄下的arch軟連線,下面的程式碼:

建立軟連線2

?

1

2

3

4

5

6

7

8

9

if [ -z "${soc}" ] ; then 

ln -s ${LNPREFIX}arch-${cpu} asm/arch

else

ln -s ${LNPREFIX}arch-${soc} asm/arch

fi

if "${arch}" "arm" ] ; then

rm -f asm/proc 

ln -s ${LNPREFIX}proc-armv asm/proc

fi

 

    首先判斷soc是否為空,這裡soc顯然不為空,執行ln -s ${LNPREFIX}arch-${soc} asm/arch ,這裡LNPREFIX為空,所以這句其實就是ln -s ./arch-mx6 asm/arch,下面的同樣是將arm相關的proc連結進去。

結果可以通過ls -l來檢視:

2.4生成config.mk和標頭檔案

生成config.mk檔案

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

( echo "ARCH   = ${arch}"  

if [ ! -z "$spl_cpu" ] ; then 

echo 'ifeq ($(CONFIG_SPL_BUILD),y)'

echo "CPU    = ${spl_cpu}"

echo "else"

echo "CPU    = ${cpu}"

echo "endif"   

else

echo "CPU    = ${cpu}"   

fi    

echo "BOARD  = ${board}"

    "${vendor}" ] && echo "VENDOR = ${vendor}"  

"${soc}"    ] && echo "SOC    = ${soc}"  

exit 0 ) > config.mk

 

     上面的程式碼其實就是判斷有沒有定義spl_cpu如果定義了那就將spl_cpu資訊輸出到CPU,這裡沒有定義,因此依次就是確定了CPU.BOARD.SOC這些資訊,最後一句> config.mk將以上資訊輸出到config.mk後退出。可以切換到./include/目錄下,看到一個config.mk檔案,開啟看到如下內容:

    接著mkconfig檔案還做了自動生成標頭檔案的工作,這部分程式碼如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

if "$APPEND" "yes" ] # Append to existing config file then

echo >> config.h

else  > config.h  # Create new config file

fi

echo "/* Automatically generated - do not edit */" >>config.h

for i in ${TARGETS} ; do

i="`echo ${i} | sed '/=/ {s/=/ /;q; } ; { s/$/ 1/; }'`"

echo "#define CONFIG_${i}" >>config.h ; done

echo "#define CONFIG_SYS_ARCH  \"${arch}\""  >> config.h

echo "#define CONFIG_SYS_CPU   \"${cpu}\""   >> config.h

echo "#define CONFIG_SYS_BOARD \"${board}\"" >> config.h

"${vendor}" ] && echo "#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h

"${soc}"    ] && echo "#define CONFIG_SYS_SOC    \"${soc}\""    >> config.h

cat << EOF >> config.h

#define CONFIG_BOARDDIR board/$BOARDDIR

#include <config_cmd_defaults.h>

#include <config_defaults.h>

#include <configs/${CONFIG_NAME}.h>

#include <asm/config.h>

#include <config_fallbacks.h>

#include <config_uncmd_spl.h>

EOF

exit 0

 

    首先檢查config.h存在否,如果不存在就建立一個config,h,然後依次定義巨集到config.h中,最後加入一些arm平臺下通用的標頭檔案,最後儲存退出。開啟config.h檔案,可以清晰看到如下內容:

    這裡並未定義檔案裡面的前四行內容,應該是手動新增進去的,確定mxl是否是有SPL啟動,具體是哪個型號,然後根據具體型號再做一個配置,這裡寫到imx6image.cfg檔案裡檢視。

imx6img.cfg

?

1

2

3

4

5

6

7

8

9

/* image version */

IMAGE_VERSION 2

/*  * Boot Device : one of  * spi, sd (the board has no nand neither onenand)  */

BOOT_FROM      sd

#define __ASSEMBLY__

#include <config.h>

#include "asm/arch/iomux.h"

#include "asm/arch/crm_regs.h"

#include "clocks.cfg"

 

這個檔案加入了另外幾個標頭檔案,猜測這個檔案是和啟動方式有關的配置檔案,這裡又加入了clocks.cfg檔案,配置了啟動時候的時鐘,這部分程式碼後面分析啟動過程的時候再分析。

2.5編譯

    這部分程式碼比較多,但是主要完成了以下幾個剩餘的工作:

    1.u-boot版本號確認及語言環境確認

    2.解析make後面傳入的引數,例如make -v=1之類的,這裡我們沒有

    3.指定原始碼目錄和目標目錄

    4.獲取machine號

    5.確定交叉編譯工具鏈,制定了我們的shell名稱:/bin/bash,編譯器套件名稱:gcc,以及一些編譯引數,-Wall表示要提示所有的warning。

    6.設定標頭檔案包含路徑,輸出目標制定目錄,新增平臺相關的標頭檔案到指定目錄。

    7.根據配置執行make以及depend的依賴關係分別呼叫各子目錄,生成所有的obj檔案。

    8.交代了u-boot是如何組裝起來的,組裝規則是u-boot.lds這個檔案,把start.o和各個子目錄makefile生成的庫檔案按照LDFLAGS連線在一起,生成ELF檔案u-boot 和連線時記憶體分配圖檔案u-boot.map。這裡,我們的u-boot.bin檔案我理解的是從u-boot.elf拷貝過來的。

3.u-boot.lds

    u-boot的程式碼是根據u-boot.lds組裝起來的,由於u-boot.lds的程式碼比較晦澀,不過不要緊,只要能找到每一個階段的入口就可以了,該檔案內容如下:

第一啟動階段程式碼入口

?

1

2

3

4

5

6

7

8

9

10

11

12

OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm")

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS {

  . = 0x00000000;

  . = ALIGN(4);

.text : 

*(.__image_copy_start)   //這裡指定了影響檔案複製的起始地址

arch/arm/cpu/armv7/start.o (.text*)  //指明瞭啟動第一階段的檔案為制定目錄下的start.s

*(.text*) 

}

 

    下面開始分析start.s和具體上電後的操作。

4.啟動分析

start.S

?

22

23

.globl _start

_start: b reset

 

    這裡宣告一個連線入口_start,上電後或者復位後第一句就跳轉到reset,切過去:

start.S

?

110

111

112

113

114

115

116

117

118

reset: 

bl save_boot_params   

mrs r0, cpsr  //將當前狀態暫存器的值讀到r0

and r1, r0, #0x1f //將r0的低五位狀態賦值r1,也就是cpsr的低五位狀態

teq r1, #0x1a  //比較CPSR的低五位狀態是否等於0x1a,該狀態說明對應HYP模式,一種非安全狀態行執行的新模式。

bicne r0, r0, #0x1f //如果不等於那就清楚低五位 

orrne r0, r0, #0x13 //同樣設定低五位為0x13,也就是10011,對應的是ARM的SVC管理模式

orr r0, r0, #0xc0  //11000000,禁止IRQ和FIQ

msr cpsr,r0//將r0的值讀到CPSR,這時候am誰svc模式,中斷被禁止。

 

    

                                                               CPSR暫存器

     這裡先跳轉到save_boot_params,bl跳轉後會返回回來繼續執行,還是切過去看save_boot_params做了什麼。

save_boot_params

?

179

180

181

182

ENTRY(save_boot_params) 

bx lr   @ back to my caller

ENDPROC(save_boot_params) 

.weak save_boot_params

 

    這裡bx lr就直接返回跳轉來之前的地址,也就是什麼都不做,下面的.weak關鍵字作用是如果其他地方定義了save_boot_params那就呼叫,如果沒有定義,這就是個空函式。

    接著上面的程式碼段,具體做了什麼已經註釋的比較清楚了,接下來:

start.S

?

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD)) 

/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */

mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register 

bic r0, #CR_V  @ V = 0

mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register

/* Set vector address in CP15 VBAR register */

ldr r0, =_start 

mcr p15, 0, r0, c12, c0, 0 @Set VBAR

#endif

#ifndef CONFIG_SKIP_LOWLEVEL_INIT 

bl cpu_init_cp15 

bl cpu_init_crit

#endif

bl _main

 

     這裡我們沒有定義CONFIG_OMAP44XX和CONFIG_SPL_BUILD,因此執行

    mrc p15, 0, r0, c1, c0, 0,這是協處理器操作,只有mrc和mcr才能對arm的協處理器進行操作:

          MRC {條件}協處理器編碼,協處理器操作碼1,目的暫存器,源暫存器1,源暫存器2,{協處理器操作碼2}

          MCR {條件}協處理器編碼,協處理器操作碼1,源暫存器,目的暫存器1,目的暫存器2,{協處理器操作碼2}

    這兩個指令一般是成對使用,讀出來在寫進去,設定CP15協處理器的C1暫存器V位為0,檢視暫存器手冊:

   設定地段一場中斷向量0x0~0x1c。

    然後將_start的地址給r0,再將該地址寫到c12暫存器,也就是設定異常向量的基地址:

   

    緊接著,這裡沒有定義skip_lowlevel_init,跳入cpu_init_cp15 ,顧名思義還是對cp15協處理器的設定。程式碼如下:

cpu_init_cp15 展開原碼

expand source?

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

ENTRY(cpu_init_cp15)

/* 

* Invalidate L1 I/D 

* 使無效整個資料和指令TLB,然後使無效整個指令cache,清空整個跳轉目標的cache,清空預取緩衝區,清空寫緩衝區   

*/

mov r0, #0   @ set up for MCR 

mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs 

mcr p15, 0, r0, c7, c5, 0 @ invalidate icache 

mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array 

mcr p15, 0, r0, c7, c10, 4 @ DSB 

mcr p15, 0, r0, c7, c5, 4 @ ISB

/*

* disable MMU stuff and caches  

* 設定低端異常中斷向量,禁止MMU,禁止地址對齊檢查,禁止資料Cache,前面已經禁止了指令cache。緊接著使能地址對齊檢查,使能跳轉預測功能   

*/

mrc p15, 0, r0, c1, c0, 0 

bic r0, r0, #0x00002000 @ clear bits 13 (--V-) 

bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) 

orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align 

orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB

#ifdef CONFIG_SYS_ICACHE_OFF 

bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache

#else 

orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache //這裡沒有定義ICACHE_OFF,因此這裡使能指令Cache

#endif 

mcr p15, 0, r0, c1, c0, 0

#ifdef CONFIG_ARM_ERRATA_716044   //沒有定義該號碼的巨集,跳過

mrc p15, 0, r0, c1, c0, 0 @ read system control register

orr r0, r0, #1 << 11 @ set bit #11 

mcr p15, 0, r0, c1, c0, 0 @ write system control register

#endif

/*

* 這裡是對CP15的C15暫存器進行了操作,這裡叫做診斷暫存器,然後將4,6,15都置位,這裡我沒找到c15暫存器的手冊說明。

*/

#ifdef CONFIG_ARM_ERRATA_742230 

mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register

orr r0, r0, #1 << 4  @ set bit #4 

mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register

#endif

#ifdef CONFIG_ARM_ERRATA_743622 

mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register

orr r0, r0, #1 << 6  @ set bit #6 

mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register

#endif

#ifdef CONFIG_ARM_ERRATA_751472 

mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register

orr r0, r0, #1 << 11 @ set bit #11 

mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register

#endif

mov pc, lr   @ back to my caller ENDPROC(cpu_init_cp15)

 

    這部分首先對r0清零,然後使無效整個資料和指令TLB,然後使無效整個指令cache,清空整個跳轉目標的cache,清空預取緩衝區,清空寫緩衝區, 設定低端異常中斷向量,禁止MMU,禁止地址對齊檢查,禁止資料Cache,前面已經禁止了指令cache。緊接著使能地址對齊檢查,使能跳轉預測功能 。然後後面有三個勘誤巨集,這裡定義了三個,分別作了以下事情:對CP15的C15暫存器進行了操作,這裡叫做診斷暫存器,然後將4,6,15都置位,這裡我沒找到c15暫存器的手冊說明,具體意義不明,不過應該不影響後面的啟動過程。

    具體CP15的C0到C15暫存器資訊參考下面的連結。

    http://blog.sina.com.cn/s/blog_858820890102v1gc.html

    完了之後,跳回子函式,然後順序執行到函式cpu_init_crit:

cpu_init_crit 展開原碼

expand source?

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

/************************************************************************* 

*

* CPU_init_critical registers 

* setup important registers 

* setup memory timing 

* *************************************************************************/

ENTRY(cpu_init_crit) 

/*  

* Jump to board specific initialization...  

* The Mask ROM will have already initialized  

* basic memory. Go here to bump up clock rate and handle  

* wake up conditions.   */

   b lowlevel_init  @ go setup pll,mux,memory

ENDPROC(cpu_init_crit)

#endif

 

     未定義SKIP_LOWLEVEL_INIT這部分程式碼其實就是跳轉到lowlevel_init去了,lowlevel_init的作用就是引導載入c函式做進一步的初始化,切過去。

low_level_init.S

?

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

ENTRY(lowlevel_init) 

/*  

* Setup a temporary stack  

*/

ldr sp, =CONFIG_SYS_INIT_SP_ADDR 

bic sp, sp, #7 /* 8-byte alignment for ABI compliance */

#ifdef CONFIG_SPL_BUILD 

ldr r9, =gdata

#else 

sub sp, #GD_SIZE 

bic sp, sp, #7 

mov r9, sp

#endif 

/*  

* Save the old lr(passed in ip) and the current lr to stack  

*/

push {ip, lr}

/*  

* go setup pll, mux, memory  

*/

bl s_init 

pop {ip, pc}

ENDPROC(lowlevel_init)

 

    這裡首先設定了一個臨時的堆空間,將CONFIG_SYS_INIT_SP_ADDR的地址送到SP,這個地址=(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET),CONFIG_SYS_INIT_RAM_ADDR 在

imx-regs.h裡面定義了0x00900000,後面的CONFIG_SYS_INIT_SP_OFFSET未找到,忽略。

    設定SP八個位元組對齊之後,這裡定義了CONFIG_SPL_BUILD。將gdata賦值給r9,跳轉到s_init函式中去。

    s_init在arch\arm\cpu\armv7\mx6的soc.c中,s_init主要是對IMX6的PFDs進行了板級設定。

    在呼叫結束s_init之後,程式跳轉到到_main函式裡面,搜尋定位該感受在arch/arm/lib/crt0.S下,這裡是main函式的入口,主要做了以下工作:

  • 重新對SP賦值, 確認sp是8字對齊
  • 在棧頂保留一個global_data的大小, 這個global_data是uboot裡面的一個全域性資料, 很多地方都會用到. 俗稱 gd_t
  • 確認更新後的sp是8字對齊
  • r9指向global_data, 後面別的地方想用global_data時候, 可以直接從r9裡面獲取地址.
  • r0賦值0
  • bl board_init_f: 跳轉到board_init_f. 在編譯SPL時, 分析Makefile可以看出, 該函式的實現是在<arch/arm/lib/spl.c>.

    board_init_f在arch/arm/lib/spl.c中,主要做了以下事情:

  • 對BSS段進行清零操作
  • gd = &gdata;
    • gd的定義在DECLARE_GLOBAL_DATA_PTR <arch/arm/include/asm/global_data.h>
      • #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")
        • r9之前初始化了
    • gdata的定義在本檔案中: gd_t gdata __attribute__ ((section(".data"))); 
      • 它是一個 gd_t 也就是global_data型別的變數
      • __attribute__表示這個變數會被放到".data"這個輸入段中. 聯結器會把輸入段按照連結指令碼(u-boot-spl.lds)裡面指定的規則存放到輸出段

    接著跳轉到board_init_r,在common/spl/spl.c下面,主要做了以下事情:

    對memory,timer初始化,選擇在什麼介質啟動,最後判斷image的型別,是u-boot還是linux。

5.總結

    (reset) <arch/arm/cpu/armv7/start.S-> (b lowlevel_init: arch/arm/cpu/armv7/lowlevel_init.S) (b _main) --> <arch/arm/lib/crt0.S> (bl board_init_f) --> <arch/arm/lib/spl.c> (board_init_r) --> <common/spl/spl.c> (jump_to_image_no_args去啟動u-boot)