1. HiSilicon SS928V100 U-Boot启动流程全景解析
在嵌入式Linux系统中,U-Boot作为系统启动的"第一道关卡",承担着硬件初始化、环境准备和内核加载的关键任务。HiSilicon SS928V100平台基于ARMv8架构,其U-Boot启动流程具有典型性和代表性。本文将深入剖析从_start到main_loop的完整执行路径,揭示每个阶段的技术细节和设计考量。
1.1 ARMv8启动特性与平台背景
ARMv8架构引入64位支持的同时,也带来了异常级别(EL)的概念。SS928V100作为海思面向视觉处理的高性能SoC,启动时CPU通常运行在最高特权级EL3。U-Boot需要完成从EL3到EL1的级间切换,这涉及到:
- 安全状态配置(Secure/Non-secure)
- 执行状态切换(AArch64/AArch32)
- 内存管理单元(MMU)初始化
- 缓存一致性维护
提示:在ARMv8中,EL3专用于安全监控模式,EL2用于虚拟化,EL1运行操作系统内核,EL0运行用户应用。U-Boot主要在EL1完成大部分工作。
1.2 启动阶段划分与核心任务
整个启动流程可划分为七个关键阶段:
- 汇编入口(Reset Vector) - 硬件最原始状态初始化
- 底层初始化(Lowlevel Init) - 关键外设基础配置
- C运行时准备(C Runtime) - 为高级语言执行创造条件
- 第一阶段板级初始化(board_init_f) - 内存与外设探测
- 代码重定位(Relocation) - 从加载地址到链接地址迁移
- 第二阶段板级初始化(board_init_r) - 完整功能初始化
- 主循环(main_loop) - 交互式环境与自动启动
2. 启动流程深度拆解
2.1 阶段1:汇编入口(Reset Vector)
2.1.1 执行路径与关键操作
assembly复制// arch/arm/cpu/armv8/start.S
_start:
b reset // 复位向量跳转
reset:
b save_boot_params // 保存启动参数(可重载)
save_boot_params_ret:
msr DAIFSet, 0xf // 关闭所有异常(调试/中止/IRQ/FIQ)
bl apply_core_errata // 应用CPU勘误
bl lowlevel_init // 底层硬件初始化
bl _main // 进入C运行时
关键操作解析:
- DAIF设置:通过MSR指令设置DAIF寄存器,确保初始化过程不被中断打扰
- 勘误应用:针对特定CPU版本的硬件缺陷进行软件规避
- 栈未初始化:此时尚未设置栈指针,函数调用使用LR寄存器保存返回地址
2.1.2 启动参数保存机制
save_boot_params是weak符号,允许板级代码重载。典型实现包括:
- 保存BLx启动参数(X0-X3寄存器)
- 记录DDR初始化状态
- 存储Secure Boot相关标志
c复制// 板级覆盖示例(arch/arm/mach-hisilicon/ss928v100/lowlevel_init.c)
void save_boot_params(unsigned long r0, unsigned long r1,
unsigned long r2, unsigned long r3)
{
gd->boot_params[0] = r0; // 存储到全局数据结构
/* ... */
save_boot_params_ret(); // 返回汇编流程
}
2.2 阶段2:底层初始化(lowlevel_init)
2.2.1 中断控制器初始化
assembly复制lowlevel_init:
ldr x0, =GICD_BASE // GIC Distributor基地址
bl gic_init_secure // 安全模式初始化
bl gic_init_secure_percpu // 每核初始化
GICv3初始化关键步骤:
- 设置Distributor控制寄存器(GICD_CTLR)
- 配置中断优先级位数(GICD_TYPER)
- 使能合法安全中断组(Group0/1S)
2.2.2 平台特有初始化
海思平台通常需要:
- 配置TrustZone安全隔离区域
- 初始化DDR PHY训练参数
- 设置PMU(电源管理单元)默认状态
c复制// 典型海思平台扩展
void hisi_lowlevel_init(void)
{
/* 时钟树初始化 */
clk_set_parent(MAINPLL, 24MHz);
clk_set_rate(DDRCLK, 2400MHz);
/* 内存保护设置 */
configure_memory_protection();
}
2.3 阶段3:C运行时启动(_main)
2.3.1 栈与全局数据结构准备
assembly复制_main:
ldr x0, =(CONFIG_SYS_INIT_SP_ADDR) // 初始栈地址(通常是SRAM)
bic sp, x0, #0xf // 16字节对齐
bl board_init_f_alloc_reserve // 分配GD空间
mov x18, x0 // ARM64使用X18存储GD指针
bl board_init_f_init_reserve // 初始化GD内容
全局数据结构(gd_t)包含:
- 启动参数(boot_params)
- 内存布局信息(bd_info)
- 重定位偏移量(reloc_off)
- 环境变量指针(env_addr)
2.3.2 内存布局规划
board_init_f阶段的内存布局决策至关重要:
| 区域 | 计算方式 | 对齐要求 |
|---|---|---|
| 代码区 | _end - _start | 4K |
| 堆区 | CONFIG_SYS_MALLOC_LEN | 16字节 |
| 设备树 | fdt_totalsize() + 扩展空间 | 8字节 |
| 栈区 | CONFIG_SYS_STACK_SIZE | 16字节 |
注意:ARM64要求栈必须16字节对齐,否则可能导致非对齐访问异常。
3. 板级初始化与重定位
3.1 阶段4:board_init_f(第一阶段)
3.1.1 初始化序列解析
init_sequence_f[]包含30余个初始化步骤,关键节点包括:
c复制static const init_fnc_t init_sequence_f[] = {
// 基础框架
setup_mon_len, // 计算U-Boot二进制长度
initf_malloc, // 早期堆分配器
// 硬件抽象层
arch_cpu_init, // 架构相关初始化
timer_init, // 定时器驱动
// 调试接口
serial_init, // 串口控制器
console_init_f, // 简单控制台
// 内存配置
dram_init, // DDR容量检测
setup_dest_addr, // 重定位目标地址计算
// 海思特有
config_qos_registers, // 内存QoS调优
NULL
};
3.1.2 内存探测技术
DDR初始化通过dram_init()实现,海思平台通常采用:
- 读取芯片ID识别DRAM型号
- 根据预设参数配置PHY时序
- 使用写-读校验法检测实际容量
c复制int dram_init(void)
{
gd->ram_size = hisi_ddr_detect(); // 海思专用检测函数
if (gd->ram_size == 0)
panic("DDR init failed");
return 0;
}
3.2 阶段5:代码重定位
3.2.1 重定位必要性分析
U-Boot可能运行在两种场景:
- XIP执行:直接从Nor Flash运行(无需重定位)
- 加载执行:从临时介质(如SRAM)加载到DDR运行(需重定位)
重定位解决的核心问题:
- 代码位置无关性(PIC)处理
- 全局指针(GD)修正
- 设备树地址更新
3.2.2 重定位实现机制
assembly复制// arch/arm/lib/relocate_64.S
relocate_code:
adr x0, _start // 当前加载地址
ldr x1, =_start // 链接地址
sub x9, x1, x0 // 计算偏移量
ldr x10, =__image_copy_end
ldr x11, =__bss_start
copy_loop:
ldp x2, x3, [x0], #16 // 每次拷贝16字节
stp x2, x3, [x1], #16
cmp x0, x10
b.lo copy_loop
// 重定位GD指针
ldr x0, [x18, #GD_START_ADDR_SP]
add x18, x18, x9
关键点:
- 使用LDP/STP指令实现高效批量传输
- 重定位后必须刷新指令缓存(IC IALLU)
- 需要处理位置相关的绝对地址引用
4. 运行时环境建立与主循环
4.1 阶段6:board_init_r(第二阶段)
4.1.1 功能模块初始化
init_sequence_r[]初始化更高级别的子系统:
c复制static init_fnc_t init_sequence_r[] = {
// 核心设施
initr_caches, // 使能MMU和缓存
initr_malloc, // 完整堆分配器
// 设备驱动
initr_dm, // 设备模型初始化
initr_mmc, // 存储设备
// 系统服务
initr_env, // 环境变量加载
initr_net, // 网络栈
// 用户接口
console_init_r, // 完整控制台
stdio_add_devices, // 输入输出设备
run_main_loop, // 进入主循环
};
4.1.2 设备模型初始化
DM(Driver Model)初始化流程:
- 扫描设备树(of_live)
- 绑定驱动(device_bind)
- 探测设备(device_probe)
- 建立设备树(uclass_bind)
c复制int initr_dm(void)
{
if (IS_ENABLED(CONFIG_OF_LIVE))
dm_scan_fdt(gd->fdt_blob, false);
dm_init_and_scan(true); // 初始化并扫描
return 0;
}
4.2 阶段7:主循环(main_loop)
4.2.1 自动启动流程
c复制void main_loop(void)
{
cli_init(); // 初始化命令行
// 处理启动延时
char *bootcmd = env_get("bootcmd");
int bootdelay = env_get_ulong("bootdelay", 10, CONFIG_BOOTDELAY);
if (bootdelay >= 0 && bootcmd) {
while (bootdelay-- && !abortboot(bootdelay)) {
/* 倒计时显示 */
}
if (!abortboot_single_key())
run_command(bootcmd, 0); // 执行启动命令
}
cli_loop(); // 进入交互模式
}
4.2.2 命令行处理架构
U-Boot CLI采用三层架构:
- 解析层:parse_line()处理输入字符串
- 执行层:cmd_call()查找并执行命令
- 历史层:cli_simple_loop()维护命令历史
c复制// common/cli.c
void cli_loop(void)
{
for (;;) {
char *line = readline("=> "); // 读取输入
if (line) {
char *argv[CONFIG_SYS_MAXARGS];
int argc = parse_line(line, argv);
if (argc > 0) {
int rc = cmd_call(argv[0], argc-1, argv+1);
if (rc == CMD_RET_USAGE)
cmd_usage(argv[0]);
}
free(line);
}
}
}
5. 调试技巧与问题排查
5.1 常见启动问题分析
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 卡在_start | 复位向量配置错误 | 检查VBAR_EL3设置 |
| lowlevel_init失败 | GIC初始化异常 | 验证GICD_TYPER寄存器值 |
| board_init_f挂起 | DDR初始化未完成 | 测量DDR供电/时钟信号 |
| 重定位后崩溃 | 地址偏移计算错误 | 对比gd->relocaddr与实际地址 |
| 环境变量加载失败 | 存储介质读取错误 | 使用md命令查看原始数据 |
5.2 调试手段推荐
-
早期调试:
- 点亮LED:在_start处添加GPIO操作
- 串口输出:提前初始化最小串口功能
-
内存检测:
bash复制=> md 0x80000000 100 # 查看DDR起始区域 => mw 0x80000000 0x12345678 # 测试写操作 -
异常追踪:
- 使能CONFIG_DEBUG_UART
- 配置CONFIG_PANIC_HANG挂起而不复位
-
启动流程追踪:
c复制// 在关键函数添加标记 bootstage_mark_name(BOOTSTAGE_ID_START, "_start");
5.3 性能优化建议
-
重定位加速:
- 启用CONFIG_RELOC_FAST_PATH跳过非必要重定位
- 调整CONFIG_SYS_MALLOC_LEN减少内存拷贝
-
启动时间优化:
c复制// 禁用非必要初始化 static init_fnc_t init_sequence_f[] = { // 注释掉非关键驱动 // serial_init, }; -
内存布局调优:
python复制# 通过链接脚本调整段布局 . = ALIGN(8); __malloc_start = .; . += CONFIG_SYS_MALLOC_LEN; __malloc_end = .;
通过以上深度解析,开发者可以全面掌握HiSilicon平台U-Boot的启动机制,为定制开发和问题排查提供坚实基础。实际移植时,建议重点关注board_f.c和board_r.c中的平台相关初始化序列,以及链接脚本(u-boot.lds)的内存布局定义。