第一階段 start.S
首先我們可以在u-boot.lds中看到ENTRY(_start)
,即指定了入口_start
,_start
也就是整個start.S
的最開始;
1. reset
在arch\arm\cpu\armv8\hi3559av100
中的start.S
注意x30在ARMV8中代表lr寄存器
reset:
/* * Could be EL3/EL2/EL1, Initial State: * Little Endian, MMU Disabled, i/dCache Disabled */ adr x0, vectors switch_el x1, 3f, 2f, 1f 3: msr 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: msr vbar_el2, x0 mov x0, #0x33ff msr cptr_el2, x0 /* Enable FP/SIMD */ b 0f 1: msr vbar_el1, x0 mov x0, #3 << 20 msr cpacr_el1, x0 /* Enable FP/SIMD */ 0: /* * 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() */ /* * read system register REG_SC_GEN2 * check if ziju flag */ ldr x0, =SYS_CTRL_REG_BASE ldr w1, [x0, #REG_SC_GEN2] ldr w2, =0x7a696a75 /* magic for "ziju" */ cmp w1, w2 bne normal_start_flow mov x1, sp /* save sp */ str w1, [x0, #REG_SC_GEN2] /* clear ziju flag */
adr x0, vectors
,其中的vectors代表了異常向量表
主要做了如下事情:
1)reset SCTRL寄存器
具體可參考reset_sctrl函數,由CONFIG_SYS_RESET_SCTRL控制,一般不需要打開。該配置項的解釋如下:
Reset the SCTRL register at the very beginning of execution to avoid interference from stale mappings set up by early firmware/loaders/etc. http://lists.denx.de/pipermail/u-boot/2015-April/211147.html
2)根據當前的EL級別,配置中斷向量、MMU、Endian、i/d Cache等。
3)配置ARM的勘誤表
具體可參考apply_core_errata函數,由CONFIG_ARM_ERRATA_XXX控制,在項目的初期,可以不打開,后續根據實際情況打開)。
2. normal_start_flow流程
這里是正常啟動流程
normal_start_flow:
/* set stack for C code */ ldr x0, =(CONFIG_SYS_INIT_SP_ADDR) bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */ bl uart_early_init adr x0, Str_SystemSartup bl uart_early_puts ldr x0, =0x1202008c ldr w0, [x0] bl uart_early_put_hex /* enable I-Cache */ bl icache_enable
1)設置代碼的堆棧 2.)跳轉到uart_early_init
因為uart_early_init是全局的偽匯編指令(在uart.S中定義),所以在start.S中也可以使用到
3)聲明一個字符串Str_SystemSartup 4)使能icache
因為bne normal_start_flow
是不跳轉回來的,所以會繼續向下執行
3. running_addr_check流程
判斷是否進入not_ddr_init中,不需要DDR初始化,直接copy到DDR中
check_boot_mode:
ldr x0, =SYS_CTRL_REG_BASE ldr w0, [x0, #REG_SYSSTAT] lsr w6, w0, #4 and w6, w6, #0x3 cmp w6, #BOOT_FROM_EMMC //判斷是不是EMMC啟動 bne ufs_boot //如果不是,則進入ufs_boot
4. ziju_flow流程
自舉模式從這里我可以推斷出,芯片的啟動分為兩種,一種是自舉模式也就是本地的spiflash或nand或emmc等啟動,另一種就是pcie啟動模式。不同啟動模式對應不同的啟動流程。但不同啟動模式代碼是相互交織的,需要分清楚!
1) 初始化PLL和DDRC控制器和管腳復用情況。
/* init PLL/DDRC/pin mux/... */
ldr r0, _blank_zone_start ldr r1, _TEXT_BASE sub r0, r0, r1 ldr r1, =RAM_START_ADRS add r0, r0, r1 mov r1, #0x0 /* flags: 0->normal 1->pm */ bl init_registers /* init PLL/DDRC/... */
bl init_registers
這個函數是初始化一些寄存器,這些寄存器分了很多,包括中斷、網絡、哈希功能形式的寄存器,初始化的意思就是給一個值,但這值一般沒什么意義,具體的寄存器,后面會再進行配置!
2) start_ddr_training
/* DDR training:DR布線,完全按等長約束就沒有ddr training的說法。 當布線去掉等長約束或放寬約束條件,就要做ddr training,以保證時序的完整性,使信號的建立&保持時間窗口一致。ddr training是調整Addr/Cmd信號對CLK,DQ信號對DQS的延時。由於沒做等長約束,信號有長,有短,就會導致信號有快,慢之差(信號在1000mil走線耗時約160~180ps,相對FR-4的板材),ddr training就是找到一套參數,使信號的建立與保持時間充足。並保存且寫到配置中。*/
3) pcie_slave_boot
5. jump_to_ddr
自舉模式省略了一些PCIE判斷的情況的解釋,我也沒怎么看懂
jump_to_ddr:
adr x0, _start_armboot ldr x30,[x0] ret
開始進入跳轉到C語言階段
總結
- 關cache,關mmu,SVC模式
- 檢測是不是自舉模式還是pcie啟動,也包括是冷啟動還是熱啟動
- 串口初始化
- DDR初始化和DDR training
- 正常啟動時,會檢測啟動方式,對代碼進行相應的拷貝,重定位
- 設置堆棧
- 清bss段
- 跳轉到第二階段,即C語言階段
1. 第二個start.S
從start_armboot
開始,在startup.c
中有包含#include <config.h>
在config.h中:
/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/hisilicon/hi3559av100 #include <config_defaults.h> #include <config_uncmd_spl.h> #include <configs/hi3559av100.h> #include <asm/config.h> #include <config_fallbacks.h>
在hi3559av100.h
中:
#define CONFIG_SYS_TEXT_BASE 0x48800000
在hi3559av100.h
中,看到了CONFIG_SYS_TEXT_BASE
的宏
/* CONFIG_SYS_TEXT_BASE needs to align with where ATF loads bl33.bin */
#define CONFIG_SYS_TEXT_BASE 0x48800000
查看u-boot.map在這里又來到了上一層的start.S中來,所以可以知道這兩個是由兩個文件組成的,一個是u-boot.bin和reg_info.bin,就是說兩個不同的start.S的流程來合成一個最終的u-boot-hi3559av100.bin
這個便是正常的流程了:
本文將結合u-boot的“board—>machine—>arch—>cpu”框架,介紹u-boot中平台相關部分的啟動流程。並通過對啟動流程的簡單分析,掌握u-boot移植的基本方法。
2. 多平台架構
這些問題的本質,是軟件工程中的抽象和封裝,以最簡潔、最高效的方式,實現盡可能多的功能。u-boot作為一個跨平台、跨設備的bootloader,同樣會面臨這些問題。它的解決方案,就是“board—>machine—>arch—>cpu”框架,如下:

