點(diǎn)擊下方閱讀原文可訪問文中超鏈接
首先分析一下u-boot的鏈接腳本,這樣就能夠知道u-boot本身的大體組成及分布,如果想更詳細(xì)的了解,可以查看生成的u-boot.map文件,這個文件就能看出u-boot各個段的排布。在上一篇文章中,已經(jīng)完成了u-boot的編譯,在u-boot根目錄下可以看到生成了一個u-boot.lds文件,這個文件就是u-boot的鏈接腳本,它是由arch\arm\cpu\u-boot.lds文件經(jīng)過處理后得到的,其內(nèi)容如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
/*整個u-boot的入口*/
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
/*這個和以前舊的u-boot版本不一樣,這個現(xiàn)在被定義在arch/arm/lib/sections.c文件中,
對應(yīng)頭文件為include/asm-generic/sections.h,
用的是零長數(shù)組來實(shí)現(xiàn),不占內(nèi)存空間,相當(dāng)于只是放了一個符號,u-boot重定位時(shí)就是從這個地址開始拷貝*/
*(.__image_copy_start)
/*存放向量表的段,位于arch/arm/lib/vectors.S文件*/
*(.vectors)
/*這個就相當(dāng)于是u-boot代碼執(zhí)行的開始了(但第一句執(zhí)行的代碼不在這兒)*/
arch/arm/cpu/armv7/start.o (.text*)
}
/*表示efi_runtime段的開始*/
.__efi_runtime_start : {
*(.__efi_runtime_start)
}
.efi_runtime : {
*(.text.efi_runtime*)
*(.rodata.efi_runtime*)
*(.data.efi_runtime*)
}
/*表示efi_runtime段的結(jié)束*/
.__efi_runtime_stop : {
*(.__efi_runtime_stop)
}
.text_rest :
{
/*u-boot代碼段*/
*(.text*)
}
. = ALIGN(4);
/*u-boot只讀數(shù)據(jù)段*/
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
/*u-boot數(shù)據(jù)段*/
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
/*u-boot自定義段,u-boot命令、硬件驅(qū)動、環(huán)境變量相關(guān)的一些東西等放在此段(*號是通配符),具體的段可以查看u-boot.map文件*/
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.efi_runtime_rel_start :
{
*(.__efi_runtime_rel_start)
}
.efi_runtime_rel : {
*(.rel*.efi_runtime)
*(.rel*.efi_runtime.*)
}
.efi_runtime_rel_stop :
{
*(.__efi_runtime_rel_stop)
}
. = ALIGN(4);
.image_copy_end :
{
/*u-boot重定位拷貝結(jié)束的地址*/
*(.__image_copy_end)
}
.rel_dyn_start :
{
/*動態(tài)符號表的開始*/
*(.__rel_dyn_start)
}
.rel.dyn : {
/*放置了動態(tài)符號表,也是重定位的時(shí)候需要的*/
*(.rel*)
}
.rel_dyn_end :
{
/*動態(tài)符號表的結(jié)束*/
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
/*整個u-boot bin文件的結(jié)束*/
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
/*MMU頁表相關(guān)*/
*(.mmutable)
}
/*bss段,建立C語言運(yùn)行環(huán)境的時(shí)候需要*/
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
/*后面是一些其它的段*/
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
從其內(nèi)容可以得知整個u-boot程序的入口為_start這個標(biāo)號,在以前的u-boot中,入口都是在start.S文件中,后面新增了一個vectors.S文件,現(xiàn)在_start標(biāo)號就位于此文件中,所以先從此文件開始分析。
arch/arm/lib/vectors.S
/*向量表的定義*/
.macro ARM_VECTORS
/*未定義*/
#ifdef CONFIG_ARCH_K3
ldr pc, _reset
#else
/*從這兒可以看到,芯片上電后立馬就會執(zhí)行復(fù)位*/
b reset
#endif
/*這里只是相當(dāng)于只是一個函數(shù)指針,真正的實(shí)現(xiàn)在本文件的后面*/
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
.endm
/*從這兒可以看出,向量表被放到了專門的.vectors段中*/
.section ".vectors", "ax"
/*未定義,這是給有些芯片用來在u-boot頭部定義一些數(shù)據(jù)使用的*/
#if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
/*
省略注釋
*/
#include <asm/arch/boot0.h>
#else
/*
省略注釋
*/
/*這就是整個u-boot的入口*/
_start:
/*未定義*/
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
/*放置的向量表,定義在此文件的上面,和C語言的宏定義一樣*/
ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
下面的內(nèi)容相當(dāng)于給異常處理的函數(shù)指針綁定一個函數(shù)實(shí)現(xiàn)的實(shí)體:
/*未定義*/
#ifdef CONFIG_ARCH_K3
_reset: .word reset
#endif
/*這里定義了異常產(chǎn)生后,該去哪兒執(zhí)行異常處理的過程*/
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
以undefined_instruction為例,產(chǎn)生異常后,先執(zhí)行l(wèi)dr pc, _undefined_instruction,它會將undefined_instruction這個過程(函數(shù))地址加載到pc指針中,這樣就相當(dāng)于調(diào)用undefined_instruction這個函數(shù)了:
/*32字節(jié)對齊,2的5次方*/
.align 5
undefined_instruction:
/*下面這些過程都是在本文件實(shí)現(xiàn)的,就不詳細(xì)介紹了*/
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
向量表分析了,接著執(zhí)行了b reset后,下面就進(jìn)入arch/arm/cpu/armv7/start.S文件了:
reset:
/* Allow the board to save important registers */
/*什么也沒做*/
b save_boot_params
save_boot_params_ret:
/*未定義*/
#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
要看懂后面的內(nèi)容,先得了解armv7架構(gòu)的一些內(nèi)容,需要參考arm的相關(guān)手冊(ARMv7-A -R Architecture Reference Manual.pdf),先看一下cpsr寄存器各個位的定義:

