imx6ull中uboot源碼分析-C環(huán)境建立

概述

在基于imx6ull平臺(tái)的linux開(kāi)發(fā)中,uboot的主要作用是為linux準(zhǔn)備好運(yùn)行環(huán)境,配置好硬件并將一些參數(shù)信息按照約定傳給內(nèi)核,然后跳轉(zhuǎn)到內(nèi)核運(yùn)行。uboot本身是一個(gè)裸機(jī)程序主要功能是引導(dǎo)操作系統(tǒng)運(yùn)行,功能其實(shí)并不復(fù)雜,只是開(kāi)始要建立C運(yùn)行環(huán)境,搬運(yùn)代碼到不同地方執(zhí)行、兼容各種處理器架構(gòu)、存儲(chǔ)器、網(wǎng)絡(luò)等功能代碼非?;逎?/p>

imx系列處理器內(nèi)部都固化了一段程序,這個(gè)程序根據(jù)啟動(dòng)引腳的配置,從不同存儲(chǔ)介質(zhì)約定的地址讀取程序的配置信息(IVT),通過(guò)讀取的信息可以找到用戶(hù)程序并加載到指定內(nèi)存運(yùn)行;配置中還有一些處理器寄存器初始化數(shù)據(jù),程序會(huì)將這些數(shù)據(jù)寫(xiě)入寄存器完成硬件的基本初始化功能,其中最主要的是處理器時(shí)鐘和DDR控制器的配置數(shù)據(jù),只有時(shí)鐘和內(nèi)存處理器配置完成后CPU才具備基本的運(yùn)行用戶(hù)程序運(yùn)行的條件。在DDR初始化之前所有運(yùn)行的程序只能使用處理器內(nèi)部的SRAM,起使地址為0x00900000,大小為128K或256K,依據(jù)具體器件而定。uboot也不列外,在編譯生成鏡像的時(shí)候會(huì)在uboot.bin前面加上這些配置信息生成最終的uboot.imx鏡像文件,IVT寄存器配置信息在board/freescale/xxx/imximage.cfg中,可以閱讀doc/imx/mkimage/imximage.txt查看具體的鏡像生成過(guò)程。

編譯

uboot的編譯時(shí)從make xxx_deconfig開(kāi)始的,這個(gè)命令執(zhí)行后會(huì)在uboot源碼的根目錄生成一個(gè).config配置文件,xxx_deconfig和硬件電路板對(duì)應(yīng),不同板子可以創(chuàng)建自己的配置文件,根據(jù)實(shí)際情況修改uboot的配置。

uboot運(yùn)行

了解程序啟動(dòng)過(guò)程必須先連接啟鏈接文件,找到處理器的中斷向量表所在的文件,進(jìn)而從復(fù)位中斷入開(kāi)始代碼運(yùn)行之旅。鏈接文件和具體的處理器有關(guān),查看源碼根目錄makefile,可以看到用戶(hù)可以指定鏈接文件,如果沒(méi)指定依次在固定目錄查找。imx6ull使用的鏈接文件是arch/arm/cpu/u-boot.lds。

# If board code explicitly specified LDSCRIPT or CONFIG_SYS_LDSCRIPT, use
# that (or fail if absent).  Otherwise, search for a linker script in a
# standard location.

ifndef LDSCRIPT
    #LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug
    ifdef CONFIG_SYS_LDSCRIPT
        # need to strip off double quotes
        LDSCRIPT := $(srctree)/$(CONFIG_SYS_LDSCRIPT:"%"=%)
    endif
endif

# If there is no specified link script, we look in a number of places for it
#按優(yōu)先級(jí)在不同目錄查找連接文件
ifndef LDSCRIPT
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds
    endif
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds
    endif
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds
    endif
endif

鏈接文件中有如下一段:

SECTIONS
{
    . = ALIGN(4);
    .text :
    {
        *(.__image_copy_start)
        *(.vectors)
        CPUDIR/start.o (.text*)
    }
}

從這段中可以看出鏡像的開(kāi)頭首先放了中斷向量表和一個(gè)名為start.o兩程序段,一般鏈接文件中具體指定名稱(chēng)的段代表一個(gè)文件或函數(shù)。.vectors段定義在arch/arm/lib/vectors.S中,這個(gè)文件定義了ARM架構(gòu)的中斷向量表,從文件所處的目錄位置來(lái)看不同指令集的ARM中斷向量表相同。

arch/arm/lib/vectors.S:

    .macro ARM_VECTORS
#ifdef CONFIG_ARCH_K3
    ldr     pc, _reset
#else
    b   reset
#endif
#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
    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
#endif
    .endm

    .section ".vectors", "ax"   //定義.vectors段

#if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
/*
 * Various SoCs need something special and SoC-specific up front in
 * order to boot, allow them to set that in their boot0.h file and then
 * use it here.
 *
 * To allow a boot0 hook to insert a 'special' sequence after the vector
 * table (e.g. for the socfpga), the presence of a boot0 hook supresses
 * the below vector table and assumes that the vector table is filled in
 * by the boot0 hook.  The requirements for a boot0 hook thus are:
 *   (1) defines '_start:' as appropriate
 *   (2) inserts the vector table using ARM_VECTORS as appropriate
 */
#include <asm/arch/boot0.h>
#else

/*
 *************************************************************************
 *
 * Exception vectors as described in ARM reference manuals
 *
 * Uses indirect branch to allow reaching handlers anywhere in memory.
 *
 *************************************************************************
 */

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */

arch/arm/lib/vectors.S---->arch/arm/cpu/armv7/start.S