基於圖片1的架構,u-boot和平台有關的初始化流程,顯得比較直觀、清晰:
1)u-boot啟動后,會先執行CPU(如armv8)的初始化代碼。 2)CPU相關的代碼,會調用ARCH的公共代碼(如arch/arm)。 3)ARCH的公共代碼,在適當的時候,調用board有關的接口。u-boot的功能邏輯,大多是由common代碼實現,部分和平台有關的部分,則由公共代碼聲明,由board代碼實現。 4)board代碼在需要的時候,會調用machine(arch/arm/mach-xxx)提供的接口,實現特定的功能。因此machine的定位是提供一些基礎的代碼支持,不會直接參與到u-boot的功能邏輯中。
3. 平台相關部分的啟動流程分析
本文先不涉及u-boot和平台相關的Kconfig/Makefile部分,以ARM64為例,假定u-boot首先從“arch/arm/cpu/armv8/start.S”的_start接口開始執行。因此我們從_start開始分析。
3.1 _start
_start是u-boot啟動后的第一個執行地址,對armv8來說,它只是簡單的跳轉到reset處執行,如下:
.globl _start
_start: b reset
3.2 reset
reset:
/* Allow the board to save important registers */ b save_boot_params .globl save_boot_params_ret save_boot_params_ret: #ifdef CONFIG_SYS_RESET_SCTRL bl reset_sctrl #endif /* * Could be EL3/EL2/EL1, Initial State: * Little Endian, MMU Disabled, i/dCache Disabled */ adr x0, vectors switch_el x1, 3f, 2f, 1f 3: msr 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: msr vbar_el2, x0 mov x0, #0x33ff msr cptr_el2, x0 /* Enable FP/SIMD */ b 0f 1: msr vbar_el1, x0 mov x0, #3 << 20 msr cpacr_el1, x0 /* Enable FP/SIMD */ 0: /* Apply ARM core specific erratas */ 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 */ bl lowlevel_init
1)reset SCTRL寄存器
具體可參考reset_sctrl函數,由CONFIG_SYS_RESET_SCTRL控制,一般不需要打開。該配置項的解釋如下:
Reset the SCTRL register at the very beginning of execution to avoid interference from stale mappings set up by early firmware/loaders/etc. http://lists.denx.de/pipermail/u-boot/2015-April/211147.html
2)根據當前的EL級別,配置中斷向量、MMU、Endian、i/d Cache等。
3)配置ARM的勘誤表
具體可參考apply_core_errata函數,由CONFIG_ARM_ERRATA_XXX控制,在項目的初期,可以不打開,后續根據實際情況打開)。
就是ARM有一些bug,但可以通過軟件的方法繞過去,由u-boot的代碼注釋可知,應該只有Cortex-A57才有。具體什么bug,我也沒有去研究
4)調用lowlevel_init的功能解釋如下(具體可參考u-boot的readme文檔):
- purpose: essential init to permit execution to reach board_init_f()
- no global_data or BSS - there is no stack (ARMv7 may have one but it will soon be removed) - must not set up SDRAM or use console - must only do the bare minimum to allow execution to continue to board_init_f() - this is almost never needed - return normally from this function
海思的和原生uboot代碼的start.S其實就是增加以下內容

