詳解Cortex-A9 uboot啟動代碼
前言
我們在前面的arm系列課程,已經講解了arm的架構、匯編指令、異常、常用外設的控制器驅動,那么我們已經具備開發arm系列產品的基本技能。
本篇給大家介紹一款比較常用的bootloader:uboot,通過uboot的介紹以及源代碼的詳細分析,讓大家把之前所有ARM相關的知識點融會貫通起來。
一、uboot
1. 概念
U-Boot 是一個主要用于嵌入式系統的引導加載程序,可以支持多種不同的計算機系統結構,包括PPC、ARM、AVR32、MIPS、x86、68k、Nios與MicroBlaze。這也是一套在GNU通用公共許可證之下發布的自由軟件。
U-Boot不僅僅支持嵌入式Linux系統的引導,它還支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, android嵌入式操作系統。其目前要支持的目標操作系統是OpenBSD, NetBSD, FreeBSD,4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX, RTEMS, ARTOS, android。
2. uboot基本功能
U-Boot可支持的主要功能列表:
- 系統引導支持NFS掛載、RAMDISK(壓縮或非壓縮)形式的根文件系統;支持NFS掛載、從FLASH中引導壓縮或非壓縮系統內核;
- 基本輔助功能強大的操作系統接口功能;可靈活設置、傳遞多個關鍵參數給操作系統,適合系統在不同開發階段的調試要求與產品發布,尤以Linux支持最為強勁;支持目標板環境參數多種存儲方式,如FLASH、NVRAM、EEPROM;
- CRC32校驗可校驗FLASH中內核、RAMDISK鏡像文件是否完好;
- 設備驅動串口、SDRAM、FLASH、以太網、LCD、NVRAM、EEPROM、鍵盤、USB、PCMCIA、PCI、RTC等驅動支持;
- 上電自檢功能SDRAM、FLASH大小自動檢測;SDRAM故障檢測;CPU型號。
3. 常用命令
uboot命令比較多,下面只列舉網絡啟動要用到的命令:
4. 配置參數舉例
以下以網絡下載內核、網絡掛載nfs為例。
1)ubuntu環境
ubuntu ip:192.168.6.186
nfs配置:
配置文件如下:
- /etc/exports
配置信息如下:
nfs
2)開發板設置
開發板ip:192.168.6.187
配置命令:
- setenv ipaddr 192.168.6.187 ;板子的ip
- setenv serverip 192.168.6.186 ;虛擬機的ip
- setenv gatewayip 192.168.1.1 ;網關
- saveenv ;保存配置
加載內核和設備樹
- setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000
bootcmd:uboot2啟動之后,首先先執行找到這個參數,執行后面的命令。
從tftp服務器下載內核鏡像uImage到地址41000000,設備樹文件exynos4412-fs4412.dtb到42000000,并通過命令bootm加載啟動內核。
- 掛載nfs
- setenv bootargs root=/dev/nfs nfsroot=192.168.6.186:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.6.187
掛載nfs文件系統
- root=/dev/nfs
- nfsroot=192.168.6.186:/rootfs nfs服務器地址192.168.6.186,目錄為/rootfs,
- rw 文件系統操作權限為可續寫
- console=ttySAC2,115200 串口名稱和波特率
- init=/linuxrc 內核啟動后運行的進程為linuxrc
- ip=192.168.6.187 開發板地址
二、exynos-4412 Soc 啟動順序
要想了解exynos-4412的啟動順序,我們首先需要了解該soc的內存布局。
1. exynos-4412內存布局
通常一款soc的內存在廠家設計的時候就已經規定死了,對于使用者來說,我們無法改變。

我們只關心和啟動相關的一個地址,
- iROM 在soc內部,出廠時廠家固化了特定的程序,iROM中程序對應用戶來說不可改變
- iRAM 在soc內部,速度較快,但空間不大
- DMC RAM控制器,位于SOC內部,用于驅動RAM,大容量的RAM都需要連接到該控制器
2. Booting Sequence
不同的廠家的啟動順序是不太一樣的,本篇主要以三星的exynos-4412 soc為基礎,講解該基于該板子的uboot啟動順序。

