16.U-boot的工作流程分析-2440
分析的流程:
- 程序入口
- 第一階段程序分析
- 第二階段程序分析
2440開發板:
1.uboot的入口:
要看uboot工程的入口,首先打開頂層目錄的Makefile:
Uboot所支持的開發板,在頂層的Makefile中都會有一個配置選項。比如2440,在Makefile中的配置選項是smdk2440_config:在vim的命令模式按下/,然后輸入smdk6410_config回車會定位到這里:
這是Makefile里的一個目標。這是來配置2440開發板的。看到上圖第二行的smdk2440,這個參數決定了開發板的名稱。這個名稱是有作用的。接下來看看他的作用。
首先是找一下目錄:
可以看到這里有很多smdk的子目錄,也包括smdk2440,這兩個是對應的。該目錄存放的就是2440開發板相關的文件。里面有一個叫uboot.lds的文件,前面知道lds文件是連接器腳本。Uboot的整個過程的鏈接,是通過該腳本來鏈接控制的。打開該鏈接器腳本:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
????. = 0x00000000;
????. = ALIGN(4);
????.text :
????{
???? cpu/s3c24xx/start.o????(.text) //1可以看到位于代碼段前端的文件是start.o,對應的是一個匯編文件。這個匯編文件會最先被運行。但是這個匯編代碼里最先執行的代碼是哪些呢?
???? cpu/s3c24xx/s3c2440/cpu_init.o????(.text)
???? *(.text)
????}
????. = ALIGN(4);
????.rodata : { *(.rodata) }
????. = ALIGN(4);
????.data : { *(.data) }
????. = ALIGN(4);
????.got : { *(.got) }
?
????. = .;
????__u_boot_cmd_start = .;
????.u_boot_cmd : { *(.u_boot_cmd) }
????__u_boot_cmd_end = .;
?
????. = ALIGN(4);
????.mmudata : { *(.mmudata) }
?
????. = ALIGN(4);
????__bss_start = .;
????.bss : { *(.bss) }
????_end = .;
}
首先是找到該文件:
上面u-boot-lds文件里,在sections的標識之上,有一行:ENTRY(_start)是整個程序的入口。所以就找找start.S文件里有沒_start這個標識呢?一搜會馬上看到該標號:
到這里,看到這個_start才是整個uboot工程的入口。
?
接下來是第一階段的代碼:
在Linux里,打開上一節創建好的,Source Insight里的uboot工程:
然后找到smdk2440的start.S:
接下來看uboot做了什么事,主要是通過注釋來分析:
開發板一上電,它會跳轉到start.S的中斷向量表的開始處執行:
從它的注釋:
所以1,:知道上電后是跳到中斷向量表來執行。執行的第一條指令是b start_code。
Start_code的實在start.S的下面定義的:
從注釋知道,這是actual start code。進入之后,set the cpu to svc32 mode,設置cpu為SVC模式。
接著執行的代碼是:
3.刷新I/D caches。
4.然后是關閉MMU和cache:
后加
關閉系統時鐘,在lowlevel _init函數里bl system_clock_init函數。進入該函數:
可以看到2440是在初始化系統時鐘里關閉看門狗。
下面還有屏蔽中斷的操作:
?
后加
5.接著是運行的是一個函數:看看是定義在那個文件的。
點擊:,然后在Symbol里輸入:lowlevel_init:
會看到出現很多,然后在下面可以看到它們各自的目錄。其實每一個開發板都對應一個lowlevel_init.S:
進入該文件:
5看到系統進入該文件做的第一件事是:初始化系統時鐘:
6.接著初始化串口:
7.對nand進行簡單初始化:
8.接下來的代碼很重要:
由前面的學習知道,當我們開發一個uboot在開發階段,就是在調試uboot的時候,不用燒到NandFlash去運行,可以下載到內存里面去調試運行。這時候就不需要代碼搬移bl0,bl1,bl2的過程。就不需要進行拷貝工作了。所以上面的代碼就是判斷,判斷uboot代碼是在內存運行,還是在NandFlash運行。如果沒有運行在內存當中,就是從NandFlash啟動。就需要對內存進行初始化,就是跳轉到mem_con_init處執行,進行內存初始化。執行完內存初始化之后就要返回了。
返回到:
接著往下執行:
9.判斷是NandFlash啟動還是Nor Flash啟動:
如果是NandFlash啟動這是執行下面的:把代碼從NandFlash拷貝到內存去。
NorFlash這執行下面,把代碼從NorFlash拷貝到內存。
這里講NandFlash啟動,所以跳轉到nand_copy:
?
10,接著是設置堆棧:
?
11.接著清除BSS段:
?
到這里第二階段的代碼就執行完了。
三:
執行完上面,程序跳轉到_start_armboot處執行:
上面的代碼,通過偽指令ldr把_start_armboot的值裝入pc指針,程序就會跳轉到_start_armboot處執行。而此處的地址是start_armboot的地址。就是,程序會跳轉到start_armboot函數處執行。就是把我們的pc指針跳轉到內存去執行了。
下面看看start_armboot的地址是不是在內存中。
配置uboot:make smdk2440_config,然后執行make。
然后看到生成的文件:
?
其中,u-boot是elf文件,u-boot.bin是二進制文件。
接下來對u-boot的elf文件,進行反匯編,看看start_armboot函數的地址:
arm-linux-objdump -D -S u-boot >dump
查看:
看到函數的其實地址30009100的地址是在內存里的。所以start.S里的:
實現了從墊腳石跳轉到內存。但是,此時會發現在這里的起始地址被變為了30008000:
在第一階段里,不是說啟動地址是在0嗎?為什么這里是30008000呢?還有就是為什么是這個地址。
?
前面的學習知道,當去鏈接一個程序的時候,程序由多個文件構成,起始地址是由鏈接器腳本決定的。在/home/samba/uboot/Uboot/2440/uboot/board/samsung/smdk2440里的u-boot.lds:
起始地址是0:
這里是0,為什么那里會是30008000呢?我們回到uboot的頂層目錄,打開config.mk:
搜索text_base:
找到:
在這里-T $(LDSCRIPT)就是定義使用鏈接器腳本。后面的-Ttext 是制定代碼段的基地址的。$(TEXT_BASE)。這里有兩個起始地址,然而程序運行的時候以后面的TEXT_BASE的地址為準。它會覆蓋掉LDSCRIPT這個地址。
?
TEXT_BASE是在board/Samsung/smdk2440/config.mk里定義的。最后一行:
?
接下來就是測試驗證一下,把他修改為30005000。然后程序編譯:
Make smdk2440_config->make:
反匯編:
arm-linux-objdump -D -S u-boot >dump
上面的起始地址變了,剛才制定的。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
那起始地址為什么不是0呢?下圖:
從上圖可以看到,ldr PC,=start_armboot的跳轉,把地址從墊腳石跳轉到內存里。在前面的代碼里,有用到b reset等跳轉指令,為什么這不會跳轉到內存去執行,而是還在墊腳石里呢?
例如:
上面的跳轉,bl lowlevel_init的跳轉地址:
為什么PC指針還是在墊腳石中呢?
?
這就得講兩個詞了。絕對跳轉和相對跳轉。
B和bl是相對跳轉。
?
?
?
?
?
Ldr偽指令是絕對跳轉:
?
第三階段:
是從此函數Start_armboot進入的。主要完成的硬件和軟件的初始化,只是一些基礎的初始化。
該函數里有一個for循環:
在for里首先是讓一個指針數組,把里面的函數指針依次調用一次,if里的判斷語句就是函數指針。那么指針數組里有哪些函數指針呢。
?
指針數組:
可以看到里面都是函數指針。這里軟件的初始化就不看了,我們只看硬件的初始化。在這些函數指針里,硬件初始化的有串口初始化。Serial_init。接著是lcd的初始化:
初始化網卡:
初始化led:
接著進入一個主循環:
執行用戶輸入的命令。例如tftp命令。這里是一個死循環。老是等待執行用戶繼續輸入命令。在第一個階段,每個開發板可能有不同的地方,但是在這個地方都是一樣的。就是,在第二階段都是跳到start_armboot處執行代碼。2440的第一階段是在start.S的b reset開始。