本篇文章是彭老師第一次在B站直播間,邊直播邊記錄筆記,視頻已經上傳到B站。linux
如今完善整理成該篇文章,有想學習uboot啓動的代碼詳細流程的老鐵能夠進入我B站空間配合視頻一塊兒學習。android
【視頻地址】
B站用戶名:一口Linuxios
@ubuntu
咱們在前面的arm系列課程,已經講解了arm的架構、彙編指令、異常、經常使用外設的控制器驅動,那麼咱們已經具有開發arm系列產品的基本技能。服務器
本篇給你們介紹一款比較經常使用的bootloader:uboot,經過uboot的介紹以及源代碼的詳細分析,讓你們把以前全部ARM相關的知識點融會貫通起來。網絡
U-Boot 是一個主要用於嵌入式系統的引導加載程序,能夠支持多種不一樣的計算機系統結構,包括PPC、ARM、AVR3二、MIPS、x8六、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。ide
U-Boot可支持的主要功能列表:函數
uboot命令比較多,下面只列舉網絡啓動要用到的命令:工具
命令 | 含義 |
---|---|
bootdelay | 執行自動啓動(bootcmd中的命令)的等候秒數 |
baudrate | 串口控制檯的波特率 |
netmask | 以太網的網絡掩碼 |
ethaddr | 以太網的MAC地址 |
bootfile | 默認的下載文件名 |
printenv | 打印Uboot環境變量 |
setenv | 設置Uboot環境變量 |
ipaddr | 本地的IP地址 |
serverip | TFTP服務器端的IP地址 |
gateway | 以太網的網關 |
bootcmd | 自動啓動時執行命令 |
bootargs | 傳遞給Linux內核的啓動參數 |
bootm | 引導啓動存儲在內存中的程序映像。這些內存包括RAM和能夠永久保存的Flash。 |
如下以網絡下載內核、網絡掛載nfs爲例。
ubuntu ip:192.168.6.186
nfs配置:
配置文件以下:
/etc/exports
配置信息以下:
開發板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加載啓動內核。
setenv bootargs root=/dev/nfs nfsroot=192.168.6.186:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.6.187
掛載nfs文件系統,
要想了解exynos-4412的啓動順序,咱們首先須要瞭解該soc的內存佈局。
一般一款soc的內存在廠家設計的時候就已經規定死了,對於使用者來講,咱們沒法改變。
咱們只關心和啓動相關的一個地址,
不一樣的廠家的啓動順序是不太同樣的,本篇主要以三星的exynos-4412 soc爲基礎,講解該基於該板子的uboot啓動順序。
根據上圖,系統啓動的大概順序:
iROM會根據OM 引腳的不一樣選擇不一樣的啓動設備,對應的OM寄存器須要提供對應的啓動信息。
如上圖所示:
在三星的SoC中, 啓動流程能夠分爲三個階段BL0, BL1, BL2, BL3, 三星本身的手冊對BL1的解釋也不盡相同, 一種是將在iRAM中運行的程序都歸結爲BL1; 一種是將iRAM中三星加密的代碼bl1.bin做爲BL1, iRAM中剩餘的部分做爲BL2, 本文采用後者, 他們的主要分工以下:
能夠藉助下圖理解這個流程
咱們常說的uboot是一個兩階段bootloader,就是指上述的BL2和BL3. BL2主要作硬件直接相關的初始化,使用匯編編寫;BL3主要爲操做系統的運行準備環境,主要用C編寫,這裏以ARM平臺爲例分析其啓動流程。下面是啓動過程當中主要涉及的文件
arch/arm/cpu/armv7/start.S
board/samsung/myboard/lowlevel_init.S
arch/arm/lib/crt0.S
arch/arm/lib/board.c
arch/samsung/myboard/myboard.c
BL2的主要文件和任務流程以下
arch/arm/cpu/armv7/start.S
\1. 設置CPU爲SVC模式
\2. 關閉MMU
\3. 關閉Cache
\4. 跳轉到lowlevel_init.S low_level_init
board/samsung/origen/lowlevel_init.S
\5. 初始化時鐘
\6. 初始化內存
\7. 初始化串口
\8. 關閉看門狗
\9. 跳轉到crt0.S _main
arch/arm/lib/crt0.S
\10. 設置棧
\11. 初始化C運行環境
\12. 調用board_init_f()
arch/arm/lib/board.c
\13. board_init_f對全局信息GD結構體進行填充
arch/arm/lib/crt0.S
\14. 代碼重定位------------BL2的最後的工做, 執行完就進入DRAM執行BL2
要想了解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 }
代碼只分析到uboot命令行,函數main_loop()位置。
_start入口位於如下文件:
u-boot-2013.01/arch/arm/cpu/armv7/start.S
第二階段代碼從_main開始:
以上代碼詳細解釋,請結合B站視頻同步學習。
連接腳本決定了內存的佈局。
uboot連接腳本以下:
u-boot-2013.01/arch/arm/cpu/u-boot.lds
文件內容:
28 ENTRY(_start) 29 SECTIONS 30 { 31 . = 0x00000000; 32
uboot的入口是_start
連接地址是0x00000000
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節。
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仍然有數據,因此在此啓動後,只須要從特殊的寄存器中讀取相應的值,就能夠知道以前是由於什麼緣由休眠,進而回復休眠以前的上下文便可。
除了要保證uboot代碼是基於地址無關的,此外.rel.dyn幫咱們解決了,其實主要仍是編譯器幫咱們作了不少工做。
位置無關碼參考《15. 從0開始學ARM-位置無關碼》
文件:
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中。
文中用到的源碼、datasheet、交叉編譯工具能夠關注GH,後臺回覆 【uboot2013】便可得到。
更多嵌入式 ARM知識,請關注 一口Linux。