根據上圖,系統啟動的大概順序:
- iROM在SOC內部,是一個64KB的ROM,他樹池化一些系統啟動必須的功能。比如:時鐘、棧。
- iROM負責從特殊的啟動外設加載BL1的image到soc內部的256KB的SRAM中。啟動的外設由操作按鈕來決定的。根據不同按鍵的值,iROM將會對bl1 的image做不同的校驗。
- BL1初始化系統時鐘和DRAM控制器,然后從啟動外設加載OS image到DRAM中。根據啟動按鈕的值的不同,BL1會對OS做不同的校驗。
- 啟動完成之后,BL1跳轉到操作系統(kernel)。
iROM會根據OM 引腳的不同選擇不同的啟動設備,對應的OM寄存器需要提供對應的啟動信息。
三、內核啟動流程概述
1. 內核啟動流程 概述
uboot啟動流程
如上圖所示:
- 設備上電之后,先執行iROM中的出廠代碼,先進行必要硬件的初始化 去執行uboot,
- 通常把kernel、設備樹文件放到flash中
- 程序啟動之后,往往先從flash啟動,運行uboot
- 第一步:先進行硬件的初始化(svc模式棧、clock、內存、串口) 第二步:自搬移:把uboot從flash中拷貝到RAM中,跳轉到RAM中執行剩下的uboot代碼
- 第三步:把內核拷貝到RAM中,執行內核,把控制權交給內核。
2. 內核啟動詳細流程
開發板從上電到啟動內核的過程
四、uboot啟動流程代碼詳解
1. lds文件
要想了解uboot整個項目的代碼流程,必須首先了解鏈接腳本【鏈接腳本參考《7. 從0開始學ARM-GNU偽指令,lds使用》】。
該文件決定了uboot最終生成的鏡像文件,各個段的布局。
uboot鏈接腳本如下:
- u-boot-2013.01/arch/arm/cpu/u-boot.lds
文件內容:
- 26 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
- 27 OUTPUT_ARCH(arm)
- 28 ENTRY(_start)
- 29 SECTIONS
- 30 {
- 31 . = 0x00000000;
- 32
- 33 . = ALIGN(4);
- 34 .text :
- 35 {
- 36 __image_copy_start = .;
- 37 CPUDIR/start.o (.text*)
- 38 *(.text*)
- 39 }
- 40
- 41 . = ALIGN(4);
- 42 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
- 43
- 44 . = ALIGN(4);
- 45 .data : {
- 46 *(.data*)
- 47 }
- 48
- 49 . = ALIGN(4);
- 50
- 51 . = .;
- 52
- 53 . = ALIGN(4);
- 54 .u_boot_list : {
- 55 #include <u-boot.lst>
- 56 }
- 57
- 58 . = ALIGN(4);
- 59
- 60 __image_copy_end = .;
- 61
- 62 .rel.dyn : {
- 63 __rel_dyn_start = .;
- 64 *(.rel*)
- 65 __rel_dyn_end = .;
- 66 }
- 67
- 68 .dynsym : {
- 69 __dynsym_start = .;
- 70 *(.dynsym)
- 71 }
- 72
- 73 _end = .;
- 74
- 75 /*
- 76 * Deprecated: this MMU section is used by pxa at present but
- 77 * should not be used by new boards/CPUs.
- 78 */
- 79 . = ALIGN(4096);
- 80 .mmutable : {
- 81 *(.mmutable)
- 82 }
- 83
- 84 .bss __rel_dyn_start (OVERLAY) : {
- 85 __bss_start = .;
- 86 *(.bss*)
- 87 . = ALIGN(4);
- 88 __bss_end__ = .;
- 89 }
- 90
- 91 /DISCARD/ : { *(.dynstr*) }
- 92 /DISCARD/ : { *(.dynamic*) }
- 93 /DISCARD/ : { *(.plt*) }
- 94 /DISCARD/ : { *(.interp*) }
- 95 /DISCARD/ : { *(.gnu*) }
- 96 }
- 97
核心內容解釋:
- 27 OUTPUT_ARCH(arm) : 該鏡像運行在arm架構的硬件上
- 28 ENTRY(_start) : 程序的入口是 _start
- 29 SECTIONS
- 30 {
- 31 . = 0x00000000; : 程序的鏈接地址,不是運行地址【uboot一定是位置無關碼】
- 34 .text :
- 35 {
- 36 __image_copy_start = .; : 宏對應整個程序編譯好后首地址,自搬移代碼的初始位置
- 37 CPUDIR/start.o (.text*) : 第一個目標文件CPUDIR/start.o中的代碼段
- 38 *(.text*) : 剩下的目標文件的代碼段
- 39 }
- 60 __image_copy_end = .; : 自搬移代碼的結束為止
BSS全局未初始化變量、全局初始化為0的變量所在的段:
- 84 .bss __rel_dyn_start (OVERLAY) : {
- 85 __bss_start = .;
- 88 __bss_end__ = .;
- 89 }
2. uboot啟動代碼流程概要
代碼只分析到uboot命令行,函數main_loop()位置。

3. 啟動代碼詳細分析
_start入口位于以下文件:
- u-boot-2013.01/arch/arm/cpu/armv7/start.S
第一階段:
第二階段
第二階段代碼從_main開始:

以上代碼詳細解釋,請結合B站視頻同步學習。
五、uboot啟動的幾個關鍵知識點
1.如何判斷第一條機器指令的位置?
鏈接腳本決定了內存的布局。
uboot鏈接腳本如下:
- u-boot-2013.01/arch/arm/cpu/u-boot.lds
文件內容:
- 28 ENTRY(_start)
- 29 SECTIONS
- 30 {
- 31 . = 0x00000000;
- 32
uboot的入口是_start
鏈接地址是0x00000000
2.uboot如何搬運代碼?
代碼位于:
- u-boot-2013.01/arch/arm/cpu/armv7/start.S
搬移代碼如下:
- ENTRY(relocate_code)
- mov r4, r0 /* save addr_sp */
- mov r5, r1 /* save addr of gd */
- mov r6, r2 /* save addr of destination */
- adr r0, _start
- cmp r0, r6
- moveq r9, #0 /* no relocation. relocation offset(r9) = 0 */
- beq relocate_done /* skip relocation */
- mov r1, r6 /* r1 <- scratch for copy_loop */
- ldr r3, _image_copy_end_ofs
- add r2, r0, r3 /* r2 <- source end address */
- copy_loop:
- ldmia r0!, {r9-r10} /* copy from source address [r0] */
- stmia r1!, {r9-r10} /* copy to target address [r1] */
- cmp r0, r2 /* until source end address [r2] */
- blo copy_loop
詳情參考第四章,第3節。
3.uboot中,如何判斷此次開機是從斷電狀態開機還是從休眠狀態啟動的?
- board/samsung/fs4412/lowlevel_init.S
代碼如下:
- 41 lowlevel_init:
- 54 /* AFTR wakeup reset */
- 55 ldr r2, =S5P_CHECK_DIDLE
- 56 cmp r1, r2
- 57 beq exit_wakeup
- 58
- 59 /* LPA wakeup reset */
- 60 ldr r2, =S5P_CHECK_LPA
- 61 cmp r1, r2
- 62 beq exit_wakeup
- 63
- 64 /* Sleep wakeup reset */
- 65 ldr r2, =S5P_CHECK_SLEEP
- 66 cmp r1, r2
- 67 beq wakeup_reset
- 112 wakeup_reset:
- 113 bl system_clock_init
- 114 bl mem_ctrl_asm_init
- 115 bl tzpc_init
- 116
- 117 exit_wakeup:
- 118 /* Load return address and jump to kernel */
- 119 ldr r0, =(EXYNOS4_POWER_BASE + INFORM0_OFFSET)
- 120
- 121 /* r1 = physical address of exynos4210_cpu_resume function */
- 122 ldr r1, [r0]
- 123
- 124 /* Jump to kernel*/
- 125 mov pc, r1
由上可知,當手機因為各種原因進入休眠時,會將當前程序執行的上下文保護起來,并向一些pmic的寄存器中寫入指定的數據,以表明此次是因為何種原因進入休眠。
而手機并沒有完全斷電,而是處于一個低功耗模式下,此時啟動RAM仍然有數據,所以在此啟動后,只需要從特殊的寄存器中讀取相應的值,就可以知道之前是因為什么原因休眠,進而回復休眠之前的上下文即可。
3.uboot代碼搬到ram之后,代碼的運行地址發生了變化,如何保證程序跳轉不會出錯?
除了要保證uboot代碼是基于地址無關的,此外.rel.dyn幫我們解決了,其實主要還是編譯器幫我們做了很多工作。
位置無關碼參考《15. 從0學ARM-什么是位置無關碼?》
4.設備啟動的時候,有可能直接從ram啟動, 如何知道當前是從flah啟動還是ram啟動的?
文件:
- board/samsung/fs4412/lowlevel_init.S
代碼:
lowlevel_init:
- 85 /*
- 86 * If U-boot is already running in ram, no need to relocate U-Boot.
- 87 * Memory controller must be configured before relocating U-Boot
- 88 * in ram.
- 89 */
- 90 ldr r0, =0x0ffffff /* r0 <- Mask Bits*/
- 91 bic r1, pc, r0 /* pc <- current addr of code */
- 92 /* r1 <- unmasked bits of pc */
- 93 ldr r2, _TEXT_BASE /* r2 <- original base addr in ram */
- 94 bic r2, r2, r0 /* r2 <- unmasked bits of r2*/
- 95 cmp r1, r2 /* compare r1, r2 */
- 96 beq 1f /* r0 == r1 then skip sdram init */
原理:RAM地址空間是:0x40000000-0xA0000000 0xA0000000-0x00000000 而iROM/iRAM地址的bit:28-31均是0,所以只需要讀取出執行到lowlevel_init時pc的值,判斷其bit:28-31是否是0即可知道現在代碼是否運行在RAM中。