目錄
0 環境
ARMV8,uboot 2020.10,rpi3平臺
1 移植框架
- board,不用說了,板級,uboot使用dts後,這塊程式碼應儘量簡化
- machine, SOC級,主要是一些外設
- ARCH, 如arm(包含armv7和armv8)
- CPU, 如armv8
各框架啟動時的關係:
配置相關檔案:
configs/xxx_defconfig: 平臺的預設配置,make ***_defconfig時會用到
include/conigs/***.h: 各平臺的一些額外CONFIG_配置項,寫在標頭檔案裡
3 執行流程
3.0 連結地址
Makefile中:
LDFLAGS_u-boot += -Ttext $(CONFIG_SYS_TEXT_BASE)
連結檔案
ENTRY(_start)
SECTIONS
{
. = 0x00000000; /*text段在lds檔案的偏移0*/
. = ALIGN(8);
.text :
{
*(.__image_copy_start)
arch/arm/cpu/armv8/start.o (.text*)
}
...
- CONFIG_SYS_TEXT_BASE: lds檔案中偏移為0,再結合-Ttext選項, 所以CONFIG_SYS_TEXT_BASE是重定位之前,最初的uboot起始地址
3.1 start.S, 入口
此階段的CONFIG_
已定義:
CONFIG_SYS_TEXT_BASE: uboot realocate之前的起始地址,程式碼裡 _TEXT_BASE = CONFIG_SYS_TEXT_BASE
未定義:
CONFIG_SYS_RESET_SCTRL, 設定 little endian, 關cache, 關MMU
CONFIG_ARMV8_SET_SMPEN, 若為EL3,使能SMPEN, EL2/1無動作
CONFIG_ARMV8_SPIN_TABLE和CONFIG_ARMV8_MULTIENTRY,像是兩種處理slave core的方式,不過rpi3應該只有core0執行uboot,所以都沒有定義
程式碼加註釋:arch/arm/cpu/armv8/start.S
/*************************************************************************
*
* Startup Code (reset vector)
*
*************************************************************************/
.globl _start
_start:
#if defined(CONFIG_LINUX_KERNEL_IMAGE_HEADER)
#include <asm/boot0-linux-kernel-header.h>
#elif 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.
*/
#include <asm/arch/boot0.h>
#else
b reset // 執行此分支
#endif
.align 3 # 2^3 = 8 Bytes對齊
/*
* 計算幾個變數的值,並寫入bin檔案裡,這些變數後面會用到
*/
.globl _TEXT_BASE
_TEXT_BASE:
.quad CONFIG_SYS_TEXT_BASE
/*
* These are defined in the linker script.
*/
.globl _end_ofs
_end_ofs:
.quad _end - _start
.globl _bss_start_ofs
_bss_start_ofs:
.quad __bss_start - _start
.globl _bss_end_ofs
_bss_end_ofs:
.quad __bss_end - _start
reset:
/* Allow the board to save important registers */
b save_boot_params // 一般為空,直接跳到save_boot_params_ret
.globl save_boot_params_ret
save_boot_params_ret:
#if CONFIG_POSITION_INDEPENDENT // 未定義
/*
* Fix .rela.dyn relocations. This allows U-Boot to be loaded to and
* executed at a different address than it was linked at.
*/
pie_fixup:
adr x0, _start /* x0 <- Runtime value of _start */
ldr x1, _TEXT_BASE /* x1 <- Linked value of _start */
sub x9, x0, x1 /* x9 <- Run-vs-link offset */
adr x2, __rel_dyn_start /* x2 <- Runtime &__rel_dyn_start */
adr x3, __rel_dyn_end /* x3 <- Runtime &__rel_dyn_end */
pie_fix_loop:
ldp x0, x1, [x2], #16 /* (x0, x1) <- (Link location, fixup) */
ldr x4, [x2], #8 /* x4 <- addend */
cmp w1, #1027 /* relative fixup? */
bne pie_skip_reloc
/* relative fix: store addend plus offset at dest location */
add x0, x0, x9
add x4, x4, x9
str x4, [x0]
pie_skip_reloc:
cmp x2, x3
b.lo pie_fix_loop
pie_fixup_done:
#endif
// 未定義此巨集,設定 little endian, 關cache、 關MMU
#ifdef CONFIG_SYS_RESET_SCTRL
bl reset_sctrl
#endif
#if defined(CONFIG_ARMV8_SPL_EXCEPTION_VECTORS) || !defined(CONFIG_SPL_BUILD)
.macro set_vbar, regname, reg
msr \regname, \reg
.endm
adr x0, vectors # vectors是中斷向量表,在arch/arm/cpu/armv8/exception.S中定義
#else
.macro set_vbar, regname, reg
.endm
#endif
/*
* Could be EL3/EL2/EL1, Initial State:
* Little Endian, MMU Disabled, i/dCache Disabled
*/
switch_el x1, 3f, 2f, 1f
3: set_vbar vbar_el3, x0
mrs x0, scr_el3
orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */
msr scr_el3, x0
msr cptr_el3, xzr /* Enable FP/SIMD */
#ifdef COUNTER_FREQUENCY
ldr x0, =COUNTER_FREQUENCY
msr cntfrq_el0, x0 /* Initialize CNTFRQ */
#endif
b 0f
2: set_vbar vbar_el2, x0
mov x0, #0x33ff
msr cptr_el2, x0 /* Enable FP/SIMD */
b 0f
1: set_vbar vbar_el1, x0 /* 設定中斷向量表,x0 = vectors */
mov x0, #3 << 20
msr cpacr_el1, x0 /* Enable FP/SIMD */
0:
isb
/*
* Enable SMPEN bit for coherency.
* This register is not architectural but at the moment
* this bit should be set for A53/A57/A72.
*/
#ifdef CONFIG_ARMV8_SET_SMPEN // 未定義
switch_el x1, 3f, 1f, 1f
3:
mrs x0, S3_1_c15_c2_1 /* cpuectlr_el1 */
orr x0, x0, #0x40
msr S3_1_c15_c2_1, x0
isb
1:
#endif
/* Apply ARM core specific erratas,處理一些已知的晶片bug */
bl apply_core_errata
/*
* Cache/BPB/TLB Invalidate
* i-cache is invalidated before enabled in icache_enable()
* tlb is invalidated before mmu is enabled in dcache_enable()
* d-cache is invalidated before enabled in dcache_enable()
*/
/* Processor specific initialization */
// 初始化臨時sp, 實現一個weak函式s_init,給各個平臺一個早期初始化的機會
// 不過重新實現s_init的廠商不多
bl lowlevel_init
#if defined(CONFIG_ARMV8_SPIN_TABLE) && !defined(CONFIG_SPL_BUILD)
branch_if_master x0, x1, master_cpu
b spin_table_secondary_jump
/* never return */
#elif defined(CONFIG_ARMV8_MULTIENTRY)
branch_if_master x0, x1, master_cpu
/*
* Slave CPUs
*/
slave_cpu:
wfe
ldr x1, =CPU_RELEASE_ADDR
ldr x0, [x1]
cbz x0, slave_cpu
br x0 /* branch to the given address */
#endif /* CONFIG_ARMV8_MULTIENTRY */
master_cpu:
bl _main /* 跳轉後不返回*/
3.2 __main
此階段的CONFIG_
已定義:
CONFIG_SYS_INIT_SP_ADDR: 呼叫board_init_f和relocate_code之前用的SP
CONFIG_SYS_MALLOC_F_LEN: 早期用於malloc的空間大小
未定義:
CONFIG_TPL_BUILD:TPL跟SPL差不多,是第三級程式載入器,也是一個精簡版的u-boot,很少用到,參見 doc/README.TPL
程式碼加註釋:arch/arm/lib/crt0_64.S
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
* 先分配SP地址(CONFIG_SYS_INIT_SP_ADDR), 在sp上分配大名鼎鼎的gd_t空間,gd_t是重要的全域性資料,地址儲存在X18裡,整個uboot都在使用
*/
#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
ldr x0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr x0, =(CONFIG_SPL_STACK)
#elif defined(CONFIG_INIT_SP_RELATIVE)
adr x0, __bss_start
add x0, x0, #CONFIG_SYS_INIT_SP_BSS_OFFSET
#else
ldr x0, =(CONFIG_SYS_INIT_SP_ADDR) // 進此分支, CONFIG_SYS_INIT_SP_ADDR一般在include/configs/****.h中定義
#endif
bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */
mov x0, sp
bl board_init_f_alloc_reserve // 從sp頂端分配早期malloc空間(受CONFIG_SYS_MALLOC_F_LEN控制),分配global_data(gd)空間
mov sp, x0
/* set up gd here, outside any C code, 把global_data的地址賦給X18,X18始終儲存著gd的地址 */
mov x18, x0
bl board_init_f_init_reserve // gd初始化為0, gd->malloc_base賦值(受CONFIG_SYS_MALLOC_F_LEN控制)
mov x0, #0
bl board_init_f // front, 前置初始化.執行函式指標陣列init_sequence_f裡的若干初始化函式,會把重定位時需要的若干資訊寫給gd
#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 x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp, 獲取重定位後的sp地址*/
bic sp, x0, #0xf /* 16-byte alignment for ABI compliance ,將重定位後的地址賦給sp */
ldr x18, [x18, #GD_NEW_GD] /* x18 <- gd->new_gd, 將重定位後的gd地址賦給X18 */
/* 先把relocation_return給lr,此時lr的地址是為重定位前的relocation_return
* 後面會給lr增加重定位的offset,保證重定位執行完畢返回lr時,能找到重定位後的relocation_return
*/
adr lr, relocation_return
#if CONFIG_POSITION_INDEPENDENT // 未定義
/* Add in link-vs-runtime offset */
adr x0, _start /* x0 <- Runtime value of _start */
ldr x9, _TEXT_BASE /* x9 <- Linked value of _start */
sub x9, x9, x0 /* x9 <- Run-vs-link offset */
add lr, lr, x9
#endif
/* Add in link-vs-relocation offset */
ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd->reloc_off */
add lr, lr, x9 /* new return address after relocation, 給lr加上重定位offset,此時lr已經指向重定位後的relocation_return */
ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd->relocaddr, 重定位後的起始地址 */
b relocate_code /* 重定位,拷貝text、data、rodata段, 拷貝並重寫rel_dyn段,具體原理單獨描述 */
relocation_return:
/*
* Set up final (full) environment
*/
bl c_runtime_cpu_setup /* still call old routine, 設定中斷向量表vbar_elX */
#endif /* !CONFIG_SPL_BUILD */
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
#if defined(CONFIG_SPL_BUILD)
bl spl_relocate_stack_gd /* may return NULL */
/* set up gd here, outside any C code, if new stack is returned */
cmp x0, #0
csel x18, x0, x18, ne
/*
* Perform 'sp = (x0 != NULL) ? x0 : sp' while working
* around the constraint that conditional moves can not
* have 'sp' as an operand
*/
mov x1, sp
cmp x0, #0
csel x0, x0, x1, ne
mov sp, x0
#endif
/*
* Clear BSS section
*/
ldr x0, =__bss_start /* this is auto-relocated! */
ldr x1, =__bss_end /* this is auto-relocated! */
clear_loop:
str xzr, [x0], #8
cmp x0, x1
b.lo clear_loop
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov x0, x18 /* gd_t */
ldr x1, [x18, #GD_RELOCADDR] /* dest_addr */
b board_init_r /* PC relative jump,重定位後的初始化和main loop, rear後置初始化 */
/* NOTREACHED - board_init_r() does not return */
#endif
ENDPROC(_main)
3.3 board_init_f()和init_sequence_f[]
此階段的CONFIG_選項:
已定義:
CONFIG_OF_CONTROL:使用device tree
CONFIG_OF_EMBED: dts與uboot整合到一起,一般都用此方式。makefile連線時會把dtb放到__dtb_dt_begin的位置
CONFIG_DM: 使能driver model,驅動模型,一套抽象且統一的驅動框架,複雜了,也為標準化
CONFIG_BAUDRATE, 預設串列埠波特率
CONFIG_SERIAL_PRESENT,存在串列埠
CONFIG_DM_VIDEO,
未定義:
CONFIG_OF_SEPARATE:dts與uboot分開,一般不用
CONFIG_TIMER_EARLY:儘早使用timer
CONFIG_BOARD_EARLY_INIT_F: 早期board初始化,給board提供一個hook
CONFIG_SYSRESET
CONFIG_DISPLAY_CPUINFO
CONFIG_DISPLAY_BOARDINFO
CONFIG_SYS_I2C
CONFIG_SYS_DRAM_TEST
CONFIG_SYS_MEM_TOP_HIDE: 讓uboot對上面的記憶體不可見,將ram_size -= CONFIG_SYS_MEM_TOP_HIDE
CONFIG_SYS_SDRAM_BASE: DDR的起始地址,沒啥意義,0
CONFIG_PRAM: protected RAM, 可以預留一段不被uboot使用
CONFIG_SYS_NONCACHED_MEMORY:分配non-cache區域
board_init_f()直接執行init_sequence_f[]裡的若干函式,黃底層為board移植時一般需要實現的。
- setup_mon_len(), gd->mon_len = 整個程式大小(text/data/bss等)
- fdtdec_setup(),gd->fdt_blob = __dtb_dt_begin,dts用
- initf_malloc(), 初始化gd的malloc相關成員,gd->malloc_limit和gd->malloc_ptr
- log_init(),log相關初始化,暫不關注
- initf_bootstage(),初始化bootstage功能,用於標記啟動到哪個階段了
- arch_cpu_init(),cpu級別的初始化操作,可以在需要的時候由CPU有關的code實現,weak實現啥也沒幹
- mach_cpu_init(), SoC/machine級別初始化,在mach_***裡可以覆蓋weak實現
- initf_dm(), driver model有關的初始化操作
- arch_cpu_init_dm(), cpu相關的dm初始化
- board_early_init_f(), 早期board級初始化,一般沒用
- timer_init(), timer初始化
- env_init,environment系統初始化
- init_baud_rate,gd->baudrate = env_get_ulong("baudrate", 10, CONFIG_BAUDRATE),優先從環境變數“baudrate”中獲取
- serial_init, 初始化串列埠,之後串列埠可用
- console_init_f
- display_options,顯示版本資訊等
- display_text_info,進一步顯示資訊
- checkcpu
- announce_dram_init,列印一句話
- dram_init, ddr初始化,如果其他boot已經初始化了,初始化gd->ram_size = ***
- testdram, dram測試
- 後面是重定位後的地址分配相關內容,分配空間的函式以reserve_***開頭,見下圖
uboot把自己重定位到高地址,linux啟動後在低地址 - dram_init_banksize(),設定:
gd->bd->bi_dram[bank].start
gd->bd->bi_dram[bank].size
gd->ram_size
gd->ram_base
- show_dram_config(), 顯示dram資訊
- setup_bdinfo(), bd->bi_memstart和bd->bi_memsize
- display_new_sp(), 顯示sp
- reloc_fdt(),reloc_bootstage(), reloc_bloblist(),如果定義了相關巨集,則把對應內容放到上面圖中對位的位置
- setup_reloc(), 計算gd->reloc_off,將gd拷貝到新地址
3.4 relocate
- relocate的歷史:可能是早期SOC,uboot可能在ROM中執行,效率低,所以要拷貝到RAM裡,雖然當前SOC, 一般UBOOT在一開始就在RAM裡了,但relocate這個環節被保留了。
- arch/arm/lib/relocate_64.S, 對text、data、rel_dyn做重定位處理,返回時,已經在新地址執行了。
- relocate是uboot比較難理解的部分,需要一些基礎知識。
1.每個func後面有label,PC+offset找labal;2.label中存全域性變數地址;3.func後面的label作為text段的一部分; 4.rel_dyn段再儲存所有label的地址;5.重定位時從rel_dyn段找lable地址,再修改lable裡的內容,即修改了全域性變數的地址 - rel_dyn裡儲存的資料格式(arm64)
-objdump -D u-boot > uboot_objdump.txt可得:
Disassembly of section .rela.dyn:
00000000000f6940 <__image_copy_end>:
...
f6970: 00082ad0 .inst 0x00082ad0 ; undefined
f6974: 00000000 udf #0 // 8B, lable 地址
f6978: 00000403 udf #1027
f697c: 00000000 udf #0 // 8B, 標記
f6980: 00102070 .inst 0x00102070 ; undefined
f6984: 00000000 udf #0 // 8B, lable中儲存的資料,即地址0x00082ad0裡儲存的值(全域性變數地址)
...
f69a0: 000d71b8 .inst 0x000d71b8 ; undefined
f69a4: 00000000 udf #0
f69a8: 00000403 udf #1027
f69ac: 00000000 udf #0
f69b0: 000e85e6 .inst 0x000e85e6 ; undefined
f69b4: 00000000 udf #0
rel_dyn的格式為:
long lable_addr; label地址
long flag = 1027;
long val_in_lable; labal裡存的內容,即全域性變數地址
所以rel_dyn重定位就很簡單了, [lable_addr + rel_off] = val_in_lable + rel_off,, 段.rela.dyn本身沒有拷貝,重定位後就不用了。
原始碼分析:
/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
* x0 holds the destination address.
*/
ENTRY(relocate_code)
stp x29, x30, [sp, #-32]! /* create a stack frame */
mov x29, sp
str x0, [sp, #16]
/*
* Copy u-boot from flash to RAM
* __image_copy_start = _TEXT_BASE = CONFIG_SYS_TEXT_BASE = 0x80000, 初始載入地址
*/
adrp x1, __image_copy_start /* x1 <- address bits [31:12] */
add x1, x1, :lo12:__image_copy_start/* x1 <- address bits [11:00] */
subs x9, x0, x1 /* x9 <- Run to copy offset, X9 = 重定位前後地址的offset */
b.eq relocate_done /* skip relocation */
/*
* Don't ldr x1, __image_copy_start here, since if the code is already
* running at an address other than it was linked to, that instruction
* will load the relocated value of __image_copy_start. To
* correctly apply relocations, we need to know the linked value.
*
* Linked &__image_copy_start, which we know was at
* CONFIG_SYS_TEXT_BASE, which is stored in _TEXT_BASE, as a non-
* relocated value, since it isn't a symbol reference.
*/
ldr x1, _TEXT_BASE /* x1 <- Linked &__image_copy_start */
subs x9, x0, x1 /* x9 <- Link to copy offset, X9 = 重定位前後地址的offset */
adrp x1, __image_copy_start /* x1 <- address bits [31:12] */
add x1, x1, :lo12:__image_copy_start/* x1 <- address bits [11:00] */
adrp x2, __image_copy_end /* x2 <- address bits [31:12] */
add x2, x2, :lo12:__image_copy_end /* x2 <- address bits [11:00] */
copy_loop:
ldp x10, x11, [x1], #16 /* copy from source address [x1] */
stp x10, x11, [x0], #16 /* copy to target address [x0] */
cmp x1, x2 /* until source end address [x2] */
b.lo copy_loop
str x0, [sp, #24]
/* 上面程式碼完成__image_copy_start、__image_copy_end之間的程式碼拷貝,包括text和data段 */
/*
* Fix .rela.dyn relocations
* rela.dyn裡1個LABEL結構是:
* 8 Bytes 地址
* 8 Bytes 標記
* 8 Bytes addend
*/
adrp x2, __rel_dyn_start /* x2 <- address bits [31:12] */
add x2, x2, :lo12:__rel_dyn_start /* x2 <- address bits [11:00] */
adrp x3, __rel_dyn_end /* x3 <- address bits [31:12] */
add x3, x3, :lo12:__rel_dyn_end /* x3 <- address bits [11:00] */
fixloop:
ldp x0, x1, [x2], #16 /* (x0,x1) <- (SRC location, fixup), X0是label地址,X1存標記 */
ldr x4, [x2], #8 /* x4 <- label地址裡的值 */
and x1, x1, #0xffffffff
cmp x1, #R_AARCH64_RELATIVE
bne fixnext
/* relative fix: store addend plus offset at dest location */
add x0, x0, x9
add x4, x4, x9
str x4, [x0] /* label值 + offset = lable裡的值(全域性變數地址) +offset */
fixnext:
cmp x2, x3
b.lo fixloop
relocate_done:
switch_el x1, 3f, 2f, 1f
bl hang
3: mrs x0, sctlr_el3
b 0f
2: mrs x0, sctlr_el2
b 0f
1: mrs x0, sctlr_el1
0: tbz w0, #2, 5f /* skip flushing cache if disabled */
tbz w0, #12, 4f /* skip invalidating i-cache if disabled */
ic iallu /* i-cache invalidate all */
isb sy
4: ldp x0, x1, [sp, #16]
bl __asm_flush_dcache_range
bl __asm_flush_l3_dcache
5: ldp x29, x30, [sp],#32
ret
ENDPROC(relocate_code)
3.5 board_init_r()
3.5.1 init_sequence_r
relocate之後的初始化及主函式,執行init_sequence_r[]裡的函式指標。
- initr_trace,初始化並使能u-boot的tracing system,涉及的配置項有CONFIG_TRACE。
- initr_reloc,設定relocation完成的標誌。
- initr_caches,使能dcache、icache等,涉及的配置項有CONFIG_ARM。
- initr_reloc_global_data(), 重定位gd相關內容,gd->env_addr,gd->fdt_blob,efi相關初始化
- initr_malloc,malloc有關的初始化
- log_init, log相關初始化
- initr_dm, relocate之後,重新初始化DM,涉及的配置項有CONFIG_DM
- board_init,具體的板級初始化,需要由board程式碼根據需要實現,涉及的配置項有CONFIG_ARM。
- set_cpu_clk_info,Initialize clock framework,涉及的配置項有CONFIG_CLOCKS。
- efi_memory_init, efi相關初始化
- initr_binman() ?
- initr_dm_devices, CONFIG_TIMER_EARLY決定是否初始化timer
- stdio_init_tables
- serial_initialize
- initr_announce, 列印
- board_early_init_r, CONFIG_BOARD_EARLY_INIT_R控制是否呼叫,板級實現
- arch_early_init_r,由arch程式碼實現,涉及的配置項有CONFIG_ARCH_EARLY_INIT_R。
- power_init_board,板級的power init程式碼,由板級程式碼實現,例如hold住power。
- initr_flash,initr_nand,initr_onenand,initr_mmc,根據各巨集呼叫各儲存裝置初始化
- initr_env, 環境變數相關初始化
- initr_secondary_cpu,其他core初始化
- stdio_add_devices,各種輸入輸出裝置的初始化,如LCD driver等
- initr_jumptable ?
- console_init_r
- arch_misc_init, 受CONFIG_ARCH_MISC_INIT控制的arch雜項
- misc_init_r, 受CONFIG_MISC_INIT_R控制
- interrupt_init, 使能中斷
- initr_status_led,狀態指示LED的初始化,涉及的配置項有CONFIG_STATUS_LED、STATUS_LED_BOOT。
- initr_ethaddr,Ethernet的初始化,涉及的配置項有CONFIG_CMD_NET。
- board_late_init, 由板級程式碼實現,涉及的配置項有CONFIG_BOARD_LATE_INIT
- initr_net。網路初始化,CONFIG_CMD_NET
- run_main_loop/main_loop,主迴圈
3.5.2 main_loop
參考
http://www.wowotech.net/u-boot/boot_flow_1.html
https://blog.csdn.net/skyflying2012/article/details/37660265
https://blog.csdn.net/ooonebook/article/details/53047992