1. U-Boot啟動(dòng)過(guò)程概述
U-Boot的啟動(dòng)過(guò)程分為兩個(gè)階段。
第一階段:主要是SOC內(nèi)部的初始化,板級(jí)的初始化比較少,所以移植的修改量比較小。此階段由匯編語(yǔ)言編寫(xiě),代碼主體分布在/uboot/cpu/s5pc11x/start.S和/uboot/board/samsung/x210/lowlevel_init.S中。
第二階段:主要是板級(jí)的初始化,SOC內(nèi)部的初始化比較少,移植的修改量主要在此。此階段由c語(yǔ)言編寫(xiě),代碼主體分布在/uboot/lib_arm/board.c中。
紅色字體部分和板級(jí)關(guān)系較大,是移植的重點(diǎn)修改部分。
2. U-Boot啟動(dòng)代碼具體分析
2.1 第一階段(/ubootcpu/s5pc11x/start.S)
(0)頭文件包含
/uboot/include/config.h文件是在配置過(guò)程中中生成的(具體/uboot/mkconfig文件中實(shí)現(xiàn)),其中的內(nèi)容就是“#include
version.h文件是uboot的版本信息文件,是在編譯過(guò)程中生成的。具體的U-Boot的version信息可以在/uboot/Makefile中更改
U-Boot中的頭文件包含其實(shí)都不是真正被包含的文件,它們大多是在配置編譯階段產(chǎn)生的符號(hào)鏈接或者是具有符號(hào)鏈接功能的頭文件(/uboot/mkconfig中創(chuàng)建符號(hào)鏈接)??傊鼈兊墓δ芏碱?lèi)似于符號(hào)鏈接,目的是讓uboot的源碼更具靈活性和移植性。
#include#include #if defined(CONFIG_ENABLE_MMU) #include #endif #include
(1)填充16字節(jié)的校驗(yàn)位
uboot.bin鏡像在開(kāi)頭需要加16字節(jié)的檢驗(yàn)頭(詳見(jiàn) irom application note文件)。這一部分主要功能是在代碼段最開(kāi)始處放置16字節(jié)的填充位,即通過(guò)偽.word指令定義4個(gè)word(32位)的空間來(lái)在代碼段最開(kāi)始處占16字節(jié)。
在此處只是占據(jù)16字節(jié)的空間,校驗(yàn)頭的正確值在編譯階段通過(guò)mkv210image.c中計(jì)算并填充的。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED) .word 0x2000 .word 0x0 .word 0x0 .word 0x0 #endif
(2)設(shè)置異常向量表
.globl是globl是把_start這個(gè)標(biāo)號(hào)全局化,是編譯器的操作,并不是匯編指令。_start代表程序start.S的入口。
這段代碼的功能是設(shè)置異常向量表。b reset 所處的位置是與異常向量表基地址偏移量為的0的地方,所以當(dāng)復(fù)位異常發(fā)生時(shí)(開(kāi)機(jī)也屬于復(fù)位異常),CPU會(huì)自動(dòng)跳轉(zhuǎn)入異常表基址偏移量為0處執(zhí)行復(fù)位異常程序,即跳轉(zhuǎn)執(zhí)行reset部分的代碼。
這部分代碼只是構(gòu)建了異常向量表,當(dāng)每個(gè)異常向量指向的內(nèi)容為空(除reset異常外)。因?yàn)閁-Boot比較簡(jiǎn)單,只是作為引導(dǎo)程序,所以不需要很細(xì)致的處理各種異常。
.globl _start _start: b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq
(3)禁能IRQ和FIQ,使能SVC模式
開(kāi)機(jī)后程序從此時(shí)正式開(kāi)始運(yùn)行。
程序上電之初,異常向量表未初始化,故先禁能IRQ(普通中斷)和FIQ(快速中斷)。
使能SVC模式,即超級(jí)用戶(hù)模式。SVC 模式,主要用于 SWI(軟件中斷)和 OS(操作系統(tǒng))。這個(gè)模式有額外的特權(quán),允許你進(jìn)一步控制計(jì)算機(jī)。例如,你必須進(jìn)入超級(jí)用戶(hù)模式來(lái)讀取一個(gè)插件(podule)。這不能在用戶(hù)模式下完成。
/* * the actual reset code */ reset: /* * set the cpu to SVC32 mode and IRQ & FIQ disable */ @;mrs r0,cpsr @;bic r0,r0,#0x1f @;orr r0,r0,#0xd3 @;msr cpsr,r0 msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
(4)初始化相關(guān)全局變量
TEXT_BASE是U-Boot代碼的鏈接地址,在/uboot/board/samsung/config.mk文件中定義,該文件在/uboot/Makefile->x210_sd_config段中生成。TEXT_BASE = 0xc3e00000
_TEXT_BASE: .word TEXT_BASE
設(shè)置U-Boot在DDR中的物理地址,即運(yùn)行地址,U-Boot重定位將整個(gè)U-Boot拷貝至DDR中的_TEXT_PHY_BASE。CFG_PHY_UBOOT_BASE在開(kāi)發(fā)板配置文件(/uboot/include/configs/x210_sd_config)中定義,為0x33e00000
_TEXT_PHY_BASE: .word CFG_PHY_UBOOT_BASE
在開(kāi)啟MMU之后,虛擬地址段0xc0000000-0xd0000000將被映射到物理地址段0x30000000-0x40000000。所以TEXT_BASE = 0xc3e00000對(duì)應(yīng)的物理地址為T(mén)EXT_BASE =0x33e00000,即U-Boot的鏈接地址與運(yùn)行地址一致。
(5)禁用Cache和MMU
caches是CPU的緩沖區(qū),它的作用是存放常用的數(shù)據(jù)和指令,提高cpu與內(nèi)存之間數(shù)據(jù)與指令的傳輸速率。
MMU是CPU的內(nèi)存管理單元,它的作用是轉(zhuǎn)換虛擬地址與物理地址。
關(guān)閉caches的原因:上電初始,DDR未初始化,當(dāng)CPU從cache中取數(shù)據(jù)時(shí),可能導(dǎo)致數(shù)據(jù)預(yù)取異常。另一方面,當(dāng)匯編指令讀取緩存數(shù)據(jù),而實(shí)際物理地址對(duì)應(yīng)的數(shù)據(jù)發(fā)生變化,導(dǎo)致CPU不能獲取最新的數(shù)據(jù)。在C語(yǔ)言中無(wú)需關(guān)閉caches,因?yàn)镃語(yǔ)言中可以使用volatile關(guān)鍵字避免上述情況。
關(guān)閉MMU的原因:U-Boot的作用是硬件初始化和引導(dǎo)操作系統(tǒng),純粹的初始化階段,開(kāi)啟MMU會(huì)導(dǎo)致這個(gè)過(guò)程更復(fù)雜。
cpu_init_crit: ................................................. /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 @ clear bits 13 (--V-) bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB mcr p15, 0, r0, c1, c0, 0
(6)判斷啟動(dòng)介質(zhì)
先從(PRO_ID_BASE+OMR_OFFSET)地址的寄存器讀取啟動(dòng)介質(zhì)信息,經(jīng)過(guò)數(shù)據(jù)處理之后放入r2寄存器中。
然后通過(guò)比較r2的值來(lái)判定啟動(dòng)介質(zhì),經(jīng)過(guò)判斷得到當(dāng)前的啟動(dòng)介質(zhì)為SD/MMC,在把BOOT_MMCSD宏寫(xiě)入r3中。
最后將啟動(dòng)介質(zhì)信息從寄存器r3中寫(xiě)入(INF_REG_BASE+INF_REG3_OFFSET)地址的寄存器中
/* Read booting information */ ldr r0, =PRO_ID_BASE ldr r1, [r0,#OMR_OFFSET] bic r2, r1, #0xffffffc1 ............................................ /* SD/MMC BOOT */ cmp r2, #0xc moveq r3, #BOOT_MMCSD ............................................. ldr r0, =INF_REG_BASE str r3, [r0, #INF_REG3_OFFSET]
(7)第一次初始化棧(SRAM中)
第一次設(shè)置棧,由于不設(shè)置棧的話(huà)無(wú)法使用嵌套bl跳轉(zhuǎn)指令,即雙層函數(shù)調(diào)用,因?yàn)橹挥幸粋€(gè)LR寄存器,而后面的lowlevel_init就有雙層跳轉(zhuǎn),故這里開(kāi)始設(shè)置SRAM中用的棧。
這里棧設(shè)置的地址并沒(méi)有按照s5pv210的推薦地址,將0xd0036000地址下12Bytes內(nèi)存空間設(shè)為棧內(nèi)存,只要保證該段內(nèi)存不會(huì)被其他程序占用即可。
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */ sub sp, sp, #12 /* set stack */ mov fp, #0
(8)跳轉(zhuǎn)執(zhí)行l(wèi)owlevel_init部分
/uboot/board/x210/lowlevel_init
1)判斷復(fù)位的類(lèi)型
復(fù)位分為多種情況,如冷啟動(dòng)、休眠喚醒等。
判斷復(fù)位類(lèi)型的意義在于:冷啟動(dòng)需要重新初始化DDR,而休眠喚醒不需要再次初始化DDR。
/* check reset status */ ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET) ldr r1, [r0] bic r1, r1, #0xfff6ffff cmp r1, #0x10000 beq wakeup_reset_pre cmp r1, #0x80000 beq wakeup_reset_from_didle
2)關(guān)看門(mén)狗
U-Boot主要功能是初始化硬件資源和引導(dǎo)OS,基本不會(huì)出現(xiàn)程序跑飛的情況。因此,為避免喂狗的麻煩,等U-Boot一切就緒正常運(yùn)行后,再打開(kāi)看門(mén)狗。
/* Disable Watchdog */ ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */ mov r1, #0 str r1, [r0]
3)開(kāi)發(fā)板供電鎖存
設(shè)置開(kāi)發(fā)板的供電按鍵鎖存功能。注意是哪個(gè)gpio,移植過(guò)程可能需要修改。
/* PS_HOLD pin(GPH0_0) set to high 開(kāi)發(fā)板供電上鎖*/ ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET) ldr r1, [r0] orr r1, r1, #0x300 orr r1, r1, #0x1 str r1, [r0]
4)檢測(cè)程序當(dāng)前的執(zhí)行位置(SRAM或DDR)
本段的功能是檢測(cè)當(dāng)前代碼的執(zhí)行位置。判斷是在SRAM中還是DDR中,即CPU是冷啟動(dòng)還是休眠喚醒復(fù)位,從而來(lái)決定是否要跳過(guò)后面的時(shí)鐘、DDR的初始化代碼。
bic是位清除指令,其功能是:將pc中的某些位清零(r0中為1的位清零),剩下一些特殊的bit位賦值給r1。
比較鏈接地址和當(dāng)前地址的特定位,即比較r1和r2。若比較鏈接地址和當(dāng)前地址的特定位相等,說(shuō)明當(dāng)前代碼處于DDR中(休眠喚醒),則跳轉(zhuǎn)到標(biāo)號(hào)1處執(zhí)行后面的代碼(即跳過(guò)時(shí)鐘、DDR的初始化代碼);否則,繼續(xù)執(zhí)行后續(xù)的時(shí)鐘、DDR的初始化代碼。
ldr r0, =0xff000fff bic r1, pc, r0 /* r0 <- current base addr of code */ ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */ bic r2, r2, r0 /* r0 <- current base addr of code */ cmp r1, r2 /* compare r0, r1 */ beq 1f /* r0 == r1 then skip sdram init */
5)初始化時(shí)鐘、DDR、串口
跳轉(zhuǎn)執(zhí)行時(shí)鐘初始化函數(shù)system_clock_init,然后返回。時(shí)鐘的相關(guān)參數(shù)可以在開(kāi)發(fā)板配置文件(/uboot/include/configs/x210_sd.h)中修改,移植過(guò)程基本不需要需改該部分代碼。
跳轉(zhuǎn)執(zhí)行DDR初始化函數(shù)mem_ctrl_asm_init,然后返回。其中,DMC0,DMC1等設(shè)置很重要,直接和板子上內(nèi)存的大小和分布有關(guān),需要注意。DDR的相關(guān)參數(shù)可以在開(kāi)發(fā)板配置文件(/uboot/include/configs/x210_sd.h)中修改。
跳轉(zhuǎn)執(zhí)行串口初始化函數(shù)uart_asm_init,成功執(zhí)行后打印‘O’,然后返回?!甇’可作為調(diào)試的幫助。
若定義了ONFIG_ONENAND,則初始化ONENAND;若定義了CONFIG_NAND,則初始化NAND。
在返回start.S前打印了‘K’,與之前的‘O’組成“OK”,這是uboot的第一條打印信息,可以用來(lái)判斷l(xiāng)owlevel_init是否正常運(yùn)行。
把之前保存在棧中的lr值彈出到pc中,來(lái)返回到start.S。
/* init system clock */ bl system_clock_init /* Memory initialize */ bl mem_ctrl_asm_init 1: /* for UART */ bl uart_asm_init //初始化完成打印'O' bl tzpc_init //基本沒(méi)用 #if defined(CONFIG_ONENAND) bl onenandcon_init #endif #if defined(CONFIG_NAND) /* simple init for NAND */ bl nand_asm_init #endif .............................................. /* Print 'K' */ ldr r0, =ELFIN_UART_CONSOLE_BASE ldr r1, =0x4b4b4b4b str r1, [r0, #UTXH_OFFSET] pop {pc}
(9)再次供電鎖存
從lowlevel_init中跳轉(zhuǎn)回start.S中,再次進(jìn)行開(kāi)發(fā)板供電鎖存設(shè)置(在lowlevel_init中已經(jīng)設(shè)置過(guò)一次),可能為代碼的冗余。注意引腳號(hào)。
ldr r0, =0xE010E81C /* PS_HOLD_CONTROL register */ ldr r1, =0x00005301 /* PS_HOLD output high */ str r1, [r0]
(10)第二次初始化棧(DDR中)
為了即將執(zhí)行的c程序做準(zhǔn)備,這里開(kāi)始第二次設(shè)置棧,設(shè)置于DDR中。這里將棧設(shè)置在_TEXT_PHY_BASE,即U-Boot在DDR中的真正物理地址(U-Boot運(yùn)行地址)。由于棧是滿(mǎn)減棧,所以緊挨著uboot放置也不會(huì)沖突。
/* get ready to call C functions */ ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */ sub sp, sp, #12 mov fp, #0 /* no previous frame, so fp=0 */
(11)再次檢測(cè)當(dāng)前程序執(zhí)行地址(SRAM或DDR)
在lowlevel_init中,檢測(cè)當(dāng)前代碼的執(zhí)行位置。判斷是在SRAM中還是DDR中,即cpu是冷啟動(dòng)還是休眠喚醒復(fù)位,從而來(lái)決定是否要跳過(guò)后面的時(shí)鐘、DDR的初始化代碼。
在此處再次檢測(cè)當(dāng)前代碼的執(zhí)行位置,從而來(lái)決定是否要跳過(guò)重定位代碼。
ldr r0, =0xff000fff bic r1, pc, r0 /* r0 <- current base addr of code */ ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */ bic r2, r2, r0 /* r0 <- current base addr of code */ cmp r1, r2 /* compare r0, r1 */ beq after_copy /* r0 == r1 then skip flash copy */
(12)通過(guò)引腳來(lái)判斷啟動(dòng)介質(zhì)
這一段區(qū)別于之前的啟動(dòng)介質(zhì)判斷,本段是通過(guò)引腳來(lái)判斷啟動(dòng)介質(zhì)的
將0xD0037488地址的寄存器的值放入r0,該寄存器里的值可以判斷BL1是從SD/MMC哪個(gè)通道啟動(dòng)的,將寄存器的值放入r1。這個(gè)寄存器的信息在irom applicationnote里有記載。
將0xEB200000這個(gè)值放入r2,該值的表示是2號(hào)方式啟動(dòng),并將寄存器的值和r2(0xEB200000)進(jìn)行對(duì)比。如果相同則說(shuō)明BL1是從SD卡通道2啟動(dòng),跳轉(zhuǎn)入標(biāo)號(hào)mmcsd_boot處執(zhí)行SD/MMC重定位相關(guān)代碼(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy)。
若不是從SD卡通道2啟動(dòng),則讀取之前存儲(chǔ)的啟動(dòng)介質(zhì)信息(INF_REG_BASE+INF_REG3_OFFSET地址的寄存器),該信息有上一次啟動(dòng)介質(zhì)檢測(cè)所得(見(jiàn)(6))。隨后,跳轉(zhuǎn)至啟動(dòng)介質(zhì)相應(yīng)的xxx_boot代碼中執(zhí)行重定位相關(guān)的代碼(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy)。
#if defined(CONFIG_EVT1) /* If BL1 was copied from SD/MMC CH2 */ ldr r0, =0xD0037488 ldr r1, [r0] ldr r2, =0xEB200000 cmp r1, r2 beq mmcsd_boot #endif ldr r0, =INF_REG_BASE ldr r1, [r0, #INF_REG3_OFFSET] cmp r1, #BOOT_NAND /* 0x0 => boot device is nand */ beq nand_boot cmp r1, #BOOT_ONENAND /* 0x1 => boot device is onenand */ beq onenand_boot cmp r1, #BOOT_MMCSD beq mmcsd_boot cmp r1, #BOOT_NOR beq nor_boot cmp r1, #BOOT_SEC_DEV beq mmcsd_boot nand_boot: mov r0, #0x1000 bl copy_from_nand b after_copy onenand_boot: bl onenand_bl2_copy b after_copy mmcsd_boot: #if DELETE ldr sp, _TEXT_PHY_BASE sub sp, sp, #12 mov fp, #0 #endif bl movi_bl2_copy b after_copy nor_boot: bl read_hword b after_copy
(13)進(jìn)行U-Boot重定位
具體代碼在/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy中。
定義一個(gè)函數(shù)指針類(lèi)型copy_sd_mmc_to_mem。
typedef u32(*copy_sd_mmc_to_mem) (u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
函數(shù)返回值 | u32代表函數(shù)執(zhí)行成功與否 |
u32 channel | 代表SD/MMC啟動(dòng)通道號(hào)(0-4) |
u32 start_block | 起始?jí)K地址 |
u16 block_size | 復(fù)制的塊的個(gè)數(shù) |
u32 *trg | 復(fù)制操作的目的地址,一般是DDR內(nèi)的地址 |
u32 init | init一般給0,不用多管 |
首先讀取位于0xD0037488的寄存器值至變量ch中,再一次檢查啟動(dòng)介質(zhì)(SD卡的通道號(hào))。
然后0xD0037F98地址中的指針變量強(qiáng)制轉(zhuǎn)換為copy_sd_mmc_to_mem類(lèi)型,并賦值給copy_bl2。該指針變量指向一個(gè)函數(shù),這個(gè)函數(shù)就是CPU的IROM中固化用來(lái)復(fù)制SD/mmc中的內(nèi)容至任意地址的函數(shù)。
最后,根據(jù)ch變量的值判斷SD的通道號(hào),并將整個(gè)U-Boot從相應(yīng)的SD通道號(hào)中拷貝至DDR的指定地址(CFG_PHY_UBOOT_BASE =_TEXT_PHY_BASE =0x33e00000)處。
MOVI_BL2_POS是燒錄uboot時(shí)的扇區(qū),MOVI_BL2_BLKCNT是uboot占的扇區(qū)數(shù),具體的定義和計(jì)算都在/uboot/include/movi.h中。CFG_PHY_UBOOT_BASE為U-Boot的在DDR中的物理地址(運(yùn)行地址),具體的定義和計(jì)算都在/uboot/include/configs/x210_sd.h中。
void movi_bl2_copy(void){ ulong ch; ch = *(volatile u32 *)(0xD003A508); copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008)); u32 ret; if (ch == 0xEB000000) //SD卡通道0 { ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0); } else if (ch == 0xEB200000) //SD卡通道2 { ................. } }
(14)建立虛擬地址映射表并開(kāi)啟MMU
從movi_bl2_copy返回之后,將跳轉(zhuǎn)至start.S->after_copy,建立虛擬地址映射表并開(kāi)啟MMU。
bl movi_bl2_copy b after_copy
TTB(TranslationTableBase),即轉(zhuǎn)換表的基地址。轉(zhuǎn)換表是MMU將虛擬地址映射為物理地址的憑據(jù),建立整個(gè)虛擬地址映射的關(guān)鍵就是建立轉(zhuǎn)換表,此表存放在內(nèi)存中,工作時(shí)不需要軟件干涉。
只要將轉(zhuǎn)換表TTB的基地址(_mmu_table_base)放入cp15的c2寄存器,MMU就能自動(dòng)使用虛擬地址映射。_mmu_table_base定義在start.S其值為標(biāo)號(hào)mmu_table,mmu_table在lowlevel_init中定義。
最后通過(guò)設(shè)置cp15的c1寄存器來(lái)開(kāi)啟mmu,以實(shí)現(xiàn)虛擬地址映射和內(nèi)存訪(fǎng)問(wèn)權(quán)限管理等功能。
#if defined(CONFIG_ENABLE_MMU) /*使能域訪(fǎng)問(wèn)*/ enable_mmu: ldr r5, =0x0000ffff mcr p15, 0, r5, c3, c0, 0 @load domain access register /*將轉(zhuǎn)換表ttb的基地址放入cp15的c2寄存器,mmu就能自動(dòng)使用虛擬地址映射*/ ldr r0, _mmu_table_base ldr r1, =CFG_PHY_UBOOT_BASE ldr r2, =0xfff00000 bic r0, r0, r2 orr r1, r0, r1 mcr p15, 0, r1, c2, c0, 0 /*通過(guò)設(shè)置cp15的c1寄存器來(lái)開(kāi)啟mmu,以實(shí)現(xiàn)虛擬地址映射和內(nèi)存訪(fǎng)問(wèn)權(quán)限管理等功能*/ mmu_on: mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #1 mcr p15, 0, r0, c1, c0, 0 nop nop nop nop #endif
詳細(xì)的建表步驟在/uboot/board/x210/lowlevel_init。
映射表中規(guī)定了內(nèi)存映射和管理是以塊為單位的,在ARM中支持3種塊大小,細(xì)表1KB、粗表4KB、段1MB。真正的轉(zhuǎn)換表就是由若干個(gè)轉(zhuǎn)換表單元構(gòu)成的,每個(gè)單元負(fù)責(zé)1個(gè)內(nèi)存塊,總體的轉(zhuǎn)換表負(fù)責(zé)整個(gè)內(nèi)存空間(0-4G,32位CPU)的映射。
帶參宏將FL_SECTION_ENTRY base,ap,d,c,b定義成一個(gè)word大小的特定值,這個(gè)特定值就是轉(zhuǎn)換表的填充量。
參數(shù)分析:參數(shù)base是映射出來(lái)的段地址的基地址,從第20位開(kāi)始。20位的大小正好是1MB,故此映射表采用的是段式映射;ap是訪(fǎng)問(wèn)控制位,從第10位開(kāi)始。d、c、b都是一些權(quán)限位。
#ifdef CONFIG_ENABLE_MMU #ifdef CONFIG_MCP_SINGLE .macro FL_SECTION_ENTRY base,ap,d,c,b //macro指令是匯編中宏定義 .word (base << 20) | (ap << 10) | (d << 5) | (1<<4) | (c << 3) | ( << 2) | (1<<1) .endm //即結(jié)束宏定義 .section .mmudata, "a" .align 14 .globl mmu_table mmu_table: .set __base,0 //設(shè)置變量__base值為 /*.rept 0x100相當(dāng)于for循環(huán),一共循環(huán)0x100次,所以這一塊代碼創(chuàng)建了0x100(256)個(gè)轉(zhuǎn)換表單元*/ .rept 0x100 /*利用剛才定義的帶參宏創(chuàng)建轉(zhuǎn)換表的內(nèi)容,變量__base和3,0,0,0作為參數(shù)*/ FL_SECTION_ENTRY __base,3,0,0,0 .set __base,__base+1 //相當(dāng)于base=base+1 .endr //結(jié)束循環(huán) .........................................//后續(xù)繼續(xù)建表
由以上代碼分析,得出整張轉(zhuǎn)換表的設(shè)定如下。
輸入虛擬地址 | 輸出的物理地址 | 長(zhǎng)度 |
0-10000000 | 0-10000000 | 256MB |
10000000-20000000 | 0 | 256MB |
20000000-60000000 | 20000000-60000000 | 1GB |
60000000-80000000 | 0 | 512MB |
80000000-b0000000 | 80000000-b0000000 | 768MB |
b0000000-c0000000 | b0000000-c0000000 | 256MB |
c0000000-d0000000 | 30000000-40000000 | 256MB |
d-完 | d-完 | 768MB |
由此可知,此表僅僅將c0000000開(kāi)頭的256MB映射到了DMC0的30000000開(kāi)頭的256MB物理內(nèi)存,其他的虛擬地址空間是原樣映射的。所以u(píng)boot的鏈接地址(0xc3e00000)對(duì)應(yīng)物理地址(0x33e00000),即U-Boot的鏈接地址和運(yùn)行地址(CFG_PHY_UBOOT_BASE =0x33e00000)一致。
(15)第三次初始化棧(DDR中)
第三次設(shè)置棧,仍然設(shè)在DDR中。雖然上一次已經(jīng)在DDR中設(shè)置過(guò),但是是緊挨著uboot存放的,位置不合理。
所以本次將棧設(shè)置uboot鏈接地址上方2MB處,這個(gè)位置合理、緊湊、安全。
stack_setup: #if defined(CONFIG_MEMORY_UPPER_CODE) ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000
(16)清bss段
_bss_start和_bss_end是鏈接腳本中定義的。
利用循環(huán)清零。
clear_bss: ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x00000000 /* clear */ clbss_l: str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 ble clbss_l
(17)遠(yuǎn)跳轉(zhuǎn)至start_armboot(U-Boot第二階段)
從SRAM中遠(yuǎn)跳轉(zhuǎn)到DDR中函數(shù)start_armboot處,start_armboot定義在根目錄下/uboot/lib_arm/board.c中。
至此,uboot第一階段結(jié)束,進(jìn)入第二階段,至DDR處執(zhí)行。
ldr pc, _start_armboot _start_armboot: .word start_armboot
2.2 第二階段(/uboot/lib_arm/board.c->start_armboot)
(1)初始化全局變量
1)定義一個(gè)函數(shù)指針類(lèi)型。
typedef int (init_fnc_t) (void);
2)定義并初始化init_sequence數(shù)組
定義并初始化一個(gè)全局?jǐn)?shù)組init_sequence,其元素是指向init_fnc_t類(lèi)型函數(shù)的指針,這些函數(shù)都是用來(lái)初始化各個(gè)功能的函數(shù)。這些函數(shù)的具體功能如下:
cpu_init:CPU初始化函數(shù),但cpu初始化工作已在U-Boot的第一階段完成,所以該函數(shù)為空。
board_init:開(kāi)始板級(jí)初始化函數(shù),配置網(wǎng)卡用到的GPIO、機(jī)器碼、內(nèi)存?zhèn)鲄⒌刂?,并填充至gd結(jié)構(gòu)體。
interrupt_init:定時(shí)器的初始化函數(shù),和中斷無(wú)關(guān)(應(yīng)用,如bootdelay)。
env_init:環(huán)境變量的初始化函數(shù),由于環(huán)境變量還沒(méi)從啟動(dòng)介質(zhì)中取到DDR中,故此處的初始化只是對(duì)DDR中的環(huán)境變量(U-Boot自帶的環(huán)境變量)進(jìn)行一個(gè)簡(jiǎn)單的判定,檢測(cè)其是否可用。真正的初始化在start_armboot里面。U-Boot支持多種不同的啟動(dòng)介質(zhì)(如norflash、nandflash、sd/mmc),而各種介質(zhì)存取操作env的方法都是不同的,故U-Boot中包含在多個(gè)文件中包含了env_init函數(shù)。而此U-Boot的啟動(dòng)介質(zhì)為SD/MMC,故當(dāng)前的env_init函數(shù)在/uboot/common/Env_movi.c文件中。
init_baudrate:串口波特率初始化函數(shù),設(shè)置串口波特率,并填充至gd結(jié)構(gòu)體。具體的串口初始化工作已在U-Boot的第一階段完成(lowlevel_init)。
serial_init:串口初始化函數(shù),但具體的串口初始化工作已在U-Boot的第一階段完成(lowlevel_init),所以該函數(shù)為空。
console_init_f:控制臺(tái)初始化函數(shù),名字中的_f表示這是第一階段的初始化,由于第二階段的初始化之前需要夾雜一些前提代碼,故將在start_armboot執(zhí)行。
display_banner:用來(lái)通過(guò)串口控制臺(tái)顯示U-Boot的版本信息(logo)。
print_cpuinfo:打印CPU的信息。
checkboard:確認(rèn)開(kāi)發(fā)板信息。即,打印出當(dāng)前開(kāi)發(fā)板的名稱(chēng)信息。
init_func_i2c:I2C初始化函數(shù),可在開(kāi)發(fā)板配置文件(/uboot/include/configs/x210_sd.h)中設(shè)置U-Boot是否使用I2C。
dram_init:DDR初始化函數(shù),由于DDR硬件層面的初始化已在第一階段完成,故此函數(shù)只是通過(guò)宏來(lái)獲取DDR相關(guān)信息,并填充至gd結(jié)構(gòu)體。DDR信息相關(guān)的宏在開(kāi)發(fā)板配置文件(/uboot/include/configs/x210_sd.h)中修改。
display_dram_config:打印bd中的DDR配置信息(由dram_init獲?。?/p>
init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup */ #if defined(CONFIG_SKIP_RELOCATE_UBOOT) reloc_init, /* Set the relocation done flag, mustdo this AFTER cpu_init()*/ #endif board_init, /* basic board dependent setup */ interrupt_init, /* set up exceptions */ env_init, /* initialize environment */ init_baudrate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console */ display_banner, /* say that we are here */ #if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */ #endif #if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */ #endif #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) init_func_i2c, #endif dram_init, /* configure available RAM banks */ display_dram_config, NULL, };
3)初始化全局變量結(jié)構(gòu)體gd_t、bd_t
本段功能是初始化全局變量結(jié)構(gòu)體。
gd_t是保存全集變量的結(jié)構(gòu)體類(lèi)型,在/uboot/include/asm-arm/Global_data.h文件中定義。
typedef struct global_data { bd_t *bd;//該指針指向bd_t結(jié)構(gòu)體,其具體內(nèi)容涉與板級(jí)硬件資源信息相關(guān)的全局變量 unsigned long flags; unsigned long baudrate; //串口波特率 unsigned long have_console;//標(biāo)志位,是否使用控制臺(tái)console unsigned long reloc_off; //重定位有關(guān)偏移量 unsigned long env_addr; //環(huán)境變量結(jié)構(gòu)體的偏移量 usigned long env_valid; //標(biāo)志位,表示內(nèi)存中的環(huán)境變量能否使用 unsigned long fb_base; //幀緩存基地址,和顯示有關(guān) #ifdef CONFIG_VFD unsigned char vfd_type; /* display type */ #endif void **jt; /* jump table *///基本無(wú)用 } gd_t;
gd_t結(jié)構(gòu)體中的bd指向一個(gè)bd_t結(jié)構(gòu)體,bd_t結(jié)構(gòu)體存放的是和開(kāi)發(fā)板硬件相關(guān)的全局變量,在/uboot/include/asm-arm/U-Boot.h文件中定義。
typedef struct bd_info { int bi_baudrate; //串口波特率 unsigned long bi_ip_addr; //IP地址 unsigned char bi_enetaddr[6]; //MAC地址 struct environment_s *bi_env; ulong bi_arch_number; //機(jī)器碼 ulong bi_boot_params; //U-Boot給內(nèi)核傳參的地址 struct //DDR相關(guān)信息 { ulong start; ulong size; } bi_dram[CONFIG_NR_DRAM_BANKS]; #ifdef CONFIG_HAS_ETH1 unsigned char bi_enet1addr[6]; #endif } bd_t;
gd(globle data)是指向gd_t結(jié)構(gòu)體的指針,在/uboot/include/asm-arm/Global_data.h文件中定義。
/*register表示盡量讓cpu放在寄存器中,以提高其讀寫(xiě)速度;asm (“r8”)是指定放在寄存器的r8*/ #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
給gd結(jié)構(gòu)體指針?lè)峙鋬?nèi)存地址,以后可通過(guò)gd指針訪(fǎng)問(wèn)全局變量結(jié)構(gòu)體gd_t。
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ ulong gd_base; gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t); ............... gd = (gd_t*)gd_base; #endif
為bd_t結(jié)構(gòu)體分配內(nèi)存空間,將gd下的一段內(nèi)存空間分配給bd_t。
清空gd_t、bd_t結(jié)構(gòu)體。
memset ((void*)gd, 0, sizeof (gd_t)); gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); memset (gd->bd, 0, sizeof (bd_t))
(2)運(yùn)行init_sequence數(shù)組中所有的初始化函數(shù)
利用for循環(huán)遍歷init_sequence數(shù)組,并執(zhí)行初始化函數(shù)。若函數(shù)返回值為0,則說(shuō)明初始化函數(shù)執(zhí)行錯(cuò)誤,掛起程序,進(jìn)入死循環(huán)。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } }
(3)初始化堆內(nèi)存
配置堆內(nèi)存的起止地址、終止地址。
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE); #else mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); #endif
(4)初始化外部存儲(chǔ)設(shè)備
若在開(kāi)發(fā)板配置文件中配置過(guò)外部存儲(chǔ)設(shè)備,則進(jìn)行相應(yīng)的初始化。
#if defined(CONFIG_SMDKC110) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB "); } #endif #if defined(CONFIG_MTD_ONENAND) puts("OneNAND: "); onenand_init(); /*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/ #else //puts("OneNAND: (FSR layer enabled) "); #endif #if defined(CONFIG_CMD_NAND) puts("NAND: "); nand_init(); #endif #endif /* CONFIG_SMDKC110 */
(5)環(huán)境變量的重定位
環(huán)境變量的重定位,將環(huán)境變量從啟動(dòng)介質(zhì)中讀到DDR內(nèi),環(huán)境變量的位置是通過(guò)原始分區(qū)信息表中讀到的。
/* initialize environment */ env_relocate ();
(6)獲取IP地址和MAC地址
從重定位之后的環(huán)境變量中獲取IP地址和MAC地址(以太網(wǎng)地址),放到bd中的全局變量?jī)?nèi),以供使用。
/* IP Address */ gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); /* MAC Address */ { int i; ulong reg; char *s, *e; char tmp[64]; i = getenv_r ("ethaddr", tmp, sizeof (tmp)); s = (i > 0) ? tmp : NULL; for (reg = 0; reg < 6; ++reg) { gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0; if (s) s = (*e) ? e + 1 : e; }
(7)其他函數(shù)
devices_init (); /* get the devices list going. *///設(shè)備驅(qū)動(dòng)初始化,從linux中移植而來(lái) jumptable_init ();//跳轉(zhuǎn)表初始化,其實(shí)沒(méi)用 console_init_r (); //控制臺(tái)的第二部分的初始化,有實(shí)質(zhì)性的功能 enable_interrupts ();//開(kāi)啟中斷,實(shí)質(zhì)是一個(gè)空函數(shù),U-Boot中并沒(méi)有使用中斷
(8)進(jìn)入main_loop循環(huán)
至此,U-Boot啟動(dòng)第二階段結(jié)束。即整個(gè)U-Boot啟動(dòng)完成,進(jìn)入main_loop循環(huán)。若控制臺(tái)有任意輸入,則進(jìn)入控制臺(tái)命令解析-執(zhí)行的循環(huán)。若無(wú),則U-Boot將啟動(dòng)內(nèi)核。
for (;;) { main_loop (); }
至此,U-Boot啟動(dòng)完成。
審核編輯:湯梓紅
-
嵌入式
+關(guān)注
關(guān)注
5083文章
19131瀏覽量
305543 -
soc
+關(guān)注
關(guān)注
38文章
4169瀏覽量
218350 -
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7605瀏覽量
136934 -
u-boot
+關(guān)注
關(guān)注
0文章
121瀏覽量
38229 -
代碼
+關(guān)注
關(guān)注
30文章
4790瀏覽量
68654
原文標(biāo)題:u-boot 啟動(dòng)內(nèi)幕
文章出處:【微信號(hào):技術(shù)讓夢(mèng)想更偉大,微信公眾號(hào):技術(shù)讓夢(mèng)想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論