本文以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這麼大小)。