u-boot 啟動過程 —— 基于S3C2410
雖然u-boot已經(jīng)廣泛應(yīng)用,由于其相對的復(fù)雜性使用戶在了解其內(nèi)部機(jī)理和進(jìn)行u-boot的移植工作時還是會碰到困難。u-boot已有一些分析文檔,但多數(shù)和真正的代碼不能同步或者版本老舊,難以將概念和現(xiàn)實(shí)的代碼匹配——即硬件板上跑的代碼在文檔資料中卻看不到,更無法緊密的跟蹤。本文涉及的代碼基于在s3c2410硬件運(yùn)行的成熟u-boot-1.3.2代碼,版本較新,提供的特性非常豐富,而且在forum.linuxbj.com可以自由瀏覽和下載。此u-boot代表了業(yè)界的較高水平,可以直接構(gòu)建新版的嵌入式產(chǎn)品設(shè)計,有較高的應(yīng)用價值。
本文引用地址:http://butianyuan.cn/article/201611/317161.htmu-boot總的啟動流程如下
->reset
-> 設(shè)置CPU模式
-> 關(guān)閉看門狗/中斷
-> 設(shè)置處理器時鐘/片上總線
-> 初始化調(diào)試串口
-> MMU/外部總線/SDRAM等初始化
-> rom代碼/數(shù)據(jù)搬移到ram
-> 初始化函數(shù)調(diào)用棧
-> 初始化外圍設(shè)備/參數(shù)
->啟動完畢,進(jìn)入main_loop循環(huán)
嵌入式系統(tǒng)離不開bootloader初始化硬件以及引導(dǎo)操作系統(tǒng)。
現(xiàn)在,專用的嵌入式板子運(yùn)行嵌入式Linux系統(tǒng)已經(jīng)變得非常流行,u-boot是一種非常適合此類系統(tǒng)的bootloader。
u-boot主要提供以下功能:
- 設(shè)置目標(biāo)板硬件參數(shù)并初始化;
- 為操作系統(tǒng)傳遞必要信息;
- 執(zhí)行交互式的底層操作;
- 智能化裝載操作系統(tǒng);
- 引導(dǎo)和運(yùn)行的固件程序;
- 支持大容量存儲和USB接口
利用ZIX開發(fā)環(huán)境,能夠通過比較直觀的方式觀察u-boot內(nèi)部,而且可以將代碼調(diào)試和分析同時進(jìn)行,是一種了解、移植u-boot的強(qiáng)大工具。
使用arm工具鏈編譯u-boot源代碼,得到可以燒錄的u-boot.bin文件。
在ZIX開發(fā)環(huán)境里,可以將u-boot.bin載入s3c2410板運(yùn)行,并利用gdb調(diào)試。
gdb能通過JTAG接口訪問硬件,也可以通過TCP/IP訪問虛擬硬件。 建立好調(diào)試連接,即可通過gdb操縱u-boot啟動過程,下面可以跟隨代碼的執(zhí)行順序,了解從上點(diǎn)開始,究竟哪些操作被執(zhí)行。
s3c2410復(fù)位之后,pc指針會指向0x0地址。在u-boot代碼中,該0x0地址是一個向量表,
第一條指令跳轉(zhuǎn)branch到復(fù)位代碼start_co
DE>.globl _start _start: DE> DE>b start_co de DE> DE> DE>ldr pc, _undefined_instruction DE> DE>ldr pc, _software_interrupt DE> DE>ldr pc, _prefetch_abort DE> DE>ldr pc, _da ta_abort DE> DE>ldr pc, _not_used DE> DE>ldr pc, _irq DE> DE>ldr pc, _fiq
復(fù)位指令跳轉(zhuǎn)之后來到第154行,開始執(zhí)行arm920t處理器的基本初始化。
首先修改當(dāng)前程序狀態(tài)寄存器CPSR,使處理器進(jìn)入Supervisor|32 bit ARM模式,
并關(guān)閉ARM9TDMI中斷和快速中斷,這是通過設(shè)置CPSR相應(yīng)掩碼實(shí)現(xiàn)的:
DE>start_co de: DE> DE> DE> DE>/* DE> DE> DE> DE> * set the cpu to SVC32 mode DE> DE> DE> DE> */ DE> DE> DE> DE>mrs r0,cpsr DE> DE> DE> DE>bic r0,r0,#0x1f DE> DE> DE> DE>orr r0,r0,#0xd3 DE> DE> DE> DE>msr cpsr,r0 DE>
緊接著,將S3C2410特有的WTCON寄存器清零,此舉僅為關(guān)閉看門狗,代碼位置是234行:
DE> DE> DE>ldr r0, =pWTCON DE> DE> DE> DE>mov r1, #0x0 DE> DE> DE> DE>str r1, [r0] DE>
然后在241行,將S3C2410中斷控制器INTMSK寄存器置為全1,
INTSUBMSK置為0x7ff,禁止全部中斷源。S3C2410手冊358頁起對此有詳細(xì)描述:
DE> DE> DE>mov r1, #0xffffffff DE> DE> DE> DE>ldr r0, =INTMSK DE> DE> DE> DE>str r1, [r0] # if defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442) || DE> DE> DE> DE>defined(CONFIG_S3C2443) DE> DE> DE> DE>ldr r1, =INTSUBMSK_val DE> DE> DE> DE>ldr r0, =INTSUBMSK DE> DE> DE> DE>str r1, [r0] # endif DE>
接下來在259行,訪問arm920t控制寄存器CP15,并置位最高兩位〔31,30〕。
此兩位置為0b11后,處理器時鐘被設(shè)置為異步模式,允許處理器異步訪問總線:
DE> DE> DE>mrc p15, 0, r1, c1, c0, 0 DE> DE> DE> DE>orr r1, r1, #0xc0000000 DE> DE> DE> DE>mcr p15, 0, r1, c1, c0, 0 DE>
至此arm920t相關(guān)的配置完成,后面開始設(shè)定S3C2410時鐘合成參數(shù)。
通過設(shè)置UPLL,MPLL和CLKDIVN三個寄存器(在S3C2410手冊237頁起講述),
得到需要的處理器工作頻率,分別在308行:
DE> DE> DE>ldr r0, =UPLLCON DE> DE> DE> DE>ldr r1, =UPLLCON_val DE> DE> DE> DE>str r1, [r0] DE>
321行:
DE> DE> DE>ldr r1, =MPLLCON_val DE> DE> DE> DE>str r1, [r0, #-4] DE> DE> DE> DE>/* MPLLCON */ DE> DE> DE> DE> DE> DE>/* FCLK:HCLK:PCLK = 1:2:4 */ DE> DE> DE> DE>ldr r0, =CLKDIVN DE> DE> DE> DE>mov r1, #CLKDIVN_val DE> DE> DE> DE>str r1, [r0] DE>
S3C2410的UART0得到初始化,以便于盡早通過UART0打印信息。
此段代碼從332行開始,其中涉及到的寄存器讀者可參考S3C2410手冊293頁起:
DE> DE> DE>/* enable uart */ DE> DE> DE> DE>ldr r0, =0x4c00000c /* clkcon */ DE> DE> DE> DE>ldr r1, =0x7fff0 /* all clocks on */ DE> DE> DE> DE>str r1, [r0] DE> DE> DE> DE> DE> DE>/* gpio UART0 init */ DE> DE> DE> DE>ldr r0, =0x56000070 DE> DE> DE> DE>mov r1, #0xaa DE> DE> DE> DE>str r1, [r0] DE> DE> DE> DE> DE> DE>/* init uart */ DE> DE> DE> DE>ldr r0, =0x50000000 DE> DE> DE> DE>mov r1, #0x03 DE> DE> DE> DE>str r1, [r0] DE> DE> DE> DE>ldr r1, =0x245 DE> DE> DE> DE>str r1, [r0, #0x04] DE> DE> DE> DE>mov r1, #0x01 DE> DE> DE> DE>str r1, [r0, #0x08] DE> DE> DE> DE>mov r1, #0x00 DE> DE> DE> DE>str r1, [r0, #0x0c] DE> DE> DE> DE>mov r1, #0x1a DE> DE> DE> DE>str r1, [r0, #0x28] DE>
完成UART0設(shè)置之后,根據(jù)不同的編譯時選項和運(yùn)行時參數(shù),代碼會在360行進(jìn)入相應(yīng)的分支,分別是
- 從nand啟動,代碼執(zhí)行l(wèi)owlevel_init,主要是清除cpu cache,以及關(guān)閉mmu和i-cache,
并且根據(jù)板極硬件配置初始化外部存儲器總線和GPIO,最后把代碼從nandflash中拷貝到ram中并繼續(xù)執(zhí)行。 - 從nor啟動,與第1種情況相比,僅僅把代碼拷貝部分簡化,將DA
TA段從flash中拷貝到ram中,其余相同 - 從ram啟動,因為u-boot已經(jīng)處于配置好的ram中,
所以會跳過所有cache,mmu,sdram,nand和nor相關(guān)代碼,跳轉(zhuǎn)到done_relocate執(zhí)行
下面以最復(fù)雜的nand啟動情況為例分析。首先會跳轉(zhuǎn)到572行執(zhí)行cpu_init_crit,
通過操作CP15完成flush處理器arm920t的cache和tlb,并關(guān)閉mmu和i-cache:
DE>cpu_init_crit: DE> DE> DE> DE>/* DE> DE> DE> DE> * flush v4 I/D caches DE> DE> DE> DE>*/ DE> DE> DE> DE>mov r0, #0 DE> DE> DE> DE>mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ DE> DE> DE> DE>mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ DE> DE> DE> DE> DE> DE>/* DE> DE> DE> DE> * disable MMU stuff and caches DE> DE> DE> DE>*/ DE> DE> DE> DE>mrc p15, 0, r0, c1, c0, 0 DE> DE> DE> DE>bic r0, r0, #0x00002300 DE> DE> DE> DE>@ clear bits 13, 9:8 (--V- --RS) DE> DE> DE> DE>bic r0, r0, #0x00000087 DE> DE> DE> DE>@ clear bits 7, 2:0 (B--- -CAM) DE> DE> DE> DE>orr r0, r0, #0x00000002 DE> DE> DE> DE>@ set bit 2 (A) Align DE> DE> DE> DE>orr r0, r0, #0x00001000 DE> DE> DE> DE>@ set bit 12 (I) I-Cache DE> DE> DE> DE>mcr p15, 0, r0, c1, c0, 0 DE>
然后跳轉(zhuǎn)到board/neo1973/common/lowlevel_init.S文件的139行執(zhí)行,
進(jìn)行總線數(shù)據(jù)寬度、時序、SDRAM控制、GPIO等配置,配置完畢后會返回start.S繼續(xù)執(zhí)行。
因為該代碼是與板相關(guān),故放在board目錄里面。由于代碼較多,只粘貼開始部分:
DE> DE> DE>/* memory control configuration */ DE> DE> DE> DE>/* make r0 relative the current location so that it */ DE> DE> DE> DE>/* reads SMRDATA out of FLASH rather than memory ! */ DE> DE> DE> DE>adr r0, SMRDATA DE> DE> DE> DE>ldr r1, =BWSCON /* Bus Width Status Controller */ DE> DE> DE> DE>add r2, r0, #13*4 DE>
完成板級設(shè)置后,在cpu/arm920t/start.S的373行判斷代碼自身的執(zhí)行位置。如果從stepping stone內(nèi)執(zhí)行,
并且u-boot配置為nandboot模式,則跳轉(zhuǎn)到nand_load拷貝代碼:
DE> DE> DE>ldr r1, =BWSCON /* Z = CPU booted from NAND */ DE> DE> DE> DE>ldr r1, [r1] DE> DE> DE> DE>tst r1, #6 /* BWSCON[2:1] = OM[1:0] */ DE> DE> DE> DE>teqeq r0, #0 /* Z &= running at address 0 */ DE> DE> DE> DE>beq nand_load DE>
在417行是nand_load代碼,首先會跳轉(zhuǎn)到614行執(zhí)行may_resume
以檢測系統(tǒng)是從待機(jī)模式喚醒還是上電啟動。如果喚醒,則會根據(jù)之前保存的現(xiàn)場進(jìn)行相應(yīng)處理,
本文不做更多敘述;如果是啟動,則會返回nand_load繼續(xù)執(zhí)行。在nand_load里初始化s3c2410的nandcontroller,
涉及存儲器映射和寄存器NFCONF等,參見S3C2410手冊215頁起。同樣,僅粘貼開始部分的代碼:
DE> DE> DE>mov r1, #S3C2410_NAND_BASE DE> DE> DE> DE>ldr r2, =0xf842 @ initial value enable tacls=3,rph0=6,rph1=0 DE> DE> DE> DE>str r2, [r1, #oN FCONF] DE> DE> DE> DE>ldr r2, [r1, #oN FCONF] DE> DE> DE> DE>bic r2, r2, #0x800 @ enable chip DE>
在451行繼續(xù)根據(jù)配置設(shè)定棧指針,為后面調(diào)用C函數(shù)執(zhí)行拷貝作準(zhǔn)備:
DE> DE> DE>ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ DE> DE> DE> DE>sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ DE> DE> DE> DE>sub r0, r0, #CFG_GBL_DA TA_SIZE /* bdinfo */ #ifdef CONFIG_USE_IRQ DE> DE> DE> DE>sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif DE> DE> DE> DE>sub sp, r0, #12 /* leave 3 words for abort-stack */ DE>
然后在460行,將SDRAM中的目標(biāo)地址存入r0,0x0地址存入r1,u-boot長度存入r2,
跳入cpu/arm920t/s3c24x0/nand_read.c文件第154行執(zhí)行nand_read_ll函數(shù),該函數(shù)接受前面3個寄存器中的值作為參數(shù),并將返回值放回r0:
DE> DE> DE>ldr r0, _TEXT_BASE DE> DE> DE> DE>mov r1, #0x0 DE> DE> DE> DE>mov r2, #CFG_UBOOT_SIZE DE> DE> DE> DE>bl nand_read_ll DE>
在nand_read_ll函數(shù)中實(shí)現(xiàn)了nandflash訪問代碼,并且支持自動跳過壞塊的特性,函數(shù)循環(huán)執(zhí)行nand頁面讀取并存入SDRAM,直到u-boot全部拷貝完,并返回0,該C代碼留給讀者自己閱讀。nand_read_ll返回0后,會跳轉(zhuǎn)到ok_nand_read,并482行對拷貝的頭4K字節(jié)進(jìn)行校驗:
DE> @ verify mov r0, #0 @ldr r1, =0x33f00000 ldr r1, _TEXT_BASE mov r2, #0x400 DE> DE> DE> DE>@ 4 bytes * 1024 = 4K-bytes go_next: DE> DE> DE> DE>ldr r3, [r0], #4 DE> DE> DE> DE>ldr r4, [r1], #4 DE> DE> DE> DE>teq r3, r4 DE> DE> DE> DE>bne notmatch DE> DE> DE> DE>subs r2, r2, #4 DE> DE> DE> DE>beq done_nand_read DE> DE> DE> DE>bne go_next DE>
校驗通過后代碼506行,在地址為_booted_from_nand的SDRAM位置保存1,以便告訴上層軟件本次啟動是從nand引導(dǎo):
DE>done_nand_read: DE> DE> DE> DE>ldr r0, _booted_from_nand DE> DE> DE> DE>mov r1, #1 DE> DE> DE> DE>strb r1, [r0] DE>
然后在518行,將中斷向量表拷貝到0x0:
DE> DE> DE>mov r0, #0 DE> DE> DE> DE>ldr r1, _TEXT_BASE DE> DE> DE> DE>mov r2, #0x40 irqvec_cpy_next: DE> DE> DE> DE>ldr r3, [r1], #4 DE> DE> DE> DE>str r3, [r0], #4 DE> DE> DE> DE>subs r2, r2, #4 DE> DE> DE> DE>bne irqvec_cpy_next DE>
在532行,設(shè)置棧指針:
DE> DE> DE>ldr r0, _TEXT_BASE DE> DE> DE> DE>/* upper 128 KiB: relocated uboot */ DE> DE> DE> DE>sub r0, r0, #CFG_MALLOC_LEN DE> DE> DE> DE>/* malloc area */ DE> DE> DE> DE>sub r0, r0, #CFG_GBL_DA TA_SIZE /* bdinfo */ #ifdef CONFIG_USE_IRQ DE> DE> DE> DE>sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif DE> DE> DE> DE>sub sp, r0, #12 DE> DE> DE> DE>/* leave 3 words for abort-stack */ DE>
在541行,清除bss段并跳轉(zhuǎn)到真正的C函數(shù)start_armboot,進(jìn)入更高級的硬件初始化代碼,匯編初始化部分也全部完成使命:
DE> DE> DE>ldr r0, _bss_start /* find start of bss segment */ DE> DE> DE> DE>ldr r1, _bss_end /* stop here */ DE> DE> DE> DE>mov r2, #0x00000000 /* clear */ DE> DE> clbss_l: DE> DE> DE> DE>str r2, [r0] /* clear loop... */ DE> DE> DE> DE>add r0, r0, #4 DE> DE> DE> DE>cmp r0, r1 DE> DE> DE> DE>ble clbss_l DE> DE> DE> DE> DE> DE>ldr pc, _start_armboot DE>
start_armboot函數(shù)位于lib_arm/board.c文件第277行,首先初始化globel_da
DE> DE> DE>gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); DE> DE> DE> DE>/* compiler optimization barrier needed for GCC >= 3.4 */__asm__ __volatile__("": : :"memory"); DE> DE> DE> DE>memset ((void*)gd, 0, sizeof (gd_t));gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); DE> DE> DE> DE>memset (gd->bd, 0, sizeof (bd_t)); DE>
然后在一個for循環(huán)中從init_sequence地址開始,逐個調(diào)用初始化C函數(shù)至NULL為止。這些routine函數(shù)按調(diào)用順序分別是
- cpu_init() ——在common/main.c文件,執(zhí)行初始化中斷棧操作
- board_init() ——在board/neo1973/gta01/gta01.c文件中,執(zhí)行板級初始化,主要是更新GPIO和PLL設(shè)置
- interrupt_init() ——在/cpu/arm920t/s3c24x0/interrupts.c文件中,執(zhí)行時鐘中斷初始化
- env_init() ——在common/env_nand.c文件中,設(shè)置缺省環(huán)境變量
- init_baudrate() ——在lib_arm/board.c文件中,將環(huán)境變量中的baudrate存入bd_info結(jié)構(gòu)bd
- serial_init() ——在common/serial.c文件中,調(diào)用驅(qū)動中真正的init()初始化串口
- console_init_f() ——在common/console.c文件中,更新global_da
ta結(jié)構(gòu)gd的have_console標(biāo)記為1 - display_banner() ——在lib_arm/board.c文件中,打印u-boot banner,輸出版本、運(yùn)行地址等信息。比如在控制臺看到的
- init_func_i2c() ——在lib_arm/board.c文件中,初始化i2c總線
- dram_init() ——在board/neo1973/gta01/gta01.c文件中,填充bd->bi_dram[0]的start和size成員,用來描述u-boot可用的ram
- display_dram_config() ——在board/neo1973/gta01/gta01.c文件中,打印當(dāng)前ram配置。在控制臺能夠看到相應(yīng)的 DRAM: 128 MB
利用gdb可以清晰的看到調(diào)用過程:
DE> DE> DE>for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { DE> DE> DE> DE> DE> DE>if ((*init_fnc_ptr)() != 0) { DE> DE> DE> DE> DE> DE> DE> DE>hang (); DE> DE> DE> DE> DE> DE>} DE> DE> DE> DE>} DE>
接著是一些可選外設(shè)的初始化,如顯示屏、nor、nand、dataflash、網(wǎng)卡等,此過程執(zhí)行后全部初始化工作完成。下面僅粘貼nor代碼:
DE>#ifndef CFG_NO_FLASH DE> DE> DE> DE>/* configure available FLASH banks */ DE> DE> DE> DE>size = flash_init (); DE> DE> DE> DE>display_flash_config (size); #endif /* CFG_NO_FLASH */ DE>
之后在457行進(jìn)入無限循環(huán),調(diào)用common/main.c文件的278行main_loop()函數(shù),u-boot完成啟動過程。main_loop提供一個交互式命令行,可通過串口或usb控制臺操作,也可以進(jìn)一步引導(dǎo)操作系統(tǒng):
DE> DE> DE>for (;;) { DE> DE> DE> DE> DE> DE>main_loop (); DE> DE> DE> DE>} DE>
評論