文章對 u-boot 學習路線進行了簡單介紹, 並從 u-boot 構建框架著手解構 u-boot, 以 Kconfig 為索引文件自底向上分析框架。 除此之外還介紹了 Boot Loader 的幾個基本流程, 對其中的 TPL 過程進行了剖析。 ...
U-Boot 基礎概念與學習分享
- Board: rockchip-px30, armv8, Cortex-A35
- U-Boot: rockchip-linux/u-boot, branch next-dev
- Tools: VScode, Exuberant CTags
1. 前言
學習 u-boot 啟動流程有些時日了, 雖然看了大量的文章以及在此期間仔細閱讀源碼, 但是仍感覺很多知識點掌握不深刻容易遺忘,不如在寫博文博文的時候重溯整個流程, 也分享自己學習 u-boot 的學習路線方便後來者入門。
2. 學習路線
一般來說晶元公司會提供相關的手冊介紹各個組件, 這對瞭解特定型號的開發板是很有效的, 但不適合初學者進行系統的學習, 建立全局概念應當是第一位的。 學習 u-boot 應當對以下幾個方面有所瞭解:
- ARM Assembly, 推薦 Kyle Baldwin 的 ARM Assembly By Example, 我自己在 github 上也實現了相關的 lab。
- Device Tree, The DeviceTree Specification 結合具體的 dts/dtsi 文件閱讀學習。
- Linker Script, GNU Binutils 官方的ld文檔合具體的lds文件進行閱讀學習, 結合 程式員的自我修養--鏈接裝載與庫 這本書建立相關概念。
- Kconfig, Kconfig Language 同樣用在了 u-boot 中需要對其有一定的瞭解。
如果對 DM(Driver Model) 有興趣, 可以閱讀 Linux Device Driver, Third Edition,但具備以上幾塊知識已經足夠理解 u-boot 啟動流程。 Das U-Boot 提供的文檔以及 u-boot 源碼中提供的 README
這類文檔不需要仔細讀完, 這些僅供學習參考, 但 elinux_talks 這部分資源也值得查閱。
當然還是那句話, 全局概念非常重要, 先啃源碼建立相關概念, 帶著問題探究細節會事半功倍。
3. U-Boot 框架與啟動階段
3.1 U-Boot 架構分析
u-boot 的開發者在開發文檔中描述目錄的層次結構, 但缺少更為巨集觀的概括。 以 rockchip-px30 為例, 其在 u-boot 中的文件可被劃歸為以下幾類。以CPU,ARCH,Board 三級對文件進行劃分可以幫助我們在配置新板時有更清晰的規劃。 Quentin Schulz 在 2017 年的嵌入式 linux 歐洲會議上的演講 Porting U-Boot and Linux on new ARM boards: a step-by-step guide 則具體介紹了詳細的實施步驟。
CPU (armv8), ARCH (arm), Board(px30)
- CPU 層級依賴文件
arch/arm/cpu/armv8/*c;*S;*lds
arch/arm/include/asm/armv8/*h
- ARCH(arm) 層級依賴文件
arch/arm/lib/*c;*S;*lds
arch/arm/include/asm/*h;*S
- Board 層級依賴文件
board/rockchip/evb_px30/*c
arch/arm/mach-rockchip/px30/*c
- Board 層級配置文件
arch/arm/include/asm/arch-rockchip/*h;*S
include/rockchip/*h
include/px30_common.h;evb_px30.h
- Board 層級非依賴文件
- common(cmd, flash, env, usb, ...), disk(partition)
- drivers, fs, net, lib
U-Boot Hierarchy, HangX-Ma
u-boot 的初始化過程就是 CPU \(\rightarrow\) ARCH \(\rightarrow\) Board 的過程, 但並不嚴格劃歸, ARCH部分的通用代碼會調用 Board 相關的介面。 在 wowo 的文章中提到曾經存在於 ARCH 和 Board 之間的 machine 層級由於最新的ARM64架構引入了 device tree 的緣故, 已經將 machine 概念刪除了, 在當前 u-boot 中看到的 mach-xxx
的目錄或文件就屬於 machine 層級, 雖然 u-boot 還未更新相關的架構概念, 但在開發層面 u-boot 和 linux 內核幾乎同時適用了 device tree, 這意味著 u-boot 也很可能在之後的更新中刪除類似的 mach-xxx
文件。
3.1.1 舉例——從 Kconfig 自底向上
從 Kconfig
中自底向上梳理整個編譯框架, 假設我們使用的目標板是 rockchip-px30 系列的 evb-px30, 那麼 board/rockchip/evb_px30
文件夾中定義了目標板的一些依賴代碼, 在 include/configs/evb_px30.h
會有該目標板的配置信息, 類似的配置信息和編譯是息息相關的需要格外留意, 後續不過提點。
從頂層的 board/rockchip/evb_px30/Kconfig
查看, 可以找到 TARGET_EVB_PX30
整個關鍵量以及定義的 BORAD
, VENDOR
等編譯相關的變數。
# board\rockchip\evb_px30\Kconfig
if TARGET_EVB_PX30
config SYS_BOARD
default "evb_px30"
config SYS_VENDOR
default "rockchip"
config SYS_CONFIG_NAME
default "evb_px30"
config BOARD_SPECIFIC_OPTIONS # dummy
def_bool y
endif
順著前述所提及的關鍵量, 在 arch/arm/mach-rockchip/px30/Kconfig
中能找到引用信息(尤其是 source 了前述的 Kconfig
文件), 由於當前使用的就是 evb-px30 板, EVB_PX30
該 bool
變數是 true
。 可以看到該 Kconfig
文件在框架中屬於亟待更新的 machine 層級, 所以在該部分可以看到 SYS_SOC
這個配置變數。 在該 Kconfig
文件中還覆蓋定義了 SYS_MALLOC_F_LEN
和 SPL_SERIAL_SUPPORT
。
# arch/arm/mach-rockchip/px30/Kconfig
if ROCKCHIP_PX30
config TARGET_EVB_PX30
bool "EVB_PX30"
select BOARD_LATE_INIT
config SYS_SOC
default "rockchip"
config SYS_MALLOC_F_LEN
default 0x400
config SPL_SERIAL_SUPPORT
default y
source "board/rockchip/evb_px30/Kconfig"
endif
在更上一級目錄則看到更為通用的 Kconfig
文件會配置 ROCKCHIP_PX30
這個定義量。 可以看到在該目錄下配置了 px30 系列使用預設配置。 我們再向上一級的查找 ARCH_ROCKCHIP
變數以其找到頂層的 xxx_defconfig
配置文件。
# arch\arm\mach-rockchip\Kconfig
if ARCH_ROCKCHIP
config ROCKCHIP_PX30
bool "Support Rockchip PX30"
select ARM64 if !ARM64_BOOT_AARCH32
select GICV2
select ARM_SMCCC
select SUPPORT_SPL
select SUPPORT_TPL
select SPL if !ARM64_BOOT_AARCH32
select TPL if !ARM64_BOOT_AARCH32
select TPL_TINY_FRAMEWORK if TPL
imply SPL_SEPARATE_BSS
imply SPL_SERIAL_SUPPORT
imply TPL_SERIAL_SUPPORT
help
The Rockchip PX30 is a ARM-based SoC with a quad-core Cortex-A35
including NEON and GPU, Mali-400 graphics, several DDR3 options
and video codec support. Peripherals include Gigabit Ethernet,
USB2 host and OTG, SDIO, I2S, UART, SPI, I2C and PWMs.
if ROCKCHIP_PX30
config TPL_LDSCRIPT
default "arch/arm/mach-rockchip/u-boot-tpl-v8.lds"
config TPL_TEXT_BASE
default 0xff0e1000
config TPL_MAX_SIZE
default 10240
config ROCKCHIP_RK3326
bool "Support Rockchip RK3326 "
help
RK3326 can use most code from PX30, but at some situations we have
to distinguish between RK3326 and PX30, so this macro gives help.
It is usually selected in rk3326 board defconfig.
endif
...
在更上一級, 我們先找到了 arch\arm\Kconfig
, ARCH 層級的預設配置。
# arch\arm\Kconfig
...
config ARCH_ROCKCHIP
bool "Support Rockchip SoCs"
select OF_CONTROL
select BLK
select DM
select SPL_DM if SPL
select SYS_MALLOC_F
select SYS_THUMB_BUILD if !ARM64
select SPL_SYS_MALLOC_SIMPLE if SPL
imply DM_GPIO
select DM_SERIAL
select DM_SPI
select DM_SPI_FLASH
select DM_USB if USB
select CMD_ROCKUSB if USB_GADGET_DOWNLOAD
select ENABLE_ARM_SOC_BOOT0_HOOK
select SYS_NS16550
select SPI
select DEBUG_UART_BOARD_INIT
select PANIC_HANG
imply DM_MMC
imply DM_I2C
imply DM_PWM
imply DM_REGULATOR
imply CMD_FASTBOOT
imply FASTBOOT
imply FAT_WRITE
imply USB_FUNCTION_FASTBOOT
imply USB_FUNCTION_ROCKUSB
imply SPL_SYSRESET
imply TPL_SYSRESET
imply ADC
imply SARADC_ROCKCHIP
...
最終我們能在 configs/evb-px30_defconfig
(Target) 中找到用戶自定義的基本巨集信息, 另外一些信息則在前述提及的配置文件中。 例如 include/configs/px30_common.h
, include/configs/evb_px30.h
, 以及 include/configs/rockchip-common.h
。 我們自底向上, 特定的板級文件開始溯源, 找到了最終頂層的配置文件。 根據頂層的配置文件以及每個層級的配置文件可以梳理出編譯特定板所需的功能。 另外, 在底層的 TARGET 的配置中可以看到諸如 SYS_xxx
的一系列配置, 這些配置會在更上層的 arch\Kconfig
中定義。 所以綜上可以總結出如下配置關係圖。
3.2 Boot Loader Stage
BLx(Boot Loader Stage) 指代 Boot Loader 的各個階段, 具體的劃分根據 u-boot 初始化時所在存儲設備略有不同, 一般將 u-boot 啟動劃分為 4 個階段, BL0, BL1, BL2, BL3。值得註意的是這與 ARM TrustZone 的劃分非屬同源, 在 ARM TrustZone 的劃分中, u-boot 屬於 BL33 Non-secure 部分。
- BL0, SOC 生產廠家固化在 iROM(Internal ROM) 中的啟動代碼, 主要負責載入 BL1 的程式, 該部分被稱作 Initial Program Loader (IPL) 或者 Primary Program Loader (PPL)。
- BL1, 該部分被稱為 SPL(Secondary Program Loader), 若 SPL 部分仍超過了 flash 存儲限制, 首先會通過 TPL(Trinary Program Loader) 進行更簡潔的初始化如 DDR 部分的初始化,以保證代碼體積極小, 之後再從指定位置載入 SPL 繼續執行初始化。
- BL2, 該階段 u-boot 運行程式重定位之前的部分, 主要負責一系列初始化操作以及構建 C 語言的運行環境, 最為關鍵的是將 u-boot 重定位至 DRAM/SDRAM 中繼續執行 BL3 階段的程式。
- BL3, 在該階段實質上載入了u-boot, 當然通過 ATF(Architecture Trusted Firmware) 載入也是可以的。 該階段在負責初始化 SOC 的外設, 準備內核啟動參數以及載入運行內核等操作。
Boot loader sequences, HouchengLin
根據以上描述, 以圖例形式表述 u-boot 的啟動流程應當如下所示。
flowchart LR p1(BootROM) p2(TPL, optional) p3(SPL) p4(ATF, optional) p5(U-Boot) p6(Kernel) p1 --> p2 --> p3 --> p4 --> p5 --> p6 p1 -.-> p3 p3 -.-> p5 p1 ==> p54. 淺析 TPL
嵌入式的代碼鐵定有個名為 start.S
的入口彙編代碼, 但在進行源碼分析之前, 我比較喜歡閱讀鏈接腳本以此獲悉 u-boot 的構成以及分析啟動過程中的一些工作。 在 arch/arm/cpu/armv8
目錄下有兩個 lds
文件, armv8 的 BootROM \(\rightarrow\) u-boot 的引導使用 u-boot.lds
進行鏈接, 而在 u-boot 之前存在 SPL/TPL 階段則會使用 u-boot-spl.lds
或 arch/arm/mach-rockchip/u-boot-tpl-v8.lds
進行鏈接。
在上述流程中提及 TPL 的存在, 這也是讓我比較困惑的, TPL 如何 與 SPL 進行配合實現對 bootloader 的引導啟動, 這一塊內容值得深入探究。 不妨先從 TPL 的鏈接腳本入手, 釐清 TPL 階段的相關邏輯。
這兩篇文章關於
u-boot-spl.lds
有著不同詳略的解析, 可以用以瞭解 u-boot 相關的鏈接腳本的 section 的基本功能以及瞭解鏈接腳本的基本概念, 這些內容已有前人做了充分的解析不再贅述。
4.1 TPL Configurations
根據前述配置, evb-px30 在啟動時會經由 TPL 以及 SPL 引導 u-boot。 在 arch/arm/mach-rockchip/Kconfig
中可以看到與 TPL 相關的一些定義:TPL
, TPL_TINY_FRAMEWORK
, TPL_TINY_FRAMEWORK
, TPL_LDSCRIPT
, TPL_TEXT_BASE
, TPL_MAX_SIZE
, SUPPORT_TPL
, 這些巨集定義會影響後續編譯的過程。
其中, CONFIG_TPL_BUILD
這個巨集定義非常重要。 網上很多博客提及相關內容僅說明在定義 CONFIG_TPL
之後 CONFIG_TPL_BUILD
會自動定義, 但沒有詳細說明具體位置。 在 scripts/Makefile.autoconf
文件的 85-87 行可以看到幾行規則, 實際上向 tpl/u-boot.cfg
傳遞了 CONFIG_SPL_BUILD
, CONFIG_TPL_BUILD
這兩個量。 在其他任何 config.mk
,Kconfig
, Kbuild
這樣的文件中都不會找到這兩個量的定義。
# scripts/Makefile.autoconf
tpl/u-boot.cfg: include/config.h FORCE
$(Q)mkdir -p $(dir $@)
$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD -DCONFIG_TPL_BUILD)
另外在頂層的 Makefile
文件我們可以找到這樣一則規則, 是 script/Makefile.autoconf
的上一級引用。
# Makefile
u-boot.cfg spl/u-boot.cfg tpl/u-boot.cfg: include/config.h FORCE
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf $(@)
而當我們查看 script/Makefile.autoconf
所描述的功能時, 可以看到前述的 CONFIG_SPL_BUILD
, CONFIG_TPL_BUILD
以及其他生成的巨集定義最終會被轉移到 Kconfig
中以完成全局性的定義。
# This helper makefile is used for creating
# - symbolic links (arch/$ARCH/include/asm/arch
# - include/autoconf.mk, {spl,tpl}/include/autoconf.mk
# - include/config.h
#
# When our migration to Kconfig is done
# (= When we move all CONFIGs from header files to Kconfig)
# this makefile can be deleted.
4.2 TPL Linker Script
BootROM 完成基本的初始化後首先會在 iRAM 中載入 TPL 段的運行代碼。
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
.text : {
. = ALIGN(8);
*(.__image_copy_start)
CPUDIR/start.o (.text*)
*(.text*)
}
.rodata : {
. = ALIGN(8);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
}
.data : {
. = ALIGN(8);
*(.data*)
}
.u_boot_list : {
. = ALIGN(8);
KEEP(*(SORT(.u_boot_list*)));
}
.image_copy_end : {
. = ALIGN(8);
*(.__image_copy_end)
}
.end : {
. = ALIGN(8);
*(.__end)
}
_image_binary_end = .;
.bss_start (NOLOAD) : {
. = ALIGN(8);
KEEP(*(.__bss_start));
}
.bss (NOLOAD) : {
*(.bss*)
. = ALIGN(8);
}
.bss_end (NOLOAD) : {
KEEP(*(.__bss_end));
}
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
#if defined(CONFIG_TPL_MAX_SIZE)
ASSERT(__image_copy_end - __image_copy_start < (CONFIG_TPL_MAX_SIZE), \
"TPL image too big");
#endif
#if defined(CONFIG_TPL_BSS_MAX_SIZE)
ASSERT(__bss_end - __bss_start < (CONFIG_TPL_BSS_MAX_SIZE), \
"TPL image BSS too big");
#endif
#if defined(CONFIG_TPL_MAX_FOOTPRINT)
ASSERT(__bss_end - _start < (CONFIG_TPL_MAX_FOOTPRINT), \
"TPL image plus BSS too big");
#endif
ENTRY(_start)
實際上聲明瞭程式的入口地址, 對 TPL 而言這是顯而易見的, 因為 TPL 需要在該階段獲得程式的控制權完成一系列基本的初始化進程。 與其他 ld
文件不同的是, TPL 的鏈接腳本對 TPL 程式本身的大小有嚴格的控制。 在 machine 級的 arch/arm/mach-rockchip/Kconfig
中我們定義了 TPL_MAX_SIZE
, 這使得我們可以檢查 TPL image 的大小以滿足 iRAM 的空間限制要求。一般來說,__image_copy_start
和 __image_copy_end
這兩個變數常用來輔助 u-boot 的重定位, 但在此處被賦予了新的功能。 另外可以看到 bss
段都被聲明瞭 NOLOAD
屬性, 這意味著 bss
段在 image 中並不占用任何空間, 但相關的地址信息會被保留用以在 u-boot載入時的一些數據初始化操作。 因而可以歸納得到 TPL 載入時實際的記憶體分佈情況。
TPL Loading Memory, HangX-Ma
另外可以從 ld
文件中看到, 入口程式是 CPUDIR/start.o
, CPUDIR
可以依據層級劃分從各個較為頂層的 Makefile
文件中找到具體定義。 但根據架構分析中的概念, 不難得出此處的 CPUDIR
是 arch/arm/armv8
。 start.S
最終會定位到 _main
程式入口繼續執行流程。(關於 start.S
的詳細流程可以參考 ARMv8架構u-boot啟動流程詳細分析(二), 內核新視界) 由於我們的編譯是 AArch64 架構, 那麼 C Runtime Environment 的建立也應當是 crt0_64.S
, 可以在這個文件中看到, board_init_f_alloc_reserve
, board_init_f_init_reserve
, board_init_f_boot_flags
幾個函數通過在棧頂預留記憶體來達到給 GD(Global Data)
開闢記憶體空間, 在 AArch64 架構中 GD
指針地址會被保留在 x18
寄存器中供全局使用, 之後跳轉到 board_init_f
。 這是一個分水嶺, TPL, SPL 以及 u-boot 都會執行這個函數。
一般來說可以將 u-boot 的啟動過程劃分為兩個階段, 也就是前述的 BL2 和 BL3 的區分。 Pre-relocation(common/board_f.c
), 此處的 f
表示程式執行所在的存儲介質是 flash
, 以及 After-relocation(common/board_r.c
), 此處的 r
表示程式執行所在的存儲介質是 RAM
。
我們知道 TPL 只完成一些很基本的初始化流程, 對於 TPL 而言實際上不存在重定位的需求, 所以關鍵就在 board_init_f
這個函數。
- SPL:
arch\arm\mach-rockchip\spl.c
- TPL:
arch\arm\mach-rockchip\tpl.c
- U-Boot:
common\board_f.c
在編譯鏈接時, 編譯組件就會對這幾個文件進行區分, 以保證綁定正確的可執行文件。 在 arch\arm\mach-rockchip\Makefile
中就巧妙的在編譯 TPL 文件時取消了 SPL 相關文件的生成, 而在編譯 SPL 文件時則不受 TPL 的相關定義影響。
根據之前的巨集定義梳理的在 TPL 階段的 board_init_f
所做的工作如下, 時鐘初始化, CPU 部分初始化, UART 串口初始化, SDRAM 初始化。 這些工作都完成之後會通過 arch\arm\mach-rockchip\Kconfig
預設定義的 TPL_ROCKCHIP_BACK_TO_BROM
巨集引導的 back_to_bootrom
返回 BootROM 階段再進行下一階段的 SPL。
board_init_f
rockchip_stimer_init
arch_cpu_init
debug_uart_init
timer_init
sdram_init
back_to_bootrom
flowchart LR
p1(BootROM)
p2(TPL)
p3(SPL)
p4(U-Boot)
p5(Kernel)
p1 -.->|stage 1 'init'| p2
p2 -.->|stage 2 'back to rom'| p1
p1 -->|stage 3| p3
p3 --> p4
p4 --> p5
至於 SPL 的具體流程可以參考 TPL 的流程進行推導相關的資料也非常詳細, 在參考部分的 U-Boot 部分已經列舉了篩選過的較好的資料可供選讀。
5. 總結
文章對 u-boot 學習路線進行了簡單介紹, 並從 u-boot 構建框架著手解構 u-boot, 以 Kconfig 為索引文件自底向上分析框架。 除此之外還介紹了 Boot Loader 的幾個基本流程, 對其中的 TPL 過程進行了剖析。後續會在此篇博文的基礎上進行增改擴充基礎概念部分, 而其他需要仔細剖析的部分則另建博文進行闡述。
6. 參考
6.1 U-Boot
- 從0移植uboot (二) _uboot啟動流程分析, Abnor
- u-boot分析(文章類), wowo
- X-003-UBOOT-基於Bubblegum-96平臺的u-boot移植說明, wowo
- u-boot啟動流程, wowothink
- ARMv8架構u-boot啟動流程詳細分析(一), 內核新視界
- ARMv8架構u-boot啟動流程詳細分析(二), 內核新視界
- Armv8架構UBOOT 啟動篇——SPL(start.S), Kernel_Nuts
- Armv8架構UBOOT 啟動篇——SPL(u-boot-spl.lds鏈接腳本), Kernel_Nuts
- U-Boot - Bootloader for IoT Platform? [ELCE 2018], Alexey Brodkin, Synopsys
- U-boot startup sequence, HouchengLin
6.2 ARM 參考手冊
- Arm® Instruction Set Reference Guide
- ARM Developer Suite Assembler Guide
- Arm Architecture Reference Manual for A-profile architecture
- Arm Cortex-A35 Processor Technical Reference Manual
- ARM ELF Specification
6.3 AArch64 架構
- Learn the architecture - AArch64 memory management
- Learn the architecture - Introduction to security
- Learn the architecture - AArch64 Exception Model
- Learn the architecture - TrustZone for AArch64