在文件vectors.S中我們看到定義了一個(gè)名為.vectors的段,這和鏈接文件中最開(kāi)始放置的段名對(duì)應(yīng),因此程序從標(biāo)號(hào)_start開(kāi)始運(yùn)行,imx6ull中沒(méi)有定義CONFIG_SYS_DV_NOR_BOOT_CFG,因此第一行程序在ARM_VECTORS定義的宏中,我看到這個(gè)宏正是定義了ARM的中斷向量表,表中的第一條指令復(fù)位后自動(dòng)執(zhí)行,跳轉(zhuǎn)到reset函數(shù)中,函數(shù)實(shí)現(xiàn)在arch/arm/cpu/armv7/start.S文件中,這個(gè)文件和ARM的具體架構(gòu)有關(guān),我們下面一段一段分析這個(gè)文件中的代碼。代碼分析是建立在定義了CONFIG_SPL_BUILD宏的基礎(chǔ)上,也就是使用SPL,如果不使用SPL則會(huì)省去relocate_code函數(shù)調(diào)用,也就是不用搬運(yùn)代碼了,會(huì)簡(jiǎn)單一些。不管是否使用SPL在復(fù)位發(fā)生時(shí)uboot一開(kāi)始都運(yùn)行在鏈接時(shí)指定的內(nèi)存地址中,只是使用SPL時(shí)會(huì)搬運(yùn)一次代碼到DDR頂部運(yùn)行。

    .globl  reset
    .globl  save_boot_params_ret
    .type   save_boot_params_ret,%function
#ifdef CONFIG_ARMV7_LPAE
    .global switch_to_hypervisor_ret
#endif

reset:
    /* Allow the board to save important registers */
    b   save_boot_params   //跳轉(zhuǎn)到參數(shù)保存函數(shù),在本文件中,預(yù)留,沒(méi)有實(shí)際操作
#endif

代碼重定位

文件開(kāi)頭就定義了全局標(biāo)號(hào)reset,中斷向量表中b reset指令正是跳轉(zhuǎn)到這里執(zhí)行,要強(qiáng)調(diào)一下這條指令時(shí)一條相對(duì)尋址指令,也就處理器是通過(guò)PC寄存器加上一個(gè)偏移值來(lái)獲取目標(biāo)地址然后跳轉(zhuǎn)過(guò)去的,目標(biāo)地址和當(dāng)前PC的值是相關(guān)的,只要他們相對(duì)位置固定這條指令總能正確執(zhí)行。這個(gè)很重要,因?yàn)樵诰幾guboot的時(shí)候并不確定它會(huì)在內(nèi)存的哪個(gè)地址運(yùn)行,如果采用絕對(duì)地址尋址,當(dāng)uboot實(shí)際運(yùn)行地址和編譯時(shí)候指定的鏈接地址不同時(shí)程序會(huì)跳轉(zhuǎn)到一個(gè)錯(cuò)誤地址。這種相對(duì)尋址的指令就可以解決這個(gè)問(wèn)題,讓uboot可以運(yùn)行在內(nèi)存任意地址(要想實(shí)現(xiàn)這點(diǎn)還有其它一些工作要做,下面會(huì)討論)。也許有人會(huì)想,我一個(gè)開(kāi)始在給指定板子編譯uboot時(shí)根據(jù)硬件就可以知道內(nèi)存的地址信息,直接按照這個(gè)地址來(lái)編譯鏡像,加載uboot時(shí)也加載在這個(gè)地址不就行了嗎。沒(méi)錯(cuò)這樣確實(shí)可以保證程序邏輯上可以正確運(yùn)行,但是這樣有時(shí)是做不到的。

  • 有的處理器上電后并不能自動(dòng)初始化DDR控制器,一般內(nèi)部有一小段SRAM,上電后uboot的最前面的一段代碼會(huì)被拷貝到這個(gè)小內(nèi)存中運(yùn)行,這段程序就是uboot的第一階段引導(dǎo)程序,它的主要功能之一就是初始化主內(nèi)存DDR,主內(nèi)存初始化完成后會(huì)將所有uboot拷貝到DDR中運(yùn)行。這就帶來(lái)了問(wèn)題uboot要保證在內(nèi)部SRAM和DDR中都能正確運(yùn)行,但鏈接時(shí)只能指定一個(gè)運(yùn)行地址(一般為DDR范圍內(nèi)的地址)。
  • uboot的主要作用是引導(dǎo)linux內(nèi)核運(yùn)行,內(nèi)存中會(huì)存在uboot和linux內(nèi)核兩個(gè)可執(zhí)行程序,他們之間的地址存在沖突的可能性,因此uboot在設(shè)計(jì)的時(shí)候會(huì)自動(dòng)自我拷貝到DDR的最高地址處運(yùn)行把低地址留給linux內(nèi)核,也就是說(shuō)即使在主內(nèi)存中uboot也有可能運(yùn)行在不同地址。

基于上述兩點(diǎn)以及為了簡(jiǎn)化uboot的編譯配置,uboot采用了位置無(wú)關(guān)的代碼編譯方式,即在編譯代碼時(shí)加入了-pie選項(xiàng)保證整個(gè)uboot的是位置無(wú)關(guān)的。

