uboot启动流程详细分析(基于i.m6ull)
uboot介紹
uboot就是一段引導程序,在加載系統內核之前,完成硬件初始化,內存映射,為后續內核的引導提供一個良好的環境。uboot是bootloader的一種,全稱為universal boot loader。
一、uboot的makefile
1.1 makefile整體解析過程
為了生成u-boot.bin這個文件,首先要生成構成u-boot.bin的各個庫文件、目標文件。為了各個庫文件、目標文件就必須進入各個子目錄執行其中的Makefile。由此,確定了整個編譯的命令和順序。
1.2 makefile整體編譯過程
- 首先,根據各個庫文件、目標文件出現的先后順序,依次進入各個子目錄編譯從而生成這些目標
- 然后,回到頂層目錄,繼續執行頂層Makefile的總目標,最后生成u-boot.bin。
uboot的編譯分為兩步:配置、編譯。
(1)第一步:配置,執行make pangu_basic_defconfig進行配置,生成.config文件
(2)第二步:編譯,執行make進行編譯,生成u-boot.bin。
二、uboot啟動流程
- uboot分為 uboot-spl 和 uboot 兩個組成部分。
uboot啟動分三個階段
BL0
ROM上的固化程序(Boot Rom)
BL1(u-boot-spl)
- 初始化部分時鐘(和SDRAM相關)
- 初始化DDR(外部SDRAM)
- 從存儲介質上(比如SD\eMMC\nand flash)將BL2鏡像加載到SDRAM上
- 驗證BL2鏡像的合法性
- 跳轉到BL2鏡像所在的地址上
- 初始化部分硬件,包括時鐘、內存等等
- 加載內核到內存上
- 加載文件系統、atags或者dtb到內存上
- 根據操作系統啟動要求正確配置好一些硬件
啟動操作系統
2.1 uboot的鏈接文件(u-boot.lds)
鏈接文件的作用
指定代碼段和數據段、只讀數據段在內存中的存放地址;(地址具體為i.m6ull , 其他芯片可能不是 0X87800000)
-
u-boot.map 是 uboot 的映射文件,看到某個文件或者函數鏈接到了哪個地址,
-
__image_copy_start 為 0X87800000,而.text 的起始地址也是0X87800000。
-
vectors 段保存中斷向量表,vectors 段的起始地址也是 0X87800000,說明整個 uboot 的起始地址就是 0X87800000,
-
這也是為什么我們裸機例程的鏈接起始地址選擇 0X87800000 了,目的就是為了和 uboot 一致。
指定代碼的入口地址;
- 連接文件中找到程序的入口點:_start, 其中_start 在文件 arch/arm/lib/vectors.S 。
2.2 uboot啟動流程
第一階段(uboot-spl , 入口是上述lds文件中分析的_start)
- SPL是Secondary Program Loader的簡稱,第二階段程序加載器,這里所謂的第二階段是相對于SOC中的Boot ROM來說的
Boot ROM會通過檢測啟動方式來加載第二階段bootloader。uboot已經是一個bootloader了,那么為什么還多一個uboot spl呢?
-
這個主要原因是對于一些SOC來說,它的內部SRAM可能會比較小,小到無法裝載下一個完整的uboot鏡像,那么就需要spl,它主要負責初始化外部RAM和環境,并加載真正的uboot鏡像到外部RAM(DDR)中來執行。
-
所以由此來看,SPL應該是一個非常小的loader程序,可以運行于SOC的內部SRAM中,它的主要功能就是加載真正的uboot并運行之。
2.2.1. 進入_start函數:
_start:#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG.word CONFIG_SYS_DV_NOR_BOOT_CFG #endifb resetldr pc, _undefined_instructionldr pc, _software_interruptldr pc, _prefetch_abortldr pc, _data_abortldr pc, _not_usedldr pc, _irqldr pc, _fiq有一條跳轉指令b reset跳轉到reset函數處去執行
注意,spl的流程在reset中就應該被結束,也就是說在reset中,就應該轉到到BL2,也就是uboot中了。
2.2.2. 進入reset函數
reset:/* Allow the board to save important registers */b save_boot_params @進入reset第一步跳轉到save_boot_params save_boot_params_ret: @ save_boot_params 內部通過cpsr 設置cpu為SVC模式,關閉FIQ和IRQ中斷/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0/* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_cp15 @ 跳轉到cpu_init_cp15 ,初始化協處理器CP15,從而禁用MMU和TLB。bl cpu_init_crit @ 跳轉到cpu_init_crit ,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化 #endifbl _main @ 跳轉到_main ,加載BL2以及跳轉到BL2的主體部分進入reset函數,首先設置cpu為SVC模式,關閉中斷。然后跳轉到cpu_init_cp15 ,初始化協處理器CP15,從而禁用MMU和TLB。跳轉到cpu_init_crit ,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化。最后跳轉到**_main**,加載BL2以及跳轉到BL2的主體部分
- 關閉中斷。
uboot引導linux起到的過程中本身就是一個完成的過程,不需要中斷機制。
2.2.3 cpu_init_cp15函數
ENTRY(cpu_init_cp15)/** Invalidate L1 I/D*/mov r0, #0 @ set up for MCRmcr p15, 0, r0, c8, c7, 0 @ invalidate TLBsmcr p15, 0, r0, c7, c5, 0 @ invalidate icachemcr p15, 0, r0, c7, c5, 6 @ invalidate BP arraymcr p15, 0, r0, c7, c10, 4 @ DSBmcr p15, 0, r0, c7, c5, 4 @ ISB @@ 這里只需要知道是對CP15處理器的部分寄存器清零即可。 @@ 將協處理器的c7\c8清零等等,各個寄存器的含義請參考《ARM的CP15協處理器的寄存器》/** disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002000 @ clear bits 13 (--V-)bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)orr r0, r0, #0x00000002 @ set bit 1 (--A-) Alignorr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB #ifdef CONFIG_SYS_ICACHE_OFFbic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache #elseorr r0, r0, #0x00001000 @ set bit 12 (I) I-cache #endifmcr p15, 0, r0, c1, c0, 0 @@ 通過上述的文章的介紹,我們可以知道cp15的c1寄存器就是MMU控制器 @@ 上述對MMU的一些位進行清零和置位,達到關閉MMU和cache的目的,具體的話去看一下上述文章吧。ENDPROC(cpu_init_cp15)cpu_init_cp15 用來設置 CP15 相關的內容,完成啟動ICACHE,關閉DCACHE,關閉MMU和TLB 。
- 關閉MMU
,MMU是用于虛擬地址向物理地址進行映射的一個結構。在 uboot階段操作的就直接是 物理地址,所以不需要轉換。 - 啟動ICACHE(指令),關閉DCACHE(數據)
啟動指令CACHE課可以加快指令讀取的速度,但是數據CACHE 必須 要關閉,因為它本身是一個CPU的二級緩存,在運行程序的時候可能會往里面去取數據,但是此時ram里面的數據可能并沒有存入到里面,這就可能導致讀取到錯誤的數據。
2.2.4 cpu_init_crit函數(包含lowlevel_init函數)
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)cpu_init_crit函數的內容是跳轉到lowlevel_init函數。
2.2.5. lowlevel_init 函數(平臺級和板級的初始化)
lowlevel_init主要完成平臺級和板級的初始化
在lowlevel_init中,我們要實現如下:
- 檢查一些復位狀態
- 關閉看門狗
- 系統時鐘的初始化
- 內存、DDR的初始化
- 串口初始化(可選)
- Nand flash的初始化
在im6ull中
此時初始化SP指向 內存空間為IRAM(內部ram ,OCRAM 128K ,0x00900000),(初始化內存空間,為第二階段準備ram)
2.2.6. _main函數
** _main函數的主要工作是 **
- 設置c語言的運行環境
- 設置sp和gd的中間環境
- 重定義代碼
- 設置最終的環境
-
因為后面是C語言環境,首先是設置堆棧
-
初始化gd(下圖中global date,內部ram) , 進行清零(同上內部ram)
-
調用 board_init_f 函數(將SP指針從內部IRAM,轉移到外部DDR),主要用來初始化 DDR,定時器,完成代碼拷貝等等
-
調用函數 relocate_code,也就是代碼重定位函數,此函數負責將 uboot 拷貝到新的地方去
-
調用函數 relocate_vectors,對中斷向量表做重定位
-
清除 BSS 段 , 。
bss段不占用空間,都是未初始化的全局變量或者已經初始化為零的變量,本來就是零,直接清零就好。不清零的話未初始化的變量可能會存在未知的數值。 -
設置函數 board_init_r 的兩個參數 , 調用 board_init_r 函數 ,
-
board_init_r 函數打印一些列的信息到串口,然后會進入main_loop() 。main_loop會進行倒計時,如果此時按下回車就會進入uboot的shell交互界面,否則就會自動引導啟動OS系統。
2.2.7. board_init_f 函數
- uboot 會將自己重定位到 DRAM(DDR) 最后面的地址區域,也就是將自己拷貝到 DRAM 最后面的內存區域中。這么做的目的是給 Linux 騰出空間,防止 Linuxkernel 覆蓋掉 uboot,將 DRAM 前面的區域完整的空出來。
- 在拷貝之前肯定要給 uboot 各部分分配好內存位置和大小,比這些信息都保存在 gd 的成員變量中(從板子配置文件里讀取),因此要對 gd 的這些成員變量做初始化。最終形成一個完整的內存“分配圖”,在后面重定位 uboot 的時候就會用到這個內存“分配圖”(外部)。
- 注:上電后芯片內部Boot ROM把uboot搬移到DRAM頭部(0x87800000),重定位則再搬移到DDR后部
2.2.8. relocate_code 函數
- relocate_code 函數是用于代碼拷貝
重定位就是 uboot 將自身拷貝到 DRAM 的另一個地放去繼續運行(DRAM 的高地址處)。我們知道,一個可執行的 bin 文件,其鏈接地址和運行地址要相等,也就是鏈接到哪個地址,在運行之前就要拷貝到哪個地址去。現在我們重定位以后,運行地址就和鏈接地址不同了,這樣尋址的時候不會出問題嗎?
代碼動態重定位原理
分析問題產生原因
r0 = gd->relocaddr = 0x9ff47000 , uboot重定位后的首地址
r1 = 0x87800000 源地址的首地址
r2 = 0x8785dc6c 源地址的結束地址
r4 = 0x9ff46000 - 0x87800000 = 0x18747000 偏移量
拷貝是從r1復制往r0粘貼 , 一次兩個32位
當r1等于r2,拷貝完成
當簡單粗暴的將uboot從0x87800000拷貝到0x9ff47000 ,程序運行時地址和連接地址不同,發生錯誤。uboot解決方法是使用位置無關碼,借用 .rel.dyn 段
使用位置無關碼解重定位后和連接地址不同問題的原理
舉例: board_init 函數會調用 rel_test,rel_test 會調用全局變量 rel_a
源代碼
反匯編代碼
8785dcf8 <rel_a>: 8785dcf8: 00000000 andeq r0, r0, r0878042b4 <rel_test>: 878042b4: e59f300c ldr r3, [pc, #12] ; 878042c8 <rel_test+0x14> 878042b8: e3a02064 mov r2, #100 ; 0x64 878042bc: e59f0008 ldr r0, [pc, #8] ; 878042cc <rel_test+0x18> 878042c0: e5832000 str r2, [r3] 878042c4: ea00d64c b 87839bfc <printf> 878042c8: 8785dcf8 ; <UNDEFINED> instruction: 0x8785dcf8 878042cc: 87842aaf strhi r2, [r4, pc, lsr #21]從反匯編代碼中分析:
- 想要找到 rel_a 的地址,首先 r3 = pc + 12 = 0x878042b4 + 8 + 12 = 0x878042c8 。(由ARM 流水線決定 pc = 當前地址 + 8)
- 之后 r3 在0x878042c8 中存儲數據為0x8785dcf8,即為rel_a地址
- 這里沒直接讀取rel_a , 而是借助0x878042c8 。 0x878042c8 就是Label
重定位后,地址變化
9ffa4cf8 <rel_a>: 9ffa4cf8: 00000000 andeq r0, r0, r09ff4b2b4<rel_test>: 9ff4b2b4: e59f300c ldr r3, [pc, #12] ; 878042c8 <rel_test+0x14> 9ff4b2b8: e3a02064 mov r2, #100 ; 0x64 9ff4b2bc: e59f0008 ldr r0, [pc, #8] ; 878042cc <rel_test+0x18> 9ff4b2c0: e5832000 str r2, [r3] 9ff4b2c4: ea00d64c b 87839bfc <printf> 9ff4b2c8: 8785dcf8 ; <UNDEFINED> instruction: 0x8785dcf8 9ff4b2cc: 87842aaf strhi r2, [r4, pc, lsr #21]- 這時 Label中的值還是重定位之前的,必須要將8785dcf8換為重定位后的rel_a地址
- 重定位后的Label中的數據 = 0x878042c8(老的Label) + 0x18747000(uboot整體的偏移量) = 0x9ffa4cf8;
- 讀取 rel_a 地址為 ,r3 = 0x9ff4b2b4 + 8 + 12 = 0x9ff4b2c8 ,即為Label地址,然后從Label中讀取到 0x9ffa4cf8,為rel_a重定義后真是地址
uboot 中使用 .rel.dyn 段具體實現位置無關碼的原理
完成這個功能在連接的時候需要加上”-pie”
.rel.dyn 段代碼段
8785dcec: 87800020 strhi r0, [r0, r0, lsr #32] 8785dcf0: 00000017 andeq r0, r0, r7, lsl r0 …… 8785e2fc: 878042c8 strhi r4, [r0, r8, asr #5] 8785e300: 00000017 andeq r0, r0, r7, lsl r0- .rel.dyn 段的格式,也就是兩個 4 字節數據為一組。
高 4 字節是 Label 地址標識 0X17,低 4 字節就是 Label 的地址, - 第三行是878042c8,第四行是00000017 。說明878042c8是一個Label。正是上述分析中 存放 rel_a 地址的Label
- 在重定位后,rel_a 真是地址 = Label內數據 + offset(0x18747000)
2.2.9 relocate_vectors函數
- 中斷向量表重定位后的地址,就是重定位后uboot的首地址
2.2.10. board_init_r 函數(板級初始化,進入第二階段)
board_init_f 并沒有初始化所有的外設,還需要做一些后續工作,這些后續工作就是由函數 board_init_r 來完成的
uboot relocate后的板級初始化 ,最后執行run_main_loop
第二階段
2.2.11 run_main_loop函數
- run_main_loop-> main_loop->
- -> bootdelay_process 獲取bootdelay的值,然后保存到stored_bootdelay
全局變量里面,獲取bootcmd環境變量值,并且將其
返回 - -> autoboot_command 參數是bootcmd的值。檢查倒計時內有無打斷
- -> abortboot 參數為boot delay,此函數會處理倒計時
- -> abortboot_normal 參數為boot delay,此函數會處理倒計時
- ->run_command_list函數,參數為s(bootcmd的命令)倒計時結束時hush shell沒輸入執行,啟動內核
- -> abortboot 參數為boot delay,此函數會處理倒計時
- -> cli_loop 是uboot命令模式處理函數。
- -> parse_file_outer
- -> parse_stream_outer
- -> parse_stream 解析輸入的字符,得到命令
- -> run_list 運行命令
- -> run_list_real
- -> run_pipe_real
- -> cmd_process 處理命令,也就是執行命令
- -> run_pipe_real
- -> run_list_real
- -> parse_stream_outer
- -> parse_file_outer
- -> bootdelay_process 獲取bootdelay的值,然后保存到stored_bootdelay
進入了main_loop()函數。
2.2.11 main_loop()函數
void main_loop(void) {const char *s;bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); // 這里用于標記uboot的進度,對于tiny210來說起始什么都沒做cli_init(); // cli的初始化,主要是hush模式下的初始化run_preboot_environment_command(); // preboot相關的東西,后續有用到再說明s = bootdelay_process();if (cli_process_fdt(&s))cli_secure_boot_cmd(s);autoboot_command(s); // autoboot的東西,后續使用autoboot的時候再專門說明cli_loop(); // 進入cli的循環模式,也就是命令行模式panic("No CLI available"); }- 調用 bootstage_mark_name 函數,打印出啟動進度。
- 調用 cli_init 函數,跟命令初始化有關,初始化 hush shell 相關的變量。( hush shell 為uboot倒計時結束的輸入shell)
- run_preboot_environment_command 函數,獲取環境變量 perboot 的內容,perboot
是一些預啟動命令 - bootdelay_process 函數,此函數會讀取環境變量 bootdelay (延遲時間)和 bootcmd(啟動內核命令) 的內容,
然后將 bootdelay 的值賦值給全局變量 stored_bootdelay,返回值為環境變量 bootcmd 的值。 - autoboot_command(s)函數,此函數就是檢查倒計時是否結束?倒計時結束之前有
沒有被打斷?輸入參數s為bootcmd的命令- 倒計時自然結束執行函數run_command_list,此函數會執行參數 s 指定的一系列命令,也就是環境變量 bootcmd 的命令,bootcmd 里面保存著默認的啟動命令,因此 linux 內核啟動
- 倒計時結束之前按下了鍵盤上的按鍵,那么 run_command_list函數就不會執行,執行后面的cli_loop函數
- cli_loop函數,是 uboot 的命令行處理函數
2.2.12 cli_loop函數
cli_loop 函數是 uboot 的命令行處理函數,我們在 uboot 中輸入各種命令,進行各種操作就是有 cli_loop 來處理的
void cli_loop(void) {#ifdef CONFIG_SYS_HUSH_PARSERparse_file_outer();/* This point is never reached */for (;;);#elsecli_simple_loop(); //永遠不會執行#endif /*CONFIG_SYS_HUSH_PARSER*/ }- 調用 parse_file_outer函數
2.2.13 parse_file_outer函數
int parse_file_outer(void) {int rcode;struct in_str input;setup_file_in_str(&input);rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);return rcode;}- 調用函數 setup_file_in_str 初始化變量 input 的成員變量
- 調用函數 parse_stream_outer,這個函數就是 hush shell 的命令解釋器,負責接收命令行輸入,然后解析并執行相應的命令
2.3.14 函數 parse_stream_outer
static int parse_stream_outer(struct in_str *inp, int flag){struct p_context ctx;o_string temp=NULL_O_STRING;int rcode;int code = 1;do {......rcode = parse_stream(&temp, &ctx, inp,flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');......if (rcode != 1 && ctx.old_flag == 0) {......run_list(ctx.list_head);......} else {......}b_free(&temp);/* loop on syntax errors, return on EOF */} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&(inp->peek != static_peek || b_peek(inp)));return 0; }- do-while 循環就是處理輸入命令的
- 函數 parse_stream 進行命令解析
- 調用 run_list 函數來執行解析出來的命令,run_list 調用 run_list_real 函數,run_list_real 函數調用 run_pipe_real 函數,run_pipe_real 函數調用 cmd_process 函數。最終通過函數 cmd_process 來處理命令
2.3.15 補充:uboot的命令
uboot把所有命令的數據結構都放在一個表格中,我們后續稱之為命令表。表中的每一項代表著一個命令,其項的類型是cmd_tbl_t。
struct cmd_tbl_s {char *name; /* Command Name */int maxargs; /* maximum number of arguments */int repeatable; /* autorepeat allowed? *//* Implementation function */int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);char *usage; /* Usage message (short) */ #ifdef CONFIG_SYS_LONGHELPchar *help; /* Help message (long) */ #endif }; typedef struct cmd_tbl_s cmd_tbl_t;參數說明如下:
name:定義一個命令的名字。 其實就是執行的命令的字符串。這個要注意。
maxargs:這個命令支持的最大參數
repeatable:是否需要重復
cmd:命令處理函數的地址
usage:字符串,使用說明
help:字符串,幫助
Uboot使用U_BOOT_CMD來定義一個命令。CONFIG_CMD_XXX來使能uboot中的某個命令。
//uboot命令定義代碼 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)U_BOOT_CMD最終是定義了一個cmd_tbl_t類型的結構體變量,所有的命令最終都是存放在.u_boot_list段里面。cmd_tbl_t結構體中的cmd成員變量就是具體的命令執行函數,命令執行函數都是do_xxx。
// bootm就是我們的命令字符串 // 在tiny210.h中定義了最大參數數量是64 // #define CONFIG_SYS_MAXARGS 64 /* max number of command args */ // 1表示重復一次 // 對應命令處理函數是do_bootm // usage字符串是"boot application image from memory" // help字符串是bootm_help_text定義的字符串。 U_BOOT_CMD(bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,"boot application image from memory", bootm_help_text );//并且命令處理函數的格式如下: //當命令處理函數執行成功時,需要返回0.返回非0值 int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])2.3.16 函數 cmd_process
函數 cmd_process 來處理命令并執行
- cmd_process
- ->find_cmd 從.u_boot_list段里面查找命令,當找到對應的命令以后以返回值的
形式給出,為cmd_tbl_t類型 - ->cmd_call
->cmdtp->cmd 直接引用cmd成員變量,執行do_xxx函數
- ->find_cmd 從.u_boot_list段里面查找命令,當找到對應的命令以后以返回值的
函數 cmd_process代碼
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],int *repeatable, ulong *ticks) {enum command_ret_t rc = CMD_RET_SUCCESS;cmd_tbl_t *cmdtp;/* Look up command in command table */cmdtp = find_cmd(argv[0]);// 第一個參數argv[0]表示命令,調用find_cmd獲取命令對應的表項cmd_tbl_t。if (cmdtp == NULL) {printf("Unknown command '%s' - try 'help'\n", argv[0]);return 1;}/* found - check max args */if (argc > cmdtp->maxargs)rc = CMD_RET_USAGE;// 檢測參數是否正常/* If OK so far, then do the command */if (!rc) {if (ticks)*ticks = get_timer(0);rc = cmd_call(cmdtp, flag, argc, argv);// 調用cmd_call執行命令表項中的命令,成功的話需要返回0值if (ticks)*ticks = get_timer(*ticks);// 判斷命令執行的時間*repeatable &= cmdtp->repeatable;// 這個命令執行的重復次數存放在repeatable中的}if (rc == CMD_RET_USAGE)rc = cmd_usage(cmdtp);// 命令格式有問題,打印幫助信息return rc; }- find_cmd函數, 從.u_boot_list段獲取命令對應的命令表項cmd_tbl_t 。
- cmd_call函數,執行命令表項cmd_tbl_t 中的命令,執行do_xxx函數
2.3.17 uboot啟動linux內核(bootz命令)
在run_main_loop函數倒計時結束時沒有輸入,uboot執行bootcmd命令,啟動內核
-
bootcmd 保存著 uboot 默認命令,這些命令一般都是用來啟動 Linux 內核的,讀取Linux鏡像文件和設備樹文件(從MMC、SD卡、網絡讀取等)到DARM(DDR)中,然后啟動內核(bootz、bootm)。
-
bootm和bootz的不同地方
- bootm用于加載uImage和ramdisk
bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address} - bootz用于加載zImage和ext4文件系統
bootz ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}
- bootm用于加載uImage和ramdisk
-
bootz的使用(tftp從網絡讀取)
tftp 80800000 zImage tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb bootz 80800000 - 83000000
bootz流程
2.3.18 do_bootz函數
do_bootz是bootz的執行函數
do_bootz
- -> bootz_start
- -> do_bootm_states 階段為BOOTM_STATE_START
- -> bootm_start 對images全局變量清零
- -> images->ep = 0X80800000
- ->bootz_setup 判斷zImage是否正確
- -> bootm_find_images 查找鏡像文件
- -> boot_get_fdt 找到設備樹,然后將設備樹起始地址和長度,寫入到images的ft_addr和ft_len成員變量中。
- -> bootm_disable_interrupts 關閉中斷相關
- -> images.os.os = IH_OS_LINUX; 表示要啟動Linux系統
- -> do_bootm_states 狀態BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 、
BOOTM_STATE_OS_GO,- -> bootm_os_get_boot_func 查找Linux內核啟動函數。找到Linux內核啟動函數
do_bootm_linux,賦值給boot_fn。 - -> boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); 就是do_bootm_linux。
- -> boot_prep_linux 啟動之前的一些工作,對于使用設備樹來說,他會將Bootargs傳遞給Linux內核,通過設備樹完成。也就是向Linux內核傳參。
- -> boot_jump_linux
- -> machid= gd->bd->bi_arch_number;
- -> kernel_entry = (void (*)(int, int, uint))images->ep; 0X80800000。
- -> announce_and_cleanup 輸出Starting kernel……
- -> kernel_entry(0, machid, r2); 啟動Linux內核。Uboot的最終使命,啟動Linux內核。
- -> bootm_os_get_boot_func 查找Linux內核啟動函數。找到Linux內核啟動函數
- -> do_bootm_states 階段為BOOTM_STATE_START
2.3.19 kernel_entry函數
kernel_entry函數是內核的入口函數。內核鏡像第一行代碼
kernel_entry(0, machid, r2);此函數有三個參數:zero,arch,params,
- 第一個參數 zero 同樣為 0;
- 第二個參數為機器 ID;
- 第三個參數啟動參數標記列表(ATAGS)或者設備樹(DTB)首地址,ATAGS 是傳統的方法,用于傳遞一些命令行信息啥的,如果使用設備樹的話就要傳遞設備樹(DTB)。
- 是匯編函數,因為內核開始是匯編代碼。
- images->ep 保存著 Linux內核鏡像的起始地址,也就是kernel_entry的地址
kernel_entry函數的傳參
- 向匯編函數傳遞參數要使用 r0、r1 和 r2寄存器
- r0 = 0
- r1 = 機器類型ID(machid)
- Linux 內核會在自己的機器 ID 列表里面查找是否存在與 uboot 傳遞進來的 machid 匹配的項目,如果存在就說明 Linux 內核支持這個機器,那么 Linux 就會啟動!
- 如果使用設備樹的話這個 machid 就無效了,設備樹存有一個“兼容性”這個屬性,Linux 內核會比較“兼容性”屬性的值(字符串)來查看是否支持這個機器。
- r2 =
- 如果使用設備樹的話,r2 應該是設備樹的起始地址
- 如果不使用設備樹的話,r2 應該是 uboot 傳遞給 Linux 的參數起始地址,也就是環境變量 bootargs 的值
如何從uboot跳轉到內核
直接修改PC寄存器的值為Linux內核所在的地址,CPU從內核所在的地址去取指令,從而執行內核代碼
為什么要傳參數給內核
在此之前, uboot已經完成了硬件的初始化,可以說已經噎適應了“這塊開發板。然而,內核并不是對于所有的開發板都能完美適配的(如果適配了,可想而知這個內核有多龐大,又或者有新技術發明了,可以完美的適配各種開發板),此時,對于開發板的環境一無所知。所以,要想啟動 Linux內核, uboot必須要給內核傳遞一些必要的信息來告訴內核當前所處的環境。
總結
以上是生活随笔為你收集整理的uboot启动流程详细分析(基于i.m6ull)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试相关东东
- 下一篇: ChatGPT实现代码生成