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相關配置 展開原碼
|
這裡呼叫了boards.cfg檔案,切進去檢視該檔案,這個檔案裡面其實定義了u-boot可以支援的所有開發板,如下圖:
2.3分析建立軟連線過程
mkconfig建立軟連線 展開原碼
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 表示判斷原始碼目錄是不是我們目標檔案生產的目錄,顯然是的,我們生成的目標檔案是在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 |
|
首先判斷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 |
|
上面的程式碼其實就是判斷有沒有定義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 |
|
首先檢查config.h存在否,如果不存在就建立一個config,h,然後依次定義巨集到config.h中,最後加入一些arm平臺下通用的標頭檔案,最後儲存退出。開啟config.h檔案,可以清晰看到如下內容:
這裡並未定義檔案裡面的前四行內容,應該是手動新增進去的,確定mxl是否是有SPL啟動,具體是哪個型號,然後根據具體型號再做一個配置,這裡寫到imx6image.cfg檔案裡檢視。
imx6img.cfg
1 2 3 4 5 6 7 8 9 |
|
這個檔案加入了另外幾個標頭檔案,猜測這個檔案是和啟動方式有關的配置檔案,這裡又加入了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 |
|
下面開始分析start.s和具體上電後的操作。
4.啟動分析
start.S
22 23 |
|
這裡宣告一個連線入口_start,上電後或者復位後第一句就跳轉到reset,切過去:
start.S
110 111 112 113 114 115 116 117 118 |
|
CPSR暫存器
這裡先跳轉到save_boot_params,bl跳轉後會返回回來繼續執行,還是切過去看save_boot_params做了什麼。
save_boot_params
179 180 181 182 |
|
這裡bx lr就直接返回跳轉來之前的地址,也就是什麼都不做,下面的.weak關鍵字作用是如果其他地方定義了save_boot_params那就呼叫,如果沒有定義,這就是個空函式。
接著上面的程式碼段,具體做了什麼已經註釋的比較清楚了,接下來:
start.S
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
|
這裡我們沒有定義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 展開原碼
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 |
|
這部分首先對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 展開原碼
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
|
未定義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 |
|
這裡首先設定了一個臨時的堆空間,將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之前初始化了
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
- gdata的定義在本檔案中: gd_t gdata __attribute__ ((section(".data")));
- 它是一個 gd_t 也就是global_data型別的變數
- __attribute__表示這個變數會被放到".data"這個輸入段中. 聯結器會把輸入段按照連結指令碼(u-boot-spl.lds)裡面指定的規則存放到輸出段
- gd的定義在DECLARE_GLOBAL_DATA_PTR <arch/arm/include/asm/global_data.h>
接著跳轉到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)