接著分析代碼,首先跳轉(zhuǎn)到save_boot_params處執(zhí)行,這在當(dāng)前本文件中定義,預(yù)留的函數(shù)接口沒(méi)有實(shí)際操作然后跳回save_boot_params_ret處。接下來(lái)的操作就比較有意思了,主要功能是第一次重定位代碼,這里說(shuō)是第一次是因?yàn)楹竺娴拇a在搬運(yùn)uboot后還會(huì)再次重定位一次。前文描述了編譯器在編譯uboot時(shí)使用了-pie選項(xiàng)這樣鏡像中會(huì)生成一個(gè)名為rel.dyn的段,這個(gè)段中包含了程序內(nèi)所有需要重定位的全局變量和函數(shù)的地址信息。對(duì)于匯編程序可以精確的選擇相對(duì)尋址的指令引用相關(guān)函數(shù)和變量,但是C語(yǔ)言程序要由編譯器翻譯成機(jī)器碼沒(méi)法精確要編譯器使用指定的匯編指令進(jìn)行相對(duì)尋址,而且當(dāng)程序?qū)ぶ贩秶容^大時(shí)就不可避免的使用絕對(duì)尋址指令,因此就設(shè)計(jì)了一套機(jī)制,編譯器將所有需要絕對(duì)尋址的標(biāo)號(hào)(函數(shù)和全局變量)的內(nèi)存地址信息專(zhuān)門(mén)放到一個(gè)段中,運(yùn)行代碼本身要根據(jù)實(shí)際運(yùn)行內(nèi)存地址和編譯鏈接地址的差值去修正該段中導(dǎo)出的全局標(biāo)號(hào)的引用地址,當(dāng)實(shí)際運(yùn)行內(nèi)存地址和編譯鏈接地址一致時(shí)不需要修正。這個(gè)原理其實(shí)和操作系統(tǒng)常用的動(dòng)態(tài)鏈接庫(kù)加載時(shí)根據(jù)got段引用外部標(biāo)號(hào)及導(dǎo)出內(nèi)部標(biāo)號(hào)異曲同工。原理是程序內(nèi)部在引用全局標(biāo)號(hào)時(shí)都不時(shí)直接以用,而是在將全局標(biāo)號(hào)的地址信息放到一個(gè)中轉(zhuǎn)表中,這個(gè)表每個(gè)條目記錄著全局標(biāo)號(hào)的實(shí)際內(nèi)存地址,當(dāng)引用全局標(biāo)號(hào)時(shí)首先從中轉(zhuǎn)表中拿到其內(nèi)存地址,然后再通過(guò)這個(gè)內(nèi)存地址引用真正的全局標(biāo)號(hào)。而rel.dyn存在的意義就是方便修改中轉(zhuǎn)表中的內(nèi)容,達(dá)到重定位全局標(biāo)號(hào)的目的。

地址重定位

如圖,程序中定義了全局變量A,當(dāng)我在代碼其它地方引用這個(gè)變量時(shí)一般編譯器會(huì)直接計(jì)算出A所在的地址直接操作內(nèi)存,這樣有可能會(huì)通過(guò)絕對(duì)地址引用A,當(dāng)鏡像運(yùn)行地址和鏈接地址不一致時(shí)A的絕對(duì)地址已經(jīng)改變,再用原來(lái)的鏈接地址去引用A必然時(shí)錯(cuò)誤的。圖中將地址A的地址放在了內(nèi)存0x81370000中,假設(shè)A的鏈接地址是0x82150000,如果引用A時(shí)先從0x81370000地址處取得變量A的真實(shí)地址,再用這個(gè)地址去引用變量A,同樣可以正確對(duì)A進(jìn)項(xiàng)操作,當(dāng)重定位后整個(gè)鏡像的地址向下偏移了0x10000(從rel.dyn段所處的運(yùn)行地址可以計(jì)算出),此時(shí)A的地址變?yōu)榱?x81360000,如果還想正確引用A只要把0x81380000地址中A的指針改為0x81360000即可,這個(gè)過(guò)程就實(shí)現(xiàn)了代碼的重定位。這里大家也許有個(gè)疑問(wèn),上述過(guò)程在獲取變量A指針的過(guò)程中使用了地址0x81370000這個(gè)也不是絕對(duì)地址嗎?這個(gè)編譯器是這么處理的,編譯器會(huì)把被引用的全局標(biāo)號(hào)的地址放在引用它的代碼附近的內(nèi)存中,這樣引用全局標(biāo)號(hào)的指令很容易通過(guò)相對(duì)地址引用到這個(gè)內(nèi)存進(jìn)而獲取全局標(biāo)號(hào)的地址。之所以是這樣是因?yàn)橄鄬?duì)地址引用的范圍是有限的,只能引用當(dāng)前PC前后一定范圍內(nèi)的地址。至于編譯器怎么實(shí)現(xiàn)將全局標(biāo)號(hào)的地址放在引用這個(gè)標(biāo)號(hào)的附近,這個(gè)其實(shí)很好辦參考start.S中類(lèi)似_rel_dyn_start_ofs的定義即可。

下面我們通過(guò)注釋分析代碼整個(gè)重定位流程:

save_boot_params_ret:
#ifdef CONFIG_POSITION_INDEPENDENT
    /*
     * Fix .rela.dyn relocations. This allows U-Boot to loaded to and
     * executed at a different address than it was linked at.
     */
pie_fixup:
    /* 獲取標(biāo)號(hào)reset的運(yùn)行地址到r0 */
    adr r0, reset   /* r0 <- Runtime value of reset label */
    /* 獲取標(biāo)號(hào)reset的鏈接地址到r0 */
    ldr r1, =reset  /* r1 <- Linked value of reset label */
    /* 計(jì)算運(yùn)行地址和link地址的偏移 */
    subs    r4, r0, r1  /* r4 <- Runtime-vs-link offset */

    /* 如果為0,說(shuō)明link地址和運(yùn)行地址一致,不需要重定位直接退出 */
    beq pie_fixup_done

    /* 
     * 下面幾行代碼的作用是計(jì)算運(yùn)行時(shí)rel.dyn段在內(nèi)存中實(shí)際地址,只有獲取這個(gè)段的
     * 真實(shí)的起使地址才能依據(jù)其中的信息進(jìn)行重定位。
     */
    //獲取pie_fixup標(biāo)號(hào)的運(yùn)行地址
    adr r0, pie_fixup
    //_rel_dyn_start_ofs鏈接時(shí)rel.dyn段相對(duì)pie_fixup標(biāo)號(hào)的偏移
    ldr r1, _rel_dyn_start_ofs
    //計(jì)算rel.dyn運(yùn)行時(shí)起始地址
    /*
     * rel.dyn(運(yùn)行起始地址) = rel.dyn(鏈接起始地址)- pie_fixup(鏈接地址) + pie_fixup(運(yùn)行地址)
     *
     * 個(gè)人認(rèn)為覺(jué)得已經(jīng)在r4中已經(jīng)計(jì)算了運(yùn)行地址相對(duì)link地址的偏移,直接用rel.dyn鏈接地址加上這個(gè)偏移也可以計(jì)算rel.dyn的起使運(yùn)行地址,而且這樣更直觀一些。
    */
    add r2, r0, r1  /* r2 <- Runtime &__rel_dyn_start */
    ldr r1, _rel_dyn_end_ofs
    //計(jì)算rel.dyn運(yùn)行結(jié)束地址
    add r3, r0, r1  /* r3 <- Runtime &__rel_dyn_end */