圖1
其中與模式相關(guān)的位是bit[4:0],詳細(xì)的定義參見下圖,可以看到armv7總共有九種模式:

圖2
再來看一下關(guān)于中斷和異常相關(guān)的位描述:

圖3
現(xiàn)在繼續(xù)看后面的程序就輕松很多了,下面這段內(nèi)容就是關(guān)閉中斷,并且設(shè)置CPU進(jìn)入SVC32模式,關(guān)于其過程這里就不推導(dǎo)了,對照上面的圖,很簡單就能夠推導(dǎo)出來:
/*
* 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
/*看是否處于HYP模式*/
teq r1, #0x1a @ test for HYP mode
/*如果未在HYP模式就設(shè)置為SVC模式*/
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
繼續(xù)往后看,是協(xié)處理器相關(guān)的內(nèi)容,還是一樣,先從手冊找到其相關(guān)的描述,建議先了解一下關(guān)于協(xié)處理器指令的使用方法,再理解這段代碼就會輕松很多:

圖4 關(guān)于SCTLR寄存器的位定義

圖5 關(guān)于V位的描述
下面看代碼:
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* 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
從cp15協(xié)處理器SCTLR寄存器V位的描述來看,設(shè)置為0的話,向量表基地址就是0x00000000,并且向量表基地址是可以被重映射的,而設(shè)置為1的話,向量表基地址就是0xffff0000,不能被重映射,因?yàn)楹竺鎢-boot會被拷貝到DRAM中運(yùn)行,如果向量表還在內(nèi)部IRAM的話,如果我們想使用中斷,那就訪問不到了,所以得將向量表基地址重映射,這樣產(chǎn)生中斷后才能正確的被響應(yīng),因此這里將V位設(shè)置為0允許重映射,然后將向量表基地址設(shè)置為_start這個標(biāo)號所在的地址,從前面的內(nèi)容我們知道,_start標(biāo)號開始的地方就是放置的中斷向量表。
接下來cpu_init_cp15函數(shù)又是對協(xié)處理器的一堆操作,就不詳細(xì)講了,想繼續(xù)深入分析的可以參考ARMv7-A -R Architecture Reference Manual.pdf文檔,直接點(diǎn)擊鏈接就可下載,下面簡要的注釋一下相關(guān)的內(nèi)容:
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
/*TLB無效*/
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
/*指令緩存無效*/
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
/*分支預(yù)測無效*/
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
/*數(shù)據(jù)和指令同步屏障*/
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
/*設(shè)置向量表基地址,前面已經(jīng)設(shè)置過*/
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
/*數(shù)據(jù)和緩存一致性失能,對齊錯誤檢查失能,MMU失能*/
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
/*對齊錯誤檢查使能*/
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
/*程序流預(yù)測使能*/
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
/*未定義*/
#if CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
/*指令緩存使能*/
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endif
mcr p15, 0, r0, c1, c0, 0
/*勘誤相關(guān)的都沒有定義*/
/*
省略勘誤相關(guān)的內(nèi)容
*/
mov r5, lr @ Store my Caller
/*讀取芯片的一些信息,如版本號、架構(gòu)等,但實(shí)際上沒用到*/
mrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (MIDR)
mov r3, r1, lsr #20 @ get variant field
and r3, r3, #0xf @ r3 has CPU variant
and r4, r1, #0xf @ r4 has CPU revision
mov r2, r3, lsl #4 @ shift variant field for combined value
orr r2, r4, r2 @ r2 has combined CPU variant + revision
/* Early stack for ERRATA that needs into call C code */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
/*設(shè)置一個臨時(shí)的棧用于下面的勘誤相關(guān)內(nèi)容的執(zhí)行,這個地址三星設(shè)置在了DRAM里面,
要是現(xiàn)在我們還沒有初始化DRAM,那執(zhí)行C函數(shù)的時(shí)候,程序肯定就跑飛了,好在這部分內(nèi)容都沒有執(zhí)行*/
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
/*
省略勘誤相關(guān)的內(nèi)容
*/
/*這里就返回了*/
mov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)
到了這里,關(guān)于arm內(nèi)核相關(guān)的東西就結(jié)束了,后面就是SOC相關(guān)的內(nèi)容了。
歡迎掃碼關(guān)注我的微信公眾號