5)如果是多CPU的場景,處理其它的CPU的boot
多CPU功能由CONFIG_ARMV8_MULTIENTRY控制,不需要打開。
6)跳轉到arm公共的_main中執行
ARM64平台的_main位於crt0_64.S文件中,具體請參考下面的描述。
3.3 _main
crt0是C-runtime Startup Code的簡稱,意思就是運行C代碼之前的准備工作。關於_main函數,crt0_64.S中有非常詳細的注釋(這一點要給u-boot點100個贊!),大家可以參考。該函數的定義如下:
ENTRY(_main) /* * Set up initial C runtime environment and call board_init_f(0). */ #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr x0, =(CONFIG_SPL_STACK) #else ldr x0, =(CONFIG_SYS_INIT_SP_ADDR) #endif bic sp, x0, #0xf/* 16-byte alignment for ABI compliance */ mov x0, sp bl board_init_f_alloc_reserve mov sp, x0 /* set up gd here, outside any C code */ mov x18, x0 bl board_init_f_init_reserve mov x0, #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 x0, [x18, #GD_START_ADDR_SP]/* x0 <- gd-="">start_addr_sp */ bic sp, x0, #0xf/* 16-byte alignment for ABI compliance */ ldr x18, [x18, #GD_BD] /* x18 <- gd-="">bd */ sub x18, x18, #GD_SIZE /* new GD is below bd */ adr lr, relocation_return ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd-="">reloc_off */ add lr, lr, x9/* new return address after relocation */ ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd-="">relocaddr */ b relocate_code relocation_return: /* * Set up final (full) environment */ bl c_runtime_cpu_setup /* still call old routine */ /* TODO: For SPL, call spl_relocate_stack_gd() to alloc stack relocation */ /* * Clear BSS section */ ldr x0, =__bss_start /* this is auto-relocated! */ ldr x1, =__bss_end /* this is auto-relocated! */ mov x2, #0 clear_loop: str x2, [x0] add x0, 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 */ /* NOTREACHED - board_init_r() does not return */ #endif /* !CONFIG_SPL_BUILD */ ENDPROC(_main)
功能可總結為(大部分翻譯自crt0_64.S中的注釋):
1)設置C代碼的運行環境,為調用board_init_f接口做准備。包括:
a)設置堆棧(C代碼的函數調用,堆棧是必須的)。如果當前的編譯是SPL(由CONFIG_SPL_BUILD定義),可單獨定義堆棧基址(CONFIG_SPL_STACK),否則,通過CONFIG_SYS_INIT_SP_ADDR定義堆棧基址。
b)調用board_init_f_alloc_reserve接口,從堆棧開始的地方,為u-boot中大名鼎鼎的GD ('global data') 數據結構,分配空間。 c)調用board_init_f_init_reserve接口,對GD進行初始化。
2)調用board_init_f函數,完成一些前期的初始化工作,例如:
a)點亮一個Debug用的LED燈,表示u-boot已經活了。
b)初始化DRAM、DDR等system范圍的RAM等。
c)計算后續代碼需要使用的一些參數,包括relocation destination、the future stack、the future GD location等。
注5:關於u-boot的relocation操作,后續會有專門的文章介紹。
3)如果當前是SPL(由CONFIG_SPL_BUILD控制),則_main函數結束,直接返回。如果是正常的u-boot,則繼續執行后續的動作。
4)根據board_init_f指定的參數,執行u-boot的relocation操作。
5)清除BBS段。
6)調用board_init_r函數,執行后續的初始化操作(已經不再本文的討論范圍了,具體請參考后續的分析文章)。
4. 總結
4.1 SPL功能
SPL是Secondary Program Loader的簡稱,之所以稱作secondary,是相對於ROM code來說的。SPL是u-boot中獨立的一個代碼分支,由CONFIG_SPL_BUILD配置項控制,是為了在正常的u-boot image之外,提供一個獨立的、小size的SPL image,通常用於那些SRAM比較小(或者其它限制)、無法直接裝載並運行整個u-boot的平台。
如果使用了SPL功能,u-boot的啟動流程通常是:
ROM code加載SPL並運行;
SPL進行必要的初始化之后,加載u-boot並運行;
u-boot進行后續的操作。
因此,如果使用SPL功能,需要盡可能的減少SPL的代碼量,以減小它的size。
4.2 配置項總結
經過第3章的流程分析,我們可以總結出和“平台相關部分的啟動流程”有關的配置項,記錄如下:
CONFIG_SYS_RESET_SCTRL,控制是否在啟動的時候reset SCTRL寄存器,一般不需要打開;
CONFIG_ARM_ERRATA_XXX,控制ARM core的勘誤信息,一般不需要打開;
CONFIG_GICV2、CONFIG_GICV3,控制GIC的版本,用到的時候再說明;
CONFIG_ARMV8_MULTIENTRY,控制是否在u-boot中使用多CPU,一般不需要;
CONFIG_SPL_BUILD,是否是能SPL的編譯,需要的話可以打開;
CONFIG_SPL_STACK,如果配置了CONFIG_SPL_BUILD,是否為SPL image配置單獨的stack(SP基址),如果需要,通過
1. 前言
介紹u-boot啟動流程中和具體版型(board)有關的部分,也即board_init_f/board_init_r所代表的、board有關初始化過程。該過程將持續u-boot的整個生命周期,直到main_loop(即傳說中的命令行)。
注1:由於u-boot后初始化過程,基本上涉及到了所有的軟件模塊,因此本文不能一一分析,基本原則就是撿看着順眼的、熟的下手了~。
2. Generic Board
u-boot的基本策略,就是聲明一系列的API(如low_level_init、board_init_f、board_init_r等等),並在u-boot的核心邏輯中調用它們。平台的移植和開發者,所需要做的,就是根據實際情況,實現它們。
與此同時,為了減少開發的工作量,u-boot為大部分API提供了通用實現(一般通過CONFIG配置項或者若定義去控制是否編譯)。以board_init_f和board_init_r兩個板級的初始化接口為例,u-boot分別在common/board_f.c和common/board_r.c兩個文件中提供了通用實現。查看common/Makefile可知:
# boards
obj-y += board_f.o obj-y += board_r.o
可以看到這兩個文件都會調用到;
3. _main
_main函數位於crt0_64.S
中:
1)設置初始的堆棧
基址由CONFIG_SYS_INIT_SP_ADDR定義。
2)分配global data所需的空間
將堆棧16 bits對齊之后,調用board_init_f_alloc_reserve接口,從堆棧開始的地方,為u-boot的global data(struct global_data)分配空間。如下:
/* common/init/board_init.c */
ulong board_init_f_alloc_reserve(ulong top) { /* Reserve early malloc arena */ #if defined(CONFIG_SYS_MALLOC_F) top -= CONFIG_SYS_MALLOC_F_LEN; #endif /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */ top = rounddown(top-sizeof(struct global_data), 16); return top; }
需要注意的是,如果定義了CONFIG_SYS_MALLOC_F_LEN,則會先預留出early malloc所需的空間。
3)初始化global data
global data的空間分配后,調用board_init_f_init_reserve,初始化global data。所謂的初始化,無非就是一些清零操作,不過有幾個地方需要注意:
1)如果不是ARM平台(!CONFIG_ARM),則可以調用arch_setup_gd接口,進行arch級別的設置。當然,前提是,對應的arch應該實現這個接口。 2)如果定義了CONFIG_SYS_MALLOC_F,則會初始化gd->malloc_base。
4)執行前置的(front)初始化操作
調用board_init_f接口,執行前置的初始化操作,會再后面的章節詳細說明。
5)執行relocation操作,后面會詳細說明。
6)清除BBS段
7)執行后置的(rear)初始化操作
調用board_init_r接口,執行前置的初始化操作,會再后面的章節詳細說明。
4. global data介紹以及背后的思考
4.1 背景知識
我們看看board_init_r函數:
在uboot的common文件下有一個board_r.c文件: u-boot是一個bootloader,有些情況下,它可能位於系統的只讀存儲器(ROM或者flash)中,並從那里開始執行。 因此,這種情況下,在u-boot執行的前期(在將自己copy到可讀寫的存儲器之前),它所在的存儲空間,是不可寫的,這會有兩個問題: 1)堆棧無法使用,無法執行函數調用,也即C環境不可用。 2)沒有data段(或者正確初始化的data段)可用,不同函數或者代碼之間,無法通過全局變量的形式共享數據。
對於問題1,通常的解決方案是:
u-boot運行起來之后,在那些不需要執行任何初始化動作即可使用的、可讀寫的存儲區域,開辟一段堆棧(stack)空間。 一般來說,大部分的平台(如很多ARM平台),都有自己的SRAM,可用作堆棧空間。如果實在不行,也有可借用CPU的data cache的方法(不再過多說明)。
對於問題2,解決方案要稍微復雜一些:
首先,對於開發者來說,在u-boot被拷貝到可讀寫的RAM(這個動作稱作relocation)之前,永遠不要使用全局變量。 其次,在relocation之前,不同模塊之間,確實有通過全局變量的形式傳遞數據的需求。怎么辦?這就是global data需要解決的事情。
4.2 global data
為了在relocation前通過全局變量的形式傳遞數據,u-boot設計了一個巧妙的方法:
1)定義一個struct global_data類型的數據結構,里面保存了各色各樣需要傳遞的數據
該數據結構的具體內容,后面用到的時候再一個一個解釋,這里不再詳細介紹。具體可參考:include/asm-generic/global_data.h
2)堆棧配置好之后,在堆棧開始的位置,為struct global_data預留空間(可參考第3章中相關的說明),並將開始地址(就是一個struct global_data指針)保存在一個寄存器中,后續的傳遞,都是通過保存在寄存器中的指針實現
對arm64平台來說,該指針保存在了X18寄存器中,如下:
bl board_init_f_alloc_reserve
mov sp, x0
/* set up gd here, outside any C code */ mov x18, x0 bl board_init_f_init_reserve
上面board_init_f_alloc_reserve的返回值(x0)就是global data的指針。
/* arch/arm/include/asm/global_data.h */
#ifdef __clang__
#define DECLARE_GLOBAL_DATA_PTR
#define gd get_gd() static inline gd_t *get_gd(void) { gd_t *gd_ptr; #ifdef CONFIG_ARM64 … __asm__ volatile("mov %0, x18\n" : "=r" (gd_ptr)); #else … } #else #ifdef CONFIG_ARM64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18") #else … #endif
5. 前置的板級初始化操作
global data准備好之后,u-boot會執行前置的板級初始化動作,即board_init_f。所謂的前置的初始化動作,主要是relocation之前的初始化操作,也就是說:
執行board_init_f的時候,u-boot很有可能還在只讀的存儲器中。大家記住這一點就可以了! 注3:大家可能會覺得這里的f(front?)和r(rear?)的命名有點奇怪,我猜這個軟件工程師應該是車迷,是不是借用了前驅和后驅的概念?不得而知啊。
對於ARM等平台來說,u-boot提供了一個通用的board_init_f接口,該接口使用u-boot慣用的設計思路:
u-boot將需要在board_init_f中初始化的內容,抽象為一系列API。這些API由u-boot聲明,由平台的開發者根據實際情況實現。具體可參考本章后續的描述。
void board_init_r(gd_t *new_gd, ulong dest_addr) { #ifdef CONFIG_NEEDS_MANUAL_RELOC int i; #endif #ifdef CONFIG_AVR32 mmu_init_r(dest_addr); #endif #if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64) gd = new_gd; #endif #ifdef CONFIG_NEEDS_MANUAL_RELOC for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++) init_sequence_r[i] += gd->reloc_off; #endif if (initcall_run_list(init_sequence_r)) hang(); /* NOTREACHED - run_main_loop() does not return */ hang(); }
5.1 board_init_f
位於common/board_f.c中的board_init_f接口的實現非常簡單,如下(省略了一些無用代碼):
void board_init_f(ulong boot_flags) { … gd->flags = boot_flags; gd->have_console = 0; if (initcall_run_list(init_sequence_f)) hang(); … }
對global data進行簡單的初始化之后,調用位於init_sequence_f數組中的各種初始化API,進行各式各樣的初始化動作。后面將會簡單介紹一些和ARM平台有關的、和平台的移植工作有關的、比較重要的API。其它API,大家可以參考source code自行理解。
5.2 fdtdec_setup
#ifdef CONFIG_OF_CONTROL
fdtdec_setup,
#endif
如果打開了CONFIG_OF_CONTROL,則調用fdtdec_setup,配置gd->fdt_blob指針(即device tree所在的存儲位置)。對ARM平台來說,u-boot的Makefile會通過連接腳本,將dtb文件打包到u-boot image的“__dtb_dt_begin”位置處,因此不需要特別關心。
5.3 trace_early_init
#ifdef CONFIG_TRACE
trace_early_init,
#endif
由CONFIG_TRACE配置項控制,暫且不用關注,后面用到的時候再分析。
5.4 initf_malloc
如果定義了CONFIG_SYS_MALLOC_F_LEN,則調用initf_malloc,初始化malloc有關的global data,如gd->malloc_limit、gd->malloc_ptr。
5.5 arch_cpu_init
cpu級別的初始化操作,可以在需要的時候由CPU有關的code實現。
5.6 initf_dm
driver model有關的初始化操作。如果定義了CONFIG_DM,則調用dm_init_and_scan初始化並掃描系統所有的device。如果定義了CONFIG_TIMER_EARLY,調用dm_timer_init初始化driver model所需的timer。
5.7 board_early_init_f
#if defined(CONFIG_BOARD_EARLY_INIT_F) board_early_init_f, #endif
如果定義CONFIG_BOARD_EARLY_INIT_F,則調用board_early_init_f接口,執行板級的early初始化。平台的開發者可以根據需要,實現board_early_init_f接口,以完成特定的功能。
5.8 timer_init
初始化系統的timer。
該接口應該由平台或者板級的代碼實現,初始化成功后,u-boot會通過其它的API獲取當前的timestamp,后面用到的時候再詳細介紹。
5.9 get_clocks
獲取當前CPU和BUS的時鍾頻率,並保存在global data中:
gd->cpu_clk gd->bus_clk
5.10 env_init
初始化環境變量有關的邏輯,不需要特別關注。
5.11 init_baud_rate
gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
獲取當前使用串口波特率,可以有兩個途徑(優先級從高到低):從"baudrate"中獲取;從CONFIG_BAUDRATE配置項獲取。
5.12 serial_init
初始化serial,包括u-boot serial core以及具體的serial driver。該函數執行后,系統的串口(特別是用於控制台的)已經可用。
5.13 console_init_f
/* Called before relocation - use serial functions */
int console_init_f(void) { gd->have_console = 1; #ifdef CONFIG_SILENT_CONSOLE if (getenv("silent") != NULL) gd->flags |= GD_FLG_SILENT; #endif print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL); return 0; }
初始化系統的控制台,之后串口輸出可用。大家可留意CONFIG_SILENT_CONSOLE配置項,如果使能,可以通過“silent”環境變量,控制u-boot的控制台是否輸出。
5.14 fdtdec_prepare_fdt
#ifdef CONFIG_OF_CONTROL
fdtdec_prepare_fdt,
#endif
如果定義了CONFIG_OF_CONTROL,調用fdtdec_prepare_fdt接口,准備device tree有關的內容。后續device tree的分析文章會詳細介紹。
5.15 display_options/display_text_info/print_cpuinfo/show_board_info
通過控制台,顯示一些信息,可用於debug。
5.16 misc_init_f
#if defined(CONFIG_MISC_INIT_F) misc_init_f, #endif
如果使能了CONFIG_MISC_INIT_F,則調用misc_init_f執行misc driver有關的初始化。
5.17 init_func_i2c
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C) init_func_i2c, #endif
如果使能了CONFIG_HARD_I2C或者CONFIG_SYS_I2C,則調用init_func_i2c執行i2c driver有關的初始化。
5.18 init_func_spi
#if defined(CONFIG_HARD_SPI) init_func_spi, #endif
如果使能了CONFIG_HARD_SPI,則調用init_func_spi執行spi driver有關的初始化。
5.19 announce_dram_init
宣布我們要進行DDR的初始化動作了(其實就是一行打印)。
5.20 dram_init
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \ defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) dram_init, /* configure available RAM banks */ #endif
調用dram_init接口,初始化系統的DDR。dram_init應該由平台相關的代碼實現。
如果DDR在SPL中已經初始化過了,則不需要重新初始化,只需要把DDR信息保存在global data中即可,例如:
gd->ram_size = …
5.21 testdram
#if defined(CONFIG_SYS_DRAM_TEST) testdram, #endif /* CONFIG_SYS_DRAM_TEST */
如果定義了CONFIG_SYS_DRAM_TEST,則會調用testdram執行DDR的測試操作。可以在開發階段打開,系統穩定后關閉。
5.22 DRAM空間的分配
DRAM初始化完成后,就可以着手規划u-boot需要使用的部分,如下圖:

總結如下:
1)考慮到后續的kernel是在RAM的低端位置解壓縮並執行的,為了避免麻煩,u-boot將使用DRAM的頂端地址,即gd->ram_top所代表的位置。其中gd->ram_top是由setup_dest_addr函數配置的。 2)u-boot所使用的DRAM,主要分為三類:各種特殊功能所需的空間,如log buffer、MMU page table、LCD fb buffer、trace buffer、等等;u-boot的代碼段、數據段、BSS段所占用的空間(就是u-boot relocate之后的執行空間),由gd->relocaddr標示;堆棧空間,從gd->start_addr_sp處遞減。 3)特殊功能以及u-boot所需空間,是由reserve_xxx系列函數保留的,具體可參考source code,這里不再詳細分析。 4)reserve空間分配完畢后,堆棧緊隨其后,遞減即可。
5.23 setup_dram_config
調用dram_init_banksize接口(由具體的平台代碼實現),初始化DDR的bank信息。
5.24 reloc_fdt
如果沒有定義CONFIG_OF_EMBED,則先將device tree拷貝到圖片1 new_fdt所在的位置,也就是device tree的relocation操作。
5.25 setup_reloc
計算relocation有關的信息,主要是 gd->reloc_off,計算公式如下:
gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
其中CONFIG_SYS_TEXT_BASE是u-boot relocation之前在(只讀)memory的位置(也是編譯時指定的位置),gd->relocaddr是relocation之后的位置,因此gd->reloc_off代表u-boot relocation操作之后的偏移量,后面relocation時會用到。
同時,該函數順便把global data拷貝到了圖片1所示的“new global data”處,其實就是global data的relocation。
6. u-boot的relocation
前面講過,u-boot是有可能在只讀的memory中啟動的。簡單起見,u-boot假定所有的啟動都是這樣,因此u-boot的啟動邏輯,都是針對這種情況設計的。在這種情況下,基於如下考慮:
1)只讀memory中執行,代碼需要小心編寫(不能使用全局變量,等等)。
2)只讀memory執行速度通常比較慢。
u-boot需要在某一個時間點,將自己從“只讀memory”中,拷貝到可讀寫的memory(如SDRAM,后面統稱RAM,注意和SRAM區分,不要理解錯了)中繼續執行,這就是relocation(重定位)操作。
relocation的時間點,可以是“系統可讀寫memory始化完成之后“的任何時間點。根據u-boot當前的代碼邏輯,是在board_init_f執行完成之后,因為board_init_f中完成了很多relocation有關的准備動作,具體可參考第5章的描述。
u-boot relocation的代碼如下(以arm64為例):
/* https://github.com/wowotechX/u-boot/blob/x_integration/arch/arm/lib/crt0_64.S */
ldr x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp */ bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */ ldr x18, [x18, #GD_BD] /* x18 <- gd->bd */ sub x18, x18, #GD_SIZE /* new GD is below bd */ adr lr, relocation_return ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd->reloc_off */ add lr, lr, x9 /* new return address after relocation */ ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd->relocaddr */ b relocate_code relocation_return:
邏輯比較簡單:
1)從global data中取出relocation之后的堆棧基址,16-byte對齊后,保存到sp中。
2)將新的global data的指針,保存在x18寄存器中。
3)計算relocation之后的執行地址(relocation_return處),計算的方法就是當前的relocation_return位置加上gd->reloc_off。
4)以relocation的目的地址(gd->relocaddr)為參數,調用relocate_code執行實際的relocation動作,就是將u-boot的代碼段、data段、bss段等數據,拷貝到新的位置(gd->relocaddr)。
7. 后置的板級初始化操作
relocate完成之后,真正的C運行環境才算建立了起來,接下來會執行“后置的板級初始化操作”,即board_init_r函數。board_init_r和board_init_f的設計思路基本一樣,也有一個很長的初始化序列----init_sequence_r,該序列中包含如下的初始化函數(邏輯比較簡單,這里不再涉及細節,權當列出index吧):
注5:老規矩,紅色字體標注的函數是比較重要的函數。
1)initr_trace,初始化並使能u-boot的tracing system,涉及的配置項有CONFIG_TRACE。
2)initr_reloc,設置relocation完成的標志。
3)initr_caches,使能dcache、icache等,涉及的配置項有CONFIG_ARM。
4)initr_malloc,malloc有關的初始化。
5)initr_dm,relocate之后,重新初始化DM,涉及的配置項有CONFIG_DM。
6)board_init,具體的板級初始化,需要由board代碼根據需要實現,涉及的配置項有CONFIG_ARM。
7)set_cpu_clk_info,Initialize clock framework,涉及的配置項有CONFIG_CLOCKS。
8)initr_serial,重新初始化串口(不太明白什么意思)。
9)initr_announce,宣布已經在RAM中執行,會打印relocate后的地址。
10)board_early_init_r,由板級代碼實現,涉及的配置項有CONFIG_BOARD_EARLY_INIT_R。
11)arch_early_init_r,由arch代碼實現,涉及的配置項有CONFIG_ARCH_EARLY_INIT_R。
12)power_init_board,板級的power init代碼,由板級代碼實現,例如hold住power。
13)initr_flash、initr_nand、initr_onenand、initr_mmc、initr_dataflash,各種flash設備的初始化。
14)initr_env,環境變量有關的初始化。
15)initr_secondary_cpu,初始化其它的CPU core。
16)stdio_add_devices,各種輸入輸出設備的初始化,如LCD driver等。
17)interrupt_init,中斷有關的初始化。
18)initr_enable_interrupts,使能系統的中斷,涉及的配置項有CONFIG_ARM(ARM平台u-boot實在開中斷的情況下運行的)。
19)initr_status_led,狀態指示LED的初始化,涉及的配置項有CONFIG_STATUS_LED、STATUS_LED_BOOT。
20)initr_ethaddr,Ethernet的初始化,涉及的配置項有CONFIG_CMD_NET。
21)board_late_init,由板級代碼實現,涉及的配置項有CONFIG_BOARD_LATE_INIT。
22)等等…
23)run_main_loop/main_loop,執行到main_loop,開始命令行操作。