pie_fix_loop:
    //獲取rel.dyn段地址中的內(nèi)容,參考上圖執(zhí)行后r0中的值是0x81370000
    ldr r0, [r2]    /* r0 <- Link location */’
    //獲取rel.dyn段地址接下來(lái)4個(gè)字節(jié)中的內(nèi)容,參考上圖執(zhí)行后r1中的值是0x17即23
    ldr r1, [r2, #4]    /* r1 <- fixup */
    //如果r1等于23則執(zhí)行重定位
    cmp r1, #23     /* relative fixup? */
    bne pie_skip_reloc

    /* relative fix: increase location by offset */
    //將r0中的地址加上偏移地址獲得實(shí)際運(yùn)行時(shí)的有效地址,執(zhí)行后r0值為0x81380000
    add r0, r4
    //獲取0x81380000中的內(nèi)容,值為0x82150000
    ldr r1, [r0]
    //0x82150000 + 0x10000, r0中的內(nèi)容加上偏移后存回0x81380000
    add r1, r4
    str r1, [r0]
    //更新運(yùn)行時(shí)rel.dyn段所在地址0x82010000,中的內(nèi)容,使其指向運(yùn)行時(shí)變量A地址所在的內(nèi)存
    str r0, [r2]
    add r2, #8
pie_skip_reloc:
    //判斷是否所有表項(xiàng)都修改完成,沒(méi)完成則循環(huán)操作
    cmp r2, r3
    blo pie_fix_loop
pie_fixup_done:
=================================================
_rel_dyn_start_ofs:
    .word   __rel_dyn_start - pie_fixup
_rel_dyn_end_ofs:
    .word   __rel_dyn_end - pie_fixup

上面描述了重定位的原理和過(guò)程,不過(guò)start.S中的這次重定位rel.dyn一般是直接跳過(guò)的,因?yàn)閺暮竺娴囊恍┐a來(lái)看在board_init_f函數(shù)調(diào)用返回后再次搬運(yùn)代碼之前,uboot中很多地方必須要求鏈接地址和運(yùn)行地址一致,比如沒(méi)有對(duì)board_init_f中調(diào)用的函數(shù)列表中的指針加上偏移值做修正。

關(guān)閉中斷和切換到SVC模式

在引導(dǎo)過(guò)程中不需要開(kāi)啟中斷以此關(guān)閉中斷,同時(shí)進(jìn)入SCV模式,這樣可以時(shí)uboot運(yùn)行在更高優(yōu)先級(jí)可以更好的控制CPU,同時(shí)linux系統(tǒng)也是運(yùn)行在這個(gè)模式下。

#ifdef CONFIG_ARMV7_LPAE
/*
 * check for Hypervisor support
 */
    mrc p15, 0, r0, c0, c1, 1       @ read ID_PFR1
    and r0, r0, #CPUID_ARM_VIRT_MASK    @ mask virtualization bits
    cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
    beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif
    /*
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
     * except if in HYP mode already
     */
    mrs r0, cpsr
    and r1, r0, #0x1f       @ mask mode bits
    teq r1, #0x1a       @ test for HYP mode
    bicne   r0, r0, #0x1f       @ clear all mode bits
    orrne   r0, r0, #0x13       @ set SVC mode
    orr r0, r0, #0xc0       @ disable FIQ and IRQ
    msr cpsr,r0

重定位中斷向量表關(guān)閉緩存和MMU

重定位中斷向量表,中斷向量指向arch/arm/lib/vectors.S中的中斷向量表。ARM結(jié)構(gòu)中的p15協(xié)處理器有一個(gè)VBAR寄存器,中斷時(shí)處理器根據(jù)這個(gè)寄存器中的地址尋找中斷向量表。不過(guò)這里設(shè)置的中斷向量表基地址明顯是有問(wèn)題,把_start的鏈接地址寫(xiě)入到了VBAR中,這明顯是錯(cuò)誤的,應(yīng)該把_start的運(yùn)行地址寫(xiě)入VBAR。不過(guò)這個(gè)沒(méi)關(guān)系,因?yàn)檫@里已經(jīng)關(guān)閉了中斷,而且后面在調(diào)用_main時(shí)還要重新設(shè)置。

#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/*
 * Setup vector:
 */
    /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
    mrc p15, 0, r0, c1, c0, 0   @ Read CP15 SCTLR Register
    bic r0, #CR_V       @ V = 0
    mcr p15, 0, r0, c1, c0, 0   @ Write CP15 SCTLR Register

#ifdef CONFIG_HAS_VBAR
    /* Set vector address in CP15 VBAR register */
    ldr r0, =_start
    mcr p15, 0, r0, c12, c0, 0  @Set VBAR
#endif
#endif

