23. 基於Cortex-A9 uboot代碼啓動分析

本篇文章是彭老師第一次在B站直播間,邊直播邊記錄筆記,視頻已經上傳到B站。linux

如今完善整理成該篇文章,有想學習uboot啓動的代碼詳細流程的老鐵能夠進入我B站空間配合視頻一塊兒學習。android

視頻地址
B站用戶名:一口Linuxios

在這裏插入圖片描述

@ubuntu

前言

咱們在前面的arm系列課程,已經講解了arm的架構、彙編指令、異常、經常使用外設的控制器驅動,那麼咱們已經具有開發arm系列產品的基本技能。服務器

本篇給你們介紹一款比較經常使用的bootloader:uboot,經過uboot的介紹以及源代碼的詳細分析,讓你們把以前全部ARM相關的知識點融會貫通起來。網絡

1、uboot

1. 概念

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

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命令比較多,下面只列舉網絡啓動要用到的命令:工具

命令 含義
bootdelay 執行自動啓動(bootcmd中的命令)的等候秒數
baudrate 串口控制檯的波特率
netmask 以太網的網絡掩碼
ethaddr 以太網的MAC地址
bootfile 默認的下載文件名
printenv 打印Uboot環境變量
setenv 設置Uboot環境變量
ipaddr 本地的IP地址
serverip TFTP服務器端的IP地址
gateway 以太網的網關
bootcmd 自動啓動時執行命令
bootargs 傳遞給Linux內核的啓動參數
bootm 引導啓動存儲在內存中的程序映像。這些內存包括RAM和能夠永久保存的Flash。

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 開發板地址

2、exynos-4412 Soc 啓動順序

要想了解exynos-4412的啓動順序,咱們首先須要瞭解該soc的內存佈局。

1. exynos-4412內存佈局

一般一款soc的內存在廠家設計的時候就已經規定死了,對於使用者來講,咱們沒法改變。

memery map咱們只關心和啓動相關的一個地址,

    1. iROM 在soc內部,出廠時廠家固化了特定的程序,iROM中程序對應用戶來講不可改變
    1. iRAM 在soc內部,速度較快,但空間不大
    1. 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寄存器須要提供對應的啓動信息。

3、內核啓動流程概述

1. 內核啓動流程 概述

uboot啓動流程
如上圖所示:

  1. 設備上電以後,先執行iROM中的出廠代碼,先進行必要硬件的初始化
    去執行uboot,
  2. 一般把kernel、設備樹文件放到flash中
  3. 程序啓動以後,每每先從flash啓動,運行uboot
  4. 第一步:先進行硬件的初始化(svc模式棧、clock、內存、串口)
    第二步:自搬移:把uboot從flash中拷貝到RAM中,跳轉到RAM中執行剩下的uboot代碼
    第三步:把內核拷貝到RAM中,執行內核,把控制權交給內核。

2. 內核啓動詳細流程

開發板從上電到啓動內核的過程

4、uboot啓動流程代碼詳解

在三星的SoC中, 啓動流程能夠分爲三個階段BL0, BL1, BL2, BL3, 三星本身的手冊對BL1的解釋也不盡相同, 一種是將在iRAM中運行的程序都歸結爲BL1; 一種是將iRAM中三星加密的代碼bl1.bin做爲BL1, iRAM中剩餘的部分做爲BL2, 本文采用後者, 他們的主要分工以下:

  • BL0: ARM的起始地址都是0地址, 三星的芯片通常將0地址映射到iROM中, BL0就是指iROM中固化的啓動代碼, 主要負責加載BL1
  • BL1: 三星對於bootloader的加密代碼bl1.bin, 要放在外設中uboot.bin的頭上, 和一部分uboot.bin一塊兒加載到iRAM中運行.
  • BL2: 從(nand/sd/usb)中拷貝的uboot.bin頭最大14K到iRAM中代碼中除去bl1.bin後剩餘的部分, 負責設置CPU爲SVC模式, 關閉MMU, 關閉中斷, 關閉iCache, 關閉看門狗, 初始化DRAM,初始化時鐘, 初始化串口, 設置棧, 校驗BL2並將其搬移到DRAM高位地址, 重定位到DRAM中執行BL3
  • BL3:是指在代碼重定向後在內存中執行的uboot的完整代碼, 負責初始化外設,更新向量表, 清BSS, 準備內核啓動參數, 加載並運行OS內核

能夠藉助下圖理解這個流程

img

咱們常說的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

1. BL2

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

2. 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     }

3. uboot啓動代碼流程概要

代碼只分析到uboot命令行,函數main_loop()位置。

4. 啓動代碼詳細分析

_start入口位於如下文件:

u-boot-2013.01/arch/arm/cpu/armv7/start.S

第一階段:

第二階段

第二階段代碼從_main開始:

以上代碼詳細解釋,請結合B站視頻同步學習。

5、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

  1. 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節。

  1. 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仍然有數據,因此在此啓動後,只須要從特殊的寄存器中讀取相應的值,就能夠知道以前是由於什麼緣由休眠,進而回復休眠以前的上下文便可。

  1. uboot代碼搬到ram以後,代碼的運行地址發生了變化,如何保證程序跳轉不會出錯?

除了要保證uboot代碼是基於地址無關的,此外.rel.dyn幫咱們解決了,其實主要仍是編譯器幫咱們作了不少工做。

位置無關碼參考《15. 從0開始學ARM-位置無關碼》

  1. 設備啓動的時候,有可能直接從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中。

文中用到的源碼、datasheet、交叉編譯工具能夠關注GH,後臺回覆 【uboot2013】便可得到。

更多嵌入式 ARM知識,請關注 一口Linux。

相關文章
相關標籤/搜索