uboot啓動流程之上電啓動到第一次準備好C語言執行環境

2020-08-10 14:39:34

本文以ast2500evb板子爲例來說明uboot的啓動過程:

 

在uboot/Makefile中,我們知道uboot.bin依賴uboot, uboot是由各種*.o鏈接而成,使用的鏈接指令碼爲uboot/board/ast2500evb/u-boot.lds.

在uboot/board/ast2500evb/u-boot.lds定義了程式的入口_start, 且其地址爲0x00000000.

Arm上電後,從reset vector address處取指令來run。

所以,當我們編出u-boot.bin檔案時,需要將其燒寫到flash的0地址處,且falsh要對映到arm的0地址處。從u-boot.lds的描述看,在flash 0地址處是符號_start.

這個_start定義可以由uboot/Makefile中的$(OBJS)來確定。

uboot/Makefile

其中$(CPUDIR)定義在uboot/config.mk中。

uboot/config.mk

$(ARCH), $(CPU)定義在由make xxx_config產生的uboot/include/config.mk中。

所以,對於ast2500evb板子來說,u-boot.bin的入口_start定義在uboot/arch/arm/cpu/arm1176/start.S

Start.S的入口符號_start,直接來個跳轉指令.

程式直接跳轉到reset處執行。(跳轉指令後面的ldr指令是cpu要求的,載入例外處理程式的指令,如軟中斷,快速中斷,中斷等)

我們來看一下reset做什麼了。

‘reset’ located the same file with ‘_start’(uboot/arch/arm/cpu/arm1176/start.S)

幾個指令介紹下:

mrs, msr:

bic, orr:

‘reset’ 操作主要是修改cpsr的值。

150行載入cpsr暫存器的值到暫存器r0;

151行清除bit0-bit5;

152行設定M域爲0b10011,即supervisor模式,設定I,F,T位,即disable中斷,disable快速中斷,設定ARM指令模式;

cpsr暫存器的結構如下:

設定完cpsr暫存器後,到cpu_init_crit:

從上述程式碼看,主要是設定協處理器,arm1176協處理器的作用如下:

178行,設定r0暫存器爲0;

179行,使得I-cache和D-cache失效(也叫重新整理I-cache和D-cache);

180行,使得I-TLB和D-TLB失效(也叫重新整理指令TLB和數據TLB),TLB全稱translation lookaside buffer.

處理完cache和TLB後,處理MMU:

185行,讀取協處理器p15的控制暫存器c1。

p15協處理器c1暫存器結構:

186行,清除V,R,S位。

清除V位,表明異常向量基地址使用c12 secure or non-sercure vector base address register設定的基地址。

187行,清除C,A,M位,即,disable L1-D-cache, disable MMU。

188行,設定A位,即,使能alignment fault checking.

189行,設定I位,即使能L1 I-cache.

設定好協處理器p15的c1暫存器(控制暫存器域)數據後,跳轉到mmu_disable。

201行,將前面的協處理器p15的c1暫存器(控制暫存器域)設定過的數據重新匯入到協處理器p15的c1暫存器(控制暫存器域),此時上面的對協處理器p15的c1暫存器(控制暫存器域)的設定就生效了,即disable了MMU, enable了L1 I-cache。

接下去,就到了237行了。

這裏使用了bl指令來跳轉到lowlevel_init處執行,bl和b指令的區別是,b指令是無條件跳轉,即跳轉後,不會回到b指令的下一條指令處;而bl指令使用了link register(r14)儲存了bl指令下一條指令的地址,即當bl指令跳轉後返回時,會執行bl指令的下一條指令,即 ‘bl     _main’。

我們接着看lowlevel_init處程式碼:

結合uboot/Makefile我們知道lowlevel_init所在的檔案:

uboot/Makefile:

uboot/arch/arm/cpu/arm1176/ast2500/Makefile

lowlevel_init在uboot/arch/arm/cpu/arm1176/ast2500/platform.S

294行,儲存lr,即儲存’b _main’指令地址到r4暫存器。

300-301行,僞指令,載入0x1e600000, 0xaeed1a03到暫存器r0和r1。

302行,將0x aeed1a03寫到地址0x1e600000處。

這個0x1e600000地址是個什麼東西?它是AHBC protection key register:

AHB(high performance bus)爲系統匯流排,即ARM 連線使用的匯流排。

如上圖AHBC00的描述,向AHBC00(0x1e600000, AHBC protection key register)寫入0x aeed1a03就是設定unlocked.

305行,是向0x1e600084地址處(AHBC84: Interrupt Control/Status Register)寫入0x00010000。

向AHBC84寫入0x00010000,即向bit16 寫入1,相當於清除該位,即清除匯流排鎖中斷狀態。

308行,向0x1e600088地址處(AHBC88: AHB Bus Target Disable Control Register)寫入0.

向AHBC88寫入0,相當於disable上圖顯示的所有功能,比如,target SPI2/SPI3, VIC。即,此時,cpu不可通過匯流排存取這些。

類似地:

312行通過寫0x1688a8a8到SCU(system control unit)暫存器SCU00(protection key register)

這裏,就相當於設定unlock SCU registers.

318行,通過SCU70暫存器來檢查eSPI(enhanced SPI)是否由flash attach.

319行,如果有flash attach, 即SCU70 bit26爲1,則跳轉到bypass_first_reset.

我們假定沒有flash attach,接下去看程式碼:

0x1e785xxx爲看門狗定時器暫存器基址。

0x1e785010爲WDT10: WDT1 Timeout Status Register。

324行,判斷到當前爲止,是否發生過看門狗定時器發生過超時事件。沒有,則跳轉到start_first_reset.

 

start_first_reset第一個宏ASTMMC_INIT_RESET_MODE_FULL是沒有定義的,我們直接看358開始的程式碼:

從357行開始到364行,都是在設定下面 下麪相關的暫存器:

基址0x1e78:7xxx和0x1e78:8xxx:

程式先設定virtual UART, virtual UART的功能描述如下:

我們來看一下程式碼怎麼設定的:

361行設定FIFO Control Register爲0b111, 即,reset VUART的FIFO.

接下去,設定VUART其他暫存器,直到設定PUART0x1e788000.

R2爲0,這裏就是disable UART的pass through.

接下去,設定PUART其他暫存器,直到LPC controller: 0x1e789000.

基址0x1e78:9xxx:

388行,對暫存器HICR2清零。

基址0x1e62:0xxx:

這個是設定firmwars SPI flash的,即ast2500evb存放uboot的flash。

基址0x1e78:5xxx:

517行,reset各種controller.

520行復位整個系統。

523行就是一直等待看門狗定時器超時後系統復位reset。復位之後,程式碼又從_start開始執行。

系統復位後,再次執行到lowlevel_init時:

這時,324行判斷時,發現系統看門狗定時器發生過超時事件,故不跳轉。程式就執行325行了。

325行之後,接着做一些設定,最終跳轉到bypass_first_reset處執行。

我們接着看bypass_first_reset.

首先設定定時器。

這裏寫0XAE,即設定暫存器TMC38的bit0爲1,即separate mode.

538行這裏寫TMC3C暫存器’1’。

結合533和538行可知,這裏是將定時器的計數清零,並且disable掉。

547行,將定時器enable起來。

558行,設定dram初始化爲SOC Firmware.

接下去,設定USB等,以及設定DDR記憶體。

Bypass_USB_init會向debug的UART口輸出列印:DRAM Init-V

對於ddr4,再輸出字元」4」

我們接着看DDR4。

0x1e6e0004爲sdram的設定暫存器MCR04.

1168行設定MCR04爲0x313, 即設定一次最大可寫入的memory size爲1G.

接下去設定記憶體的時序等資訊。

設定完後,輸出一個字元」0」

ASTMMC_DDR4_MANUAL_RPU沒有使能,跳過這段。

如1315行的註釋描述的程式後面最的事情,我們不詳細看了,跳過。

程式執行軌跡: ddr_phy_init_process  ->  ddr_phy_init_success  ->  ddr4_phyinit_done  ->  Calibration_End

Calibration_End就是check一下DRAM size:

之後init DRAM cache.

Init DRAM cache後做一堆的check,然後跳轉到set_scratch.

Set_scratch做一堆設定,比如MAC設定等,最後將之前儲存的lr傳給pc,這樣pc就跑到了_main處了。

這個_main定義在哪裏?我們來看uboot/Makefile

uboot/Makefile

uboot/arch/arm/lib/Makefile

故,_main爲定義在uboot/arch/arm/lib/crt0.S中。

從註釋來看,_main是要準備C語言執行環境的,即要設定好棧指針(SP).

83行設定了sp地址爲CONFIG_SYS_INIT_SP_ADDR。

這個CONFIG_SYS_INIT_SP_ADDR在哪定義?

在crt0S的25行include了config.h, 這個config.h爲uboot/include/config.h.

我們從Makefile中可知,標頭檔案的搜尋路徑:

uboot/config.mk(被uboot/arch/arm/lib/Makefile包含)

uboot/include/config.h

在uboot/include/config.h的9行include了uboot/include/configs/ast2500evb.h.

uboot/include/configs/ast2500evb.h

在uboot/include/configs/ast2500evb的行9 include了configs/common.cfg.

在這個uboot/include/configs/common.cfg中定義了CONFIG_SYS_INIT_SP_ADDR。

上述定義中的CONFIG_SYS_SDRAM_BASE定義在uboot/include/configs/ast2500evb.h中,如下:

由上面的定義可知,這個板子的SDRAM在CPU的地址空間爲基址0x80000000(2G), 當前階段的棧指針爲0x80000000 + 16K, 即此時棧的大小爲16K. 2G之前是排給flash的(注意,這個CONFIG_SYS_SDRAM_BASE在uboot/include/configs/ast.cfg中也定義多了,但是被uboot/include/configs/ast2500evb.h覆蓋)。

我們回到uboot/arch/arm/lib/crt0.S繼續分析程式碼:

設定好棧指針後,我們在棧頂預留GD_SIZE的記憶體給struct global_data.

GD_SIZE定義在uboot/include/generated/generic-asm-offsets.h

Crt0.S在26行包含了標頭檔案uboot/include/asm-offsets.h, 而asm-offsets.h在3行包含了標頭檔案generated/generic-asm-offsets.h

uboot/arch/arm/lib/crt0.S

uboot/include/asm-offsets.h

uboot/include/generated/generic-asm-offsets.h

crt0.S在預留了struct global_data結構後,將這個結構的地址給r8儲存,並設定r0爲0(board_init_f的參數),之後跳轉到board_init_f這個C函數處理(C語言使用的堆疊環境構建好了,雖然棧只有12K – GD_SIZE這麼大小)。