cpu_init_cp15中主是關(guān)閉緩存和MMU,關(guān)閉MMU是邏輯程序和操作系統(tǒng)最主要的區(qū)別,因此我們認(rèn)為uboot是一個(gè)復(fù)雜一些的裸機(jī)程序。

#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
    bl  cpu_init_cp15
#endif
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
    bl  cpu_init_crit
#endif
#endif

    bl   _main

cpu_init_crit調(diào)用了lowlevel_init,這個(gè)函數(shù)在文件arch/arm/cpu/armv7/lowlevel_init.S中

ENTRY(cpu_init_crit)
    /*
     * Jump to board specific initialization...
     * The Mask ROM will have already initialized
     * basic memory. Go here to bump up clock rate and handle
     * wake up conditions.
     */
    b   lowlevel_init       @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

arch/arm/cpu/armv7/start.S---->arch/arm/cpu/armv7/lowlevel_init.S

lowlevel_init函數(shù)主要將棧指向內(nèi)部IRMA中,imx6ull中定義了CONFIG_SPL_BUILD宏,其次將r9寄存器指向gdata數(shù)據(jù)結(jié)構(gòu),gdata中保存了很多全局變量。不過(guò)由于使用了SPL這些設(shè)置都意義不大,后面board_init_f還要重新設(shè)置。

.pushsection .text.s_init, "ax"
WEAK(s_init)
    bx  lr
ENDPROC(s_init)
.popsection

.pushsection .text.lowlevel_init, "ax"
WEAK(lowlevel_init)
    /*
     * Setup a temporary stack. Global data is not available yet.
     */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr sp, =CONFIG_SPL_STACK
#else
    ldr sp, =CONFIG_SYS_INIT_SP_ADDR
#endif
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
    mov r9, #0
#else
    /*
     * Set up global data for boards that still need it. This will be
     * removed soon.
     */
#ifdef CONFIG_SPL_BUILD
    ldr r9, =gdata
#else
    sub sp, sp, #GD_SIZE
    bic sp, sp, #7
    mov r9, sp
#endif
#endif
    /*
     * Save the old lr(passed in ip) and the current lr to stack
     */
    push    {ip, lr}

    /*
     * Call the very early init function. This should do only the
     * absolute bare minimum to get started. It should not:
     *
     * - set up DRAM
     * - use global_data
     * - clear BSS
     * - try to start a console
     *
     * For boards with SPL this should be empty since SPL can do all of
     * this init in the SPL board_init_f() function which is called
     * immediately after this.
     */
    bl  s_init
    pop {ip, pc}
ENDPROC(lowlevel_init)
.popsection

cpu_init_crit是個(gè)空函數(shù),接著調(diào)用_main函數(shù),其定義在arch/arm/lib/crt0.S文件中。

arch/arm/cpu/armv7/start.S---->arch/arm/lib/crt0.S

_main主要完成C語(yǔ)言環(huán)境的初始化工作,為調(diào)用c語(yǔ)言編寫(xiě)的board_init_f函數(shù)組做準(zhǔn)備,這個(gè)函數(shù)會(huì)真正初始化SPL階段的棧和gd全局?jǐn)?shù)據(jù)結(jié)構(gòu)。棧的基地址被設(shè)置成了CONFIG_TPL_STACK并做了8字節(jié)對(duì)齊處理,CONFIG_TPL_STACK在include/configs文件夾下板子對(duì)應(yīng)的配置文件中定義。這里先將CONFIG_TPL_STACK臨時(shí)作為堆棧的起始地址是因?yàn)楹竺鏁?huì)調(diào)用C函數(shù)了,必須有一個(gè)可用的堆棧。堆棧的設(shè)置并沒(méi)有一步到位后面調(diào)用C函數(shù)后還會(huì)依據(jù)返回值重新設(shè)置第一階段使用的最終堆棧。

SPL階段內(nèi)存布局.png

上圖展示了最終處理器內(nèi)部SRAM的布局情況,從CONFIG_TPL_STACK地址開(kāi)始向上依次為heap、gddata、棧,這些工作是由board_init_f_alloc_reserve、board_init_f_init_reserve兩個(gè)C函數(shù)完成的,他們定義在/common/init/board_init.c文件中,board_init_f_alloc_reserve函數(shù)負(fù)責(zé)為heap和gddata預(yù)留內(nèi)存空間,board_init_f_init_reserve負(fù)責(zé)從已經(jīng)預(yù)留的內(nèi)空間中為heap和gddata做些初始化工作,此后r9寄存器固定指向gd結(jié)構(gòu)首地址。堆棧環(huán)境準(zhǔn)備好了,接下來(lái)就是盡早調(diào)用串口初始化函數(shù)debug_uart_init,去實(shí)現(xiàn)再在文件include/debug_uart.h中,這是一個(gè)宏,最終調(diào)用board_debug_uart_init()、 _debug_uart_init()兩個(gè)函數(shù),imx6ull中board_debug_uart_init是個(gè)空函數(shù),_debug_uart_init定義在drivers/serial/serial_mxc.c中,主要就是初始化串口相關(guān)寄存器,此后串口就可以正常打印log了。接下來(lái)清零bss段,這個(gè)通過(guò)宏CLEAR_BSS引用。至此SPL階段的C語(yǔ)言環(huán)境完全準(zhǔn)備就緒可以調(diào)用board_init_f完善gd數(shù)據(jù)結(jié)構(gòu)及其余的基礎(chǔ)初始化工作。

.macro CLEAR_BSS
    ldr r0, =__bss_start    /* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
    ldr r3, =__bss_end      /* this is auto-relocated! */
    mov r1, #0x00000000     /* prepare zero to clear BSS */

    subs    r2, r3, r0      /* r2 = memset len */
    bl  memset
#else
    ldr r1, =__bss_end      /* this is auto-relocated! */
    mov r2, #0x00000000     /* prepare zero to clear BSS */

clbss_l:cmp r0, r1          /* while not at end of BSS */
    strlo   r2, [r0]        /* clear 32-bit BSS word */
    addlo   r0, r0, #4      /* move to next */
    blo clbss_l
#endif
.endm

/*
 * entry point of crt0 sequence
 */

ENTRY(_main)

/* Call arch_very_early_init before initializing C runtime environment. */
#if CONFIG_IS_ENABLED(ARCH_VERY_EARLY_INIT)
    bl  arch_very_early_init
#endif

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
    ldr r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr r0, =(CONFIG_SPL_STACK)
#else
    ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
    bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
    mov sp, r0
    bl  board_init_f_alloc_reserve
    mov sp, r0
    /* set up gd here, outside any C code */
    mov r9, r0
    bl  board_init_f_init_reserve

#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)
    bl  debug_uart_init
#endif

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

    mov r0, #0
    bl  board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we'll return
 * 'here' but relocated.
 */

    ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
    bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
    mov sp, r0
    ldr r9, [r9, #GD_NEW_GD]        /* r9 <- gd->new_gd */

    adr lr, here
#if defined(CONFIG_POSITION_INDEPENDENT)
    adr r0, _main
    ldr r1, _start_ofs
    add r0, r1
    ldr r1, =CONFIG_SYS_TEXT_BASE
    sub r1, r0
    add lr, r1
#endif
    ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */
    add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
    orr lr, #1              /* As required by Thumb-only */
#endif
    ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
    b   relocate_code
here:
/*
 * now relocate vectors
 */

    bl  relocate_vectors

/* Set up final (full) environment */

    bl  c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)

#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

# ifdef CONFIG_SPL_BUILD
    /* Use a DRAM stack for the rest of SPL, if requested */
    bl  spl_relocate_stack_gd
    cmp r0, #0
    movne   sp, r0
    movne   r9, r0
# endif

#if ! defined(CONFIG_SPL_BUILD)
    bl coloured_LED_init
    bl red_led_on
#endif
    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                  /* gd_t */
    ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
    /* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
    ldr lr, =board_init_r   /* this is auto-relocated! */
    bx  lr
#else
    ldr pc, =board_init_r   /* this is auto-relocated! */
#endif
    /* we should not return here. */
#endif

ENDPROC(_main)

_start_ofs:
    .word   _start - _main

arch/arm/cpu/armv7/start.S---->arch/arm/lib/crt0.S---->common/board_f.c

static const init_fnc_t init_sequence_f[] = {
    setup_mon_len,
#ifdef CONFIG_OF_CONTROL
    fdtdec_setup,
#endif
#ifdef CONFIG_TRACE_EARLY
    trace_early_init,
#endif
    initf_malloc,
    log_init,
    initf_bootstage,    /* uses its own timer, so does not need DM */
    event_init,
#ifdef CONFIG_BLOBLIST
    bloblist_init,
#endif
    setup_spl_handoff,
#if defined(CONFIG_CONSOLE_RECORD_INIT_F)
    console_record_init,
#endif
#if defined(CONFIG_HAVE_FSP)
    arch_fsp_init,
#endif
    arch_cpu_init,      /* basic arch cpu dependent setup */
    mach_cpu_init,      /* SoC/machine dependent CPU setup */
    initf_dm,
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
    /* get CPU and bus clocks according to the environment variable */
    get_clocks,     /* get CPU and bus clocks (etc.) */
#endif
#if !defined(CONFIG_M68K)
    timer_init,     /* initialize timer */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
    board_postclk_init,
#endif
    env_init,       /* initialize environment */
    init_baud_rate,     /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */
    display_options,    /* say that we are here */
    display_text_info,  /* show debugging info if required */
    checkcpu,
#if defined(CONFIG_SYSRESET)
    print_resetinfo,
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,      /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DTB_RESELECT)
    embedded_dtb_select,
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    show_board_info,
#endif
    INIT_FUNC_WATCHDOG_INIT
    misc_init_f,
    INIT_FUNC_WATCHDOG_RESET
#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
    init_func_i2c,
#endif
#if defined(CONFIG_VID) && !defined(CONFIG_SPL)
    init_func_vid,
#endif
    announce_dram_init,
    dram_init,      /* configure available RAM banks */
#ifdef CONFIG_POST
    post_init_f,
#endif
    INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
    testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
    INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
    init_post,
#endif
    INIT_FUNC_WATCHDOG_RESET
    /*
     * Now that we have DRAM mapped and working, we can
     * relocate the code and continue running from DRAM.
     *
     * Reserve memory at end of RAM for (top down in that order):
     *  - area that won't get touched by U-Boot and Linux (optional)
     *  - kernel log buffer
     *  - protected RAM
     *  - LCD framebuffer
     *  - monitor code
     *  - board info struct
     */
    setup_dest_addr,
#ifdef CONFIG_OF_BOARD_FIXUP
    fix_fdt,
#endif
#ifdef CONFIG_PRAM
    reserve_pram,
#endif
    reserve_round_4k,
    arch_reserve_mmu,
    reserve_video,
    reserve_trace,
    reserve_uboot,
    reserve_malloc,
    reserve_board,
    reserve_global_data,
    reserve_fdt,
    reserve_bootstage,
    reserve_bloblist,
    reserve_arch,
    reserve_stacks,
    dram_init_banksize,
    show_dram_config,
    INIT_FUNC_WATCHDOG_RESET
    setup_bdinfo,
    display_new_sp,
    INIT_FUNC_WATCHDOG_RESET
    reloc_fdt,
    reloc_bootstage,
    reloc_bloblist,
    setup_reloc,
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
    copy_uboot_to_ram,
    do_elf_reloc_fixups,
#endif
    clear_bss,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
        !CONFIG_IS_ENABLED(X86_64)
    jump_to_copy,
#endif
    NULL,
};

void board_init_f(ulong boot_flags)
{
    gd->flags = boot_flags;
    gd->have_console = 0;

    if (initcall_run_list(init_sequence_f))
        hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
        !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
        !defined(CONFIG_ARC)
    /* NOTREACHED - jump_to_copy() does not return */
    hang();
#endif
}

board_init_f函數(shù)定義在文件common/board_f.c中,代碼很簡(jiǎn)單就是順序調(diào)用init_sequence_f列表中函數(shù),參數(shù)boot_flags為0。

  • setup_mon_len 將uboot從開(kāi)始到bss段結(jié)束的長(zhǎng)度信息設(shè)置到gd->mon_len
  • fdtdec_setup 將設(shè)備樹(shù)起始地址設(shè)置到gd->fdt_blob中,設(shè)備樹(shù)放置的位置有多種方式,可以緊跟著uboot的__bss_end放置,也可以放在uboot中的__dtb_dt_spl_begin段中,用戶(hù)也可以通過(guò)配置自己指定。這個(gè)函數(shù)還要校驗(yàn)設(shè)備樹(shù)的有效性。
  • initf_malloc 初始化gd中malloc使用的heap相關(guān)信息,基址除外,基址已經(jīng)在board_init_f_init_reserve中設(shè)置
  • initf_bootstage 記錄引導(dǎo)階段
  • console_record_init:控制臺(tái)輸入輸出緩沖區(qū)初始化
  • arch_cpu_init:cpu相關(guān)初始化和具體器件有關(guān),imx6ull這個(gè)函數(shù)定義在arch/arm/mach-imx/mx6/soc.c中
  • initf_dm:設(shè)備模型初始化,這個(gè)比較復(fù)雜本文不做討論
  • board_early_init_f:板級(jí)初始化,imx6ull定義在board/freescale/mx6ullevk/mx6ullevk.c中,與板子相關(guān)
  • env_init:環(huán)境變量輸出化,uboot環(huán)境環(huán)境變量可以存儲(chǔ)在很多介質(zhì)中,每種類(lèi)型的介質(zhì)會(huì)提供一個(gè)struct env_driver類(lèi)型的驅(qū)動(dòng)結(jié)構(gòu)來(lái)從這些介質(zhì)訪問(wèn)保存的環(huán)境變量,這里就遍歷驅(qū)動(dòng)列表調(diào)用其初始化函數(shù),如果沒(méi)有唯一指定用哪個(gè)介質(zhì)中的環(huán)境變量,還會(huì)設(shè)置uboot鏡像中名為default_environment的環(huán)境變量,這個(gè)環(huán)境變量很多條目是在include/configs板級(jí)別頭文件中定義的,所有使用的環(huán)境變量最終會(huì)用一個(gè)hash列表管理。
  • display_options:到這里就在控制臺(tái)打印出uboot的基本信息了
  • dram_init:獲取DDR大小到gd->ram_size中,imx6ull是通過(guò)讀取DDR控制器中配置的信息獲取大小的
  • setup_dest_addr:初始化ddr相關(guān)信息,這些在后面代碼重定位時(shí)會(huì)用到,知道ddr這些信息后就可以重新為堆、棧以及其它數(shù)據(jù)結(jié)構(gòu)在ddr中分配內(nèi)存,分配完還要把相關(guān)數(shù)據(jù)拷貝到新內(nèi)存中,后面一系列函數(shù)都做這個(gè)工作。
  • setup_reloc:計(jì)算uboot重定位的偏移地址,后面會(huì)根據(jù)這個(gè)偏移把uboot鏡像拷貝到DDR中,同時(shí)拷貝老gd內(nèi)容到新gd中。

至此board_init_f函數(shù)完成,可以看到這個(gè)函數(shù)主要時(shí)完成了gd數(shù)據(jù)結(jié)構(gòu)的初始化、ddr內(nèi)存分配和一些數(shù)據(jù)結(jié)構(gòu)的搬運(yùn)調(diào)整工作,uboot將被搬運(yùn)到DDR的接近top的地址處。沒(méi)有設(shè)計(jì)到太多硬件的初始化。

arch/arm/lib/crt0.S

#if ! defined(CONFIG_SPL_BUILD)

/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we'll return
 * 'here' but relocated.
 */

    ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
    bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
    mov sp, r0
    ldr r9, [r9, #GD_NEW_GD]        /* r9 <- gd->new_gd */

    adr lr, here   //獲取here當(dāng)前運(yùn)行地址
#if defined(CONFIG_POSITION_INDEPENDENT)
    /* 計(jì)算當(dāng)前運(yùn)行地址和鏈接地址的偏移 */
    adr r0, _main
    ldr r1, _start_ofs
    add r0, r1
    ldr r1, =CONFIG_SYS_TEXT_BASE
    sub r1, r0
    add lr, r1
#endif
    /* 獲取gd->reloc_off值 */
    ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */
    //計(jì)算最終的返回地址
    add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
    orr lr, #1              /* As required by Thumb-only */
#endif
    ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
    b   relocate_code
here:
/*
 * now relocate vectors
 */

    bl  relocate_vectors

/* Set up final (full) environment */

    bl  c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)

#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

# ifdef CONFIG_SPL_BUILD
    /* Use a DRAM stack for the rest of SPL, if requested */
    bl  spl_relocate_stack_gd
    cmp r0, #0
    movne   sp, r0
    movne   r9, r0
# endif

#if ! defined(CONFIG_SPL_BUILD)
    bl coloured_LED_init
    bl red_led_on
#endif
    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                  /* gd_t */
    ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
    /* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
    ldr lr, =board_init_r   /* this is auto-relocated! */
    bx  lr
#else
    ldr pc, =board_init_r   /* this is auto-relocated! */
#endif
    /* we should not return here. */
#endif

ENDPROC(_main)

回到crt0.S中,開(kāi)始的時(shí)候r9保存了gd老地址的指針,接下來(lái)做下面幾個(gè)工作

  • 重新初始化?;刂芳拇嫫?,新地址在ddr中,由reserve_stacks函數(shù)分配
  • 將r9指向ddr中g(shù)d的新地址,之前在setup_reloc中將老gd拷貝到了新的gd
  • 調(diào)用relocate_code函數(shù)將uboot鏡像搬運(yùn)到ddr中,目標(biāo)地址由reserve_uboot計(jì)算,在調(diào)用這個(gè)函數(shù)之前還有一件重要任務(wù)是計(jì)算函數(shù)的返回地址lr,因?yàn)閺膔elocate_code返回時(shí),ddr中新地址的uboot鏡像已經(jīng)就緒要跳到新地址運(yùn)行而且要返回到新地址的here處繼續(xù)向下執(zhí)行,并不能重新從uboot開(kāi)始處執(zhí)行。這里計(jì)算lr比較復(fù)雜,因?yàn)樯婕暗饺齻€(gè)地址之間的關(guān)系鏈接地址、當(dāng)前運(yùn)行地址、目標(biāo)運(yùn)行地址(一般這里鏈接地址、當(dāng)前運(yùn)行地址一致),gd->reloc_off是目標(biāo)運(yùn)行地址與鏈接地址的差值,因此返回地址計(jì)算公式為:

lr = gd->reloc_off-當(dāng)前運(yùn)行地址和鏈接地址的偏移 + here當(dāng)前運(yùn)行地址

lr = 目標(biāo)運(yùn)行地址和當(dāng)前運(yùn)行地址的偏移 + here當(dāng)前運(yùn)行地址

gd->reloc_off-當(dāng)前運(yùn)行地址和鏈接地址的偏移就等于目標(biāo)運(yùn)行地址和當(dāng)前運(yùn)行地址的偏移。

  • relocate_code:這段代碼和“代碼重定位”一節(jié)中描述的功能完全一致,再次修正rel.dyn段是因?yàn)閡boot鏡像又被搬運(yùn)到新地址了。
  • relocate_vectors:重新設(shè)置中斷向量表基地址,前文設(shè)置過(guò)一次但是是不正確的,搬運(yùn)過(guò)代碼再次設(shè)置。
  • 前面已經(jīng)清除過(guò)bss段,這里不用再清除
  • spl_relocate_stack_gd: 這里又重新計(jì)算棧、堆、gd全局變量的地址如果deconfig文件中配置了CONFIG_SPL_STACK_R,這是因?yàn)椴煌渲玫膗boot可能需要更大的棧和堆,所以設(shè)置到ddr中,如果沒(méi)有配置CONFIG_SPL_STACK_R則棧是在sram中,由board_init_f_alloc_reserve分配,但是gd和堆其實(shí)已經(jīng)在ddr中,地址是由函數(shù)reserve_global_data、reserve_malloc分配,gd基地址后續(xù)在crt0.S文件中設(shè)置到r9寄存器中。配置CONFIG_SPL_STACK_R后gd會(huì)再次從ddr一個(gè)地址搬運(yùn)到另外一個(gè)地址。個(gè)人覺(jué)得uboot對(duì)棧和gd的處理實(shí)在過(guò)于復(fù)雜,本來(lái)可以至多兩次設(shè)置的事情,硬是搞成了3次,而且沒(méi)看到對(duì)不同處理器的兼容性由任何幫助。
  • board_init_r:這個(gè)函數(shù)也是完成一組函數(shù)的調(diào)用,最終根據(jù)環(huán)境變量的配置和終端的操作引導(dǎo)操作系統(tǒng)或啟動(dòng)hush(shell),至此uboot的c語(yǔ)言環(huán)境完全建立。

arch/arm/cpu/armv7/start.S---->arch/arm/lib/crt0.S---->common/board_r.c

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
    /*
     * Set up the new global data pointer. So far only x86 does this
     * here.
     * TODO(sjg@chromium.org): Consider doing this for all archs, or
     * dropping the new_gd parameter.
     */
    if (CONFIG_IS_ENABLED(X86_64) && !IS_ENABLED(CONFIG_EFI_APP))
        arch_setup_gd(new_gd);

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
    gd = new_gd;
#endif
    gd->flags &= ~GD_FLG_LOG_READY;

    if (IS_ENABLED(CONFIG_NEEDS_MANUAL_RELOC)) {
        for (int i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
            MANUAL_RELOC(init_sequence_r[i]);
    }

    if (initcall_run_list(init_sequence_r))
        hang();

    /* NOTREACHED - run_main_loop() does not return */
    hang();
}
  • 首先要重定位init_sequence_r中的函數(shù)指針,因?yàn)閕nit_sequence_r數(shù)組中的函數(shù)地址還是鏈接時(shí)候的地址,直接調(diào)用肯定出錯(cuò)所以要加上偏移地址,uboot中這種修正很多,不過(guò)這里又一個(gè)疑問(wèn)init_sequence_f中并沒(méi)有做這個(gè)操作,唯一的解釋是最后一次搬移代碼之前鏈接地址和運(yùn)行地址必須一致。
  • 順序調(diào)用init_sequence_r中的函數(shù)

這些函數(shù)本文就不具體分析了,放在另一片文章分析,函數(shù)最終調(diào)用run_main_loop進(jìn)入主循環(huán),自動(dòng)執(zhí)行環(huán)境變量bootcmd中的命令引導(dǎo)系統(tǒng),如果在規(guī)定的時(shí)間內(nèi)控制臺(tái)輸入回車(chē)則中斷引導(dǎo)進(jìn)入shell界面(hush)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容