U-Boot启动流程详解
參考:U-Boot頂層目錄鏈接腳本文件(u-boot.lds)介紹
作者:一只青木呀
發布時間: 2020-10-23 13:52:23
網址:https://blog.csdn.net/weixin_45309916/article/details/109240625
目錄
- 鏈接腳本 u-boot.lds 詳解
- 1、u-boot.lds文件
- 2、arch/arm/lib/vectors.S 文件
- 3、u-boot.map(地址映射文件)
- 4、鏈接文件分析
- 總結
- U-Boot 啟動流程詳解
- reset 函數源碼詳解
- lowlevel_init 函數詳解
- s_init 函數詳解
- _main 函數詳解
- board_init_f 函數詳解(初始化外設、uboot重定位給Linux騰空間)
- relocate_code 函數詳解
- relocate_vectors 函數詳解
- board_init_r 函數詳解
- run_main_loop 函數詳解
- cli_loop 函數詳解
- cmd_process 函數詳解
- bootz 啟動 Linux 內核過程
- images 全局變量
- do_bootz 函數(bootz啟動內核命令的執行函數)
- bootz_start 函數
- do_bootm_states 函數
- bootm_os_get_boot_func 函數
- do_bootm_linux 函數
上一章我們詳細的分析了 uboot 的頂層 Makefile,理清了 uboot 的編譯流程。本章我們來詳細的分析一下 uboot 的啟動流程,理清 uboot 是如何啟動的。通過對 uboot 啟動流程的梳理,我們就可以掌握 ①外設是在哪里被初始化的,這樣當我們需要修改這些外設驅動的時候就會心里有數。另外,通過分析 uboot 的啟動流程可以 ②了解 Linux 內核是如何被啟動的。
鏈接腳本 u-boot.lds 詳解
要分析 uboot 的啟動流程,首先要找到“入口”,找到第一行程序在哪里。程序的鏈接是由鏈接腳本(鏈接腳本是編譯生成的)來決定的,所以通過鏈接腳本可以找到程序的入口。如果沒有編譯過 uboot 的話鏈接腳本為 arch/arm/cpu/u-boot.lds。但是這個不是最終使用的鏈接腳本,最終的鏈接腳本是在這個鏈接腳本的基礎上生成的。編譯一下 uboot,編譯完成以后就會在 uboot 根目錄下生成 u-boot.lds
文件,如下圖所示:
只有編譯 u-boot 以后才會在根目錄下出現 u-boot.lds 文件!
1、u-boot.lds文件
打開 u-boot.lds,內容如下:
第 3 行為代碼當前入口點: _start, _start 在文件 arch/arm/lib/vectors.S 中有定義,如圖下所示:
2、arch/arm/lib/vectors.S 文件
從上圖可以看出, _start 后面就是中斷向量表,從圖中的“.section “.vectors”, "ax”可以得到,此代碼存放在.vectors 段里面。
3、u-boot.map(地址映射文件)
使用如下命令在uboot 中查找“__image_copy_start”:(上圖第一節第十行代碼處)
grep -nR "__image_copy_start"搜索結果如圖32.1.3 所示:
打開 u-boot.map(地址映射文件):
u-boot.map 是 uboot 的映射文件,可以從此文件看到某個文件或者函數鏈接到了哪個地址,從上圖932 行可以看到 __image_copy_start 為 0X87800000,而.text 的起始地址也是0X87800000。
4、鏈接文件分析
| *(.__image_copy_start) | uboot 拷貝的首地址 |
在鏈接文件中第 10 行*(._image_copystart) 在映射文件中可以看到地址為 0X87800000,而.text 的起始地址也是0X87800000。
在鏈接文件中第 11 行是 vectors 段, vectors 段保存中斷向量表(裸機部分講過),從u-boot.lds文件我們知道了 vectors.S 的代碼是存在 vectors 段中的。從地址映射文件中, vectors 段的起始地址也是 0X87800000,說明整個 uboot 的起始地址就是 0X87800000,這也是為什么我們裸機例程的鏈接起始地址選擇0X87800000 了,目的就是為了和uboot 一致。
在鏈接文件中第 12 行將 arch/arm/cpu/armv7/start.s 編譯出來的.o代碼放到中斷向量表后面(參照上圖u-boot.map的代碼)。
在鏈接文件中第 13 行為 text 段,其他的代碼段就放到這里
在鏈接文件中第 16 行 .rodata只讀數據段(一般存放常量)
在鏈接文件中第 18 行,數據段 (一般存放已初始化的全局和靜態變量)
在鏈接文件中第 24 行 ,.u_boot_list段
在鏈接文件中第 28 行, .image_copy_end:uboot 拷貝的結束地址
在鏈接文件中第 32 行,.rel_dyn_start:.rel.dyn 段起始地址
在鏈接文件中第 39 行,.rel_dyn_end:.rel_dyn段結束地址
在鏈接文件中第 52 行,.bss_start:.bss 段起始地址(靜態數據區,一般存放未初始化的全局和靜態變量)
在鏈接文件中第 61 行,.bss_end:.bss段結束
總結
在u-boot.lds 中有一些跟地址有關的“變量”需要我們注意一下,后面分析u-boot 源碼的時候會用到,這些變量要最終編譯完成才能確定的!!!比如我編譯完成以后這些“變量”的值如表所示:
| *(.vectors) | 0x87800000 | 中斷向量表 |
| arch/arm/cpu/armv7/start.o | 0x87800300 | strrt.c |
| __image_copy_start | 0x87800000 | uboot 拷貝的首地址 |
| __image_copy_end | 0x8785dd54 | uboot 拷貝的結束地址 |
| __rel_dyn_start | 0x8785dd54 | .rel.dyn 段起始地址 |
| __rel_dyn_end | 0x878668f4 | .rel.dyn 段結束地址 |
| _image_binary_end | 0x878668f4 | 鏡像結束地址 |
| __bss_start | 0x8785dd54 | .bss 段起始地址 |
| __bss_end | 0x878a8e74 | .bss 段結束地址 |
上表中的“變量”值可以在 u-boot.map 文件中查找,上表中除了__image_copy_start以外,其他的變量值每次編譯的時候可能會變化,如果修改了 uboot 代碼、修改了 uboot 配置、選用不同的優化等級等等都會影響到這些值。所以,一切以實際值為準。
U-Boot 啟動流程詳解
reset 函數源碼詳解
從u-boot.lds 中我們已經知道了入口點是arch/arm/lib/vectors.S 文件中的_start,代碼如下:
第48 行_start 開始的是中斷向量表,其中54~61 行就是中斷向量表,和我們裸機例程里面一樣。54 行跳轉到reset 函數里面,reset 函數在arch/arm/cpu/armv7/start.S 里面,代碼如下:
第35 行就是reset 函數。
第37 行從reset 函數跳轉到了save_boot_params 函數,而save_boot_params 函數同樣定義在start.S 里面,定義如下:
save_boot_params 函數也是只有一句跳轉語句,跳轉到save_boot_params_ret 函數(為啥不直接跳轉到呢,搞得這么麻煩),
save_boot_params_ret 函數代碼如下:
第43 行,讀取寄存器cpsr(程序狀態寄存器,前面架構部分講過) 中的值,并保存到r0 寄存器中。
第44 行,將寄存器r0 中的值與0X1F 進行與運算,結果保存到r1 寄存器中,目的就是提取cpsr 的bit0~bit4 這5 位,這5 位為M4 M3 M2 M1 M0,M[4:0]這五位用來設置處理器的工作模式,如表32.2.1.1 所示:
第45 行,判斷r1 寄存器的值是否等于0X1A(0b11010),也就是判斷當前處理器模式是否處于Hyp 模式。
第46 行,如果r1 和0X1A 不相等,也就是CPU 不處于Hyp 模式的話就將r0 寄存器的bit0~5 進行清零,其實就是清除模式位。
第47 行,如果處理器不處于Hyp 模式的話就將r0 的寄存器的值與0x13 進行或運算,0x13=0b10011,也就是設置處理器進入SVC 模式。
第48 行,r0 寄存器的值再與0xC0 進行或運算,那么r0 寄存器此時的值就是0xD3,cpsr的I 為和F 位分別控制IRQ 和FIQ 這兩個中斷的開關,設置為1 就關閉了FIQ 和IRQ!
第49 行,將r0 寄存器寫回到cpsr 寄存器中。完成設置CPU 處于SVC32 模式,并且關閉FIQ 和IRQ 這兩個中斷。
繼續執行執行下面的代碼:
第56 行,如果沒有定義CONFIG_OMAP44XX 和CONFIG_SPL_BUILD 的話條件成立,此處條件成立。
第58 行讀取CP15 中c1 寄存器的值到r0 寄存器中,根據17.1.4 小節可知,這里是讀取SCTLR 寄存器的值。
第59 行,CR_V 在arch/arm/include/asm/system.h 中有如下所示定義:
#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */因此這一行的目的就是清除SCTLR 寄存器中的bit13,SCTLR 寄存器結構如圖32.2.1.1 所示:
從圖32.2.1.1 可以看出,bit13 為V 位,此位是向量表控制位,當為0 的時候向量表基地址為0X00000000,軟件可以重定位向量表。為1 的時候向量表基地址為0XFFFF0000,軟件不能重定位向量表。這里將V 清零,目的就是為了接下來的向量表重定位,這個我們在第十七章有過詳細的介紹了。
第60 行將r0 寄存器的值重寫寫入到寄存器SCTLR 中。
第63 行設置r0 寄存器的值為_start,_start 就是整個uboot 的入口地址,其值為0X87800000,相當于uboot 的起始地址,因此0x87800000 也是向量表的起始地址。
第64 行將r0 寄存器的值(向量表值)寫入到CP15 的c12 寄存器中,也就是VBAR 寄存器。因此第58~64 行就是設置向量表重定位的。
代碼繼續往下執行:
第68 行如果沒有定義CONFIG_SKIP_LOWLEVEL_INIT 的話條件成立。我們沒有定義CONFIG_SKIP_LOWLEVEL_INIT,因此條件成立,執行下面的語句。
示例代碼32.2.1.6 中的內容比較簡單,就是分別調用函數cpu_init_cp15、cpu_init_crit 和_main。
函數cpu_init_cp15 用來設置CP15 相關的內容,比如關閉MMU 啥的,此函數同樣在start.S文件中定義的,代碼如下:
函數cpu_init_cp15 都是一些和CP15 有關的內容,我們不用關心,有興趣的可以詳細的看一下。
函數cpu_init_crit 也在是定義在start.S 文件中,函數內容如下:
可以看出函數cpu_init_crit 內部僅僅是調用了函數lowlevel_init,接下來就是詳細的分析一下lowlevel_init 和_main 這兩個函數。
lowlevel_init 函數詳解
函數lowlevel_init 在文件arch/arm/cpu/armv7/lowlevel_init.S 中定義,內容如下:
第22 行設置sp 指向CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR 在include/configs/mx6ullevk.h 文件中,在mx6ullevk.h 中有如下所示定義:
示例代碼32.2.2.2 中的IRAM_BASE_ADDR 和IRAM_SIZE 在文件
arch/arm/include/asm/arch-mx6/imx-regs.h 中有定義,如下所示,其實就是IMX6UL/IM6ULL 內部ocram 的首地址和大小。
s_init 函數詳解
在上一小節中,我們知道lowlevel_init 函數后面會調用s_init 函數,s_init 函數定義在文件arch/arm/cpu/armv7/mx6/soc.c 中,如下所示:
在第816 行會判斷當前CPU 類型,如果CPU 為MX6SX、MX6UL、MX6ULL 或MX6SLL中的任意一種,那么就會直接返回,相當于s_init 函數什么都沒做。所以對于I.MX6UL/I.MX6ULL 來說,s_init 就是個空函數。從s_init 函數退出以后進入函數lowlevel_init,但是lowlevel_init 函數也執行完成了,返回到了函數cpu_init_crit,函數cpu_init_crit 也執行完成了,最終返回到save_boot_params_ret,函數調用路徑如圖32.2.3.1 所示:
從圖32.2.3.1 可知,接下來要執行的是save_boot_params_ret 中的_main 函數,接下來分析_main 函數。
_main 函數詳解
第93 行,調用board_init_f 函數,此函數定義在文件common/board_f.c 中!主要用來初始化DDR,定時器,完成代碼拷貝等等,此函數我們后面在詳細的分析。
第103 行,重新設置環境(sp 和gd)、獲取gd->start_addr_sp 的值賦給sp,在函數board_init_f中會初始化gd 的所有成員變量,其中gd->start_addr_sp=0X9EF44E90,所以這里相當于設置
sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90 是DDR 中的地址,說明新的sp 和gd 將會存放到DDR 中,而不是內部的RAM 了。GD_START_ADDR_SP=64,參考示例代碼32.2.2.4。
這個就是_main 函數的運行流程,在_main 函數里面調用了board_init_f、relocate_code、relocate_vectors 和board_init_r 這4 個函數,接下來依次看一下這4 個函數都是干啥的。
board_init_f 函數詳解(初始化外設、uboot重定位給Linux騰空間)
_main 中會調用board_init_f 函數,board_init_f 函數主要有兩個工作:
- ①、初始化一系列外設,比如串口、定時器,或者打印一些消息等。
- ②、初始化gd 的各個成員變量,uboot 會將自己重定位到DRAM 最后面的地址區域,也就是將自己拷貝到DRAM 最后面的內存區域中。這么做的目的是給Linux 騰出空間,防止Linux kernel 覆蓋掉uboot,將DRAM 前面的區域完整的空出來。在拷貝之前肯定要給uboot 各部分分配好內存位置和大小,比如gd 應該存放到哪個位置,malloc 內存池應該存放到哪個位置等等。這些信息都保存在gd 的成員變量中,因此要對gd 的這些成員變量做初始化。最終形成一個完整的內存“分配圖”,在后面重定位uboot 的時候就會用到這個內存“分配圖”。
。。代碼未貼出:
第9 行,board_early_init_f 函數,板子相關的早期的一些初始化設置,I.MX6ULL 用來初始化串口的IO 配置
第10 行,timer_init,初始化定時器,Cortex-A7 內核有一個定時器,這里初始化的就是Cortex-A 內核的那個定時器。通過這個定時器來為uboot 提供時間。就跟Cortex-M 內核Systick 定時器一樣。關于Cortex-A 內部定時器的詳細內容,請參考文檔《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的“Chapter B8 The Generic Timer”章節。
第11 行,board_postclk_init,對于I.MX6ULL 來說是設置VDDSOC 電壓。
第12 行,get_clocks 函數用于獲取一些時鐘值,I.MX6ULL 獲取的是sdhc_clk 時鐘,也就是SD 卡外設的時鐘。
第13 行,env_init 函數是和環境變量有關的,設置gd 的成員變量env_addr,也就是環境變量的保存地址。
第14 行,init_baud_rate 函數用于初始化波特率,根據環境變量baudrate 來初始化gd->baudrate。
第15 行,serial_init,初始化串口。
第16 行,console_init_f,設置gd->have_console 為1,表示有個控制臺,此函數也將前面暫存在緩沖區中的數據通過控制臺打印出來。
第17 行、display_options,通過串口輸出一些信息,如圖32.2.5.1 所示:
第19 行,print_cpuinfo 函數用于打印CPU 信息,結果如圖32.2.5.3 所示:
第20 行,show_board_info 函數用于打印板子信息,會調用checkboard 函數,結果如圖32.2.5.4 所示:
第23 行,init_func_i2c 函數用于初始化I2C,初始化完成以后會輸出如圖32.2.5.5 所示信息:
第44 行,setup_dest_addr 函數,設置目的地址,設置gd->ram_size,gd->ram_top,gd->relocaddr這三個的值。接下來我們會遇到很多跟數值有關的設置,如果直接看代碼分析的話就太費時間了,我可以修改uboot 代碼,直接將這些值通過串口打印出來,比如這里我們修改文件common/board_f.c,因為setup_dest_addr 函數定義在文件common/board_f.c 中,在setup_dest_addr函數輸入如圖32.2.5.6 所示內容:
設置好以后重新編譯uboot,然后燒寫到SD 卡中,選擇SD 卡啟動,重啟開發板,打開SecureCRT,uboot 會輸出如圖32.2.5.7 所示信息:
從圖32.2.5.7 可以看出:
第45 行,reserve_round_4k 函數用于對gd->relocaddr 做4KB 對齊,因為
gd->relocaddr=0XA0000000,已經是4K 對齊了,所以調整后不變。
第46 行,reserve_mmu,留出MMU 的TLB 表的位置,分配MMU 的TLB 表內存以后會對gd->relocaddr 做64K 字節對齊。完成以后gd->arch.tlb_size、gd->arch.tlb_addr 和gd->relocaddr如圖32.2.5.8 所示:
從圖32.2.5.8 可以看出:
第47 行,reserve_trace 函數,留出跟蹤調試的內存,I.MX6ULL 沒有用到!
第48 行,reserve_uboot,留出重定位后的uboot 所占用的內存區域,uboot 所占用大小由gd->mon_len 所指定,留出uboot 的空間以后還要對gd->relocaddr 做4K 字節對齊,并且重新設置gd->start_addr_sp,結果如圖32.2.5.9 所示:
。。。
從圖32.2.5.16 可以看出,uboot 重定位后的偏移為0X18747000,重定位后的新地址為0X9FF4700,新的gd 首地址為0X9EF44EB8,最終的sp 為0X9EF44E90。
至此,board_init_f 函數就執行完成了,最終的內存分配如圖32.2.5.16 所示:
relocate_code 函數詳解
relocate_vectors 函數詳解
board_init_r 函數詳解
run_main_loop 函數詳解
cli_loop 函數詳解
cmd_process 函數詳解
bootz 啟動 Linux 內核過程
uboot啟動Linux使用bootz命令,①bootz命令是如何啟動Linux內核?②uboot的生命是怎么終止的?③Linux又是怎么啟動的呢?
images 全局變量
不管是bootz命令 還是bootm 命令,在啟動Linux 內核的時候都會用到一個重要的全局變量:images,images 在文件cmd/bootm.c 中有如下定義:
images 是bootm_headers_t 類型的全局變量,bootm_headers_t 是個boot 頭結構體,在文件include/image.h 中的定義如下(刪除了一些條件編譯代碼):
第335 行的os 成員變量是image_info_t 類型的,為系統鏡像信息。
第352~362 行這11 個宏定義表示BOOT 的不同階段。
接下來看一下結構體image_info_t,也就是系統鏡像信息結構體,此結構體在文件include/image.h 中的定義如下:
全局變量images 會在bootz 命令的執行中頻繁使用到,相當于Linux 內核啟動的“靈魂”。
下面詳細介紹各個函數:
do_bootz 函數(bootz啟動內核命令的執行函數)
bootz 命令的執行函數為do_bootz,在文件cmd/bootm.c 中有如下定義:
bootz_start 函數
do_bootm_states 函數
bootm_os_get_boot_func 函數
do_bootm_linux 函數
總結
以上是生活随笔為你收集整理的U-Boot启动流程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: U-Boot顶层Makefile分析
- 下一篇: VS2012 发布网站步骤