概述
U-Boot 的启动过程是一个复杂而精密的过程,从硬件上电复位到最终进入命令行,经历了多个阶段。本文档将详细剖析整个启动流程,包括汇编启动代码、重定位、C代码初始化等关键环节。
1. 启动流程总览
1.1 启动阶段划分
flowchart TD
A[上电复位<br/>PC = 0x0] --> B[Stage 1<br/>start.S 汇编启动]
B --> C[Stage 2<br/>lowlevel_init 底层初始化]
C --> D[Stage 3<br/>_main 进入 C 运行时]
D --> E[Stage 4<br/>board_init_f 重定位前初始化]
E --> F[Stage 5<br/>relocate_code 代码重定位]
F --> G[Stage 6<br/>board_init_r 重定位后初始化]
G --> H[Stage 7<br/>main_loop 命令行循环]
style A fill:#f99,stroke:#333,stroke-width:3px
style D fill:#9f9,stroke:#333,stroke-width:2px
style F fill:#99f,stroke:#333,stroke-width:2px
style H fill:#fcf,stroke:#333,stroke-width:3px
1.2 内存使用变化
graph LR
A[启动时<br/>Flash/ROM] --> B[重定位前<br/>Flash执行]
B --> C[重定位<br/>复制到RAM]
C --> D[重定位后<br/>RAM执行]
style A fill:#fcc,stroke:#333,stroke-width:2px
style B fill:#ffc,stroke:#333,stroke-width:2px
style D fill:#cfc,stroke:#333,stroke-width:2px
2. Stage 1: 汇编启动代码 (start.S)
2.1 入口点:reset
文件位置: arch/arm/cpu/armv7/start.S
.globl reset
.globl save_boot_params_ret
reset:
/* 允许开发板保存重要寄存器 */
b save_boot_params
save_boot_params_ret:
/*
* 禁用中断 (FIQ 和 IRQ),设置 CPU 为 SVC32 模式
*/
mrs r0, cpsr
and r1, r0, #0x1f @ 提取模式位
teq r1, #0x1a @ 测试是否为 HYP 模式
bicne r0, r0, #0x1f @ 清除所有模式位
orrne r0, r0, #0x13 @ 设置为 SVC 模式
orr r0, r0, #0xc0 @ 禁用 FIQ 和 IRQ
msr cpsr, r0
2.2 关键步骤详解
flowchart TD
A[reset 复位入口] --> B[save_boot_params<br/>保存启动参数]
B --> C[设置 CPU 模式<br/>SVC32 + 禁用中断]
C --> D[设置异常向量表<br/>VBAR 寄存器]
D --> E[cpu_init_cp15<br/>CP15 协处理器初始化]
E --> F[cpu_init_crit<br/>关键硬件初始化]
F --> G[_main<br/>进入 C 环境]
style A fill:#f99,stroke:#333,stroke-width:3px
style G fill:#9f9,stroke:#333,stroke-width:3px
2.3 CPU 模式和寄存器设置
ARM CPU 模式:
| 模式 | 编码 | 说明 |
|---|---|---|
| User | 0x10 | 用户模式(非特权) |
| FIQ | 0x11 | 快速中断模式 |
| IRQ | 0x12 | 普通中断模式 |
| SVC | 0x13 | 管理模式(U-Boot 使用) |
| Abort | 0x17 | 数据访问异常模式 |
| Undef | 0x1B | 未定义指令模式 |
| HYP | 0x1A | 虚拟化模式(ARMv7) |
graph TD
A[CPSR 寄存器<br/>当前程序状态] --> B[位 0-4<br/>模式位]
A --> C[位 5<br/>Thumb状态]
A --> D[位 6<br/>FIQ禁用]
A --> E[位 7<br/>IRQ禁用]
A --> F[位 24-31<br/>条件标志]
B --> G[0x13 = SVC模式]
D --> H[1 = 禁用FIQ]
E --> I[1 = 禁用IRQ]
style A fill:#fcf,stroke:#333,stroke-width:2px
style G fill:#9f9,stroke:#333,stroke-width:2px
2.4 CP15 协处理器初始化
ENTRY(cpu_init_cp15)
/*
* 使无效 L1 I/D cache
*/
mov r0, #0
mcr p15, 0, r0, c8, c7, 0 @ 使无效 TLB
mcr p15, 0, r0, c7, c5, 0 @ 使无效 I-cache
mcr p15, 0, r0, c7, c5, 6 @ 使无效分支预测
mcr p15, 0, r0, c7, c10, 4 @ 数据同步屏障
mcr p15, 0, r0, c7, c5, 4 @ 指令同步屏障
/*
* 禁用 MMU 和 Cache
*/
mrc p15, 0, r0, c1, c0, 0 @ 读取系统控制寄存器
bic r0, r0, #0x00002000 @ 清除 V 位(向量表位置)
bic r0, r0, #0x00000007 @ 清除 C、A、M 位
orr r0, r0, #0x00000002 @ 设置 A 位(对齐检查)
orr r0, r0, #0x00000800 @ 设置 Z 位(分支预测)
#ifndef CONFIG_SYS_ICACHE_OFF
orr r0, r0, #0x00001000 @ 设置 I 位(I-cache)
#endif
mcr p15, 0, r0, c1, c0, 0 @ 写回系统控制寄存器
CP15 寄存器功能:
graph TD
A[CP15 协处理器] --> B[c1: 系统控制]
A --> C[c7: Cache/TLB操作]
A --> D[c8: TLB管理]
A --> E[c12: 向量基地址]
A --> F[c15: 实现定义]
B --> G[MMU使能<br/>Cache使能<br/>对齐检查]
C --> H[Cache清除<br/>Cache使无效]
D --> I[TLB使无效]
style A fill:#f99,stroke:#333,stroke-width:2px
3. Stage 2: 底层初始化 (lowlevel_init)
3.1 函数调用链
sequenceDiagram
participant start as start.S
participant crit as cpu_init_crit
participant low as lowlevel_init
participant board as s_init
start->>crit: bl cpu_init_crit
crit->>low: bl lowlevel_init
low->>low: 设置栈指针
low->>board: bl s_init
board->>board: 时钟初始化
board->>board: DDR 初始化
board->>low: 返回
low->>crit: 返回
crit->>start: 返回
Note over board: 板级特定初始化
3.2 栈的设置
在进入 C 代码之前,必须先设置好栈:
lowlevel_init:
/*
* 设置栈指针到片上 SRAM
*/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8字节对齐 */
/* 调用板级初始化 */
bl s_init
/* 返回到调用者 */
mov pc, lr
栈空间布局:
graph TD
A[片上 SRAM<br/>CONFIG_SYS_INIT_SP_ADDR] --> B[栈顶<br/>sp 指向这里]
B --> C[栈向下增长]
C --> D[GD 全局数据]
D --> E[BD 板级数据]
style A fill:#fcf,stroke:#333,stroke-width:2px
style B fill:#f99,stroke:#333,stroke-width:2px
3.3 ⚠️ DDR 初始化的时机澄清
重要说明: 很多人误以为 lowlevel_init 或 s_init() 中会初始化 DDR,实际情况取决于平台。
i.MX6 平台的 DDR 初始化方式
在 i.MX6 平台上,DDR 初始化在 U-Boot 代码执行之前 就已经完成:
sequenceDiagram
participant BootROM
participant DCD as DCD 表
participant DDR as DDR 硬件
participant UBoot as U-Boot 代码
Note over BootROM: 上电后 BootROM 执行
BootROM->>BootROM: 1. 读取 u-boot.imx
BootROM->>DCD: 2. 找到 DCD 表
BootROM->>DCD: 3. 执行 DCD 命令
DCD->>DDR: 4. 配置 MMDC 寄存器
DDR->>DDR: 5. DDR 初始化完成
Note over DDR: ✅ DDR 硬件已可用
BootROM->>DDR: 6. 复制 U-Boot 到 DDR
BootROM->>UBoot: 7. 跳转到 U-Boot
Note over UBoot: lowlevel_init 开始执行<br/>此时 DDR 已经可用
DCD 表示例:
/* DCD 表:在 u-boot.imx 中,BootROM 执行 */
DCD Write Commands:
// 1. 使能 DDR 时钟
WRITE 0x020C4018, 0x00260324
// 2. 配置 IOMUX(DDR 引脚)
WRITE 0x020E0798, 0x000C0000 // DDR_A0
WRITE 0x020E079C, 0x000C0000 // DDR_A1
... (50+ 条配置)
// 3. 配置 MMDC(DDR 控制器)
WRITE 0x021B0800, 0xA1390003 // 控制寄存器
WRITE 0x021B080C, 0x001F001F // 时序参数
// 4. 启动 DDR
WRITE 0x021B0000, 0xC3190000 // DDR3, 512MB
因此在 i.MX6 平台:
- ✅ BootROM 阶段:通过 DCD 表初始化 DDR 硬件
- ✅ lowlevel_init:只设置栈(在 OCRAM),不初始化 DDR
- ✅ **s_init()**:初始化时钟、GPIO 等,不初始化 DDR
- ✅ **dram_init()**:探测 DDR 大小并告诉 U-Boot
时间线对比:
| 阶段 | DDR 硬件状态 | U-Boot 对 DDR 的认知 |
|---|---|---|
| BootROM + DCD | ✅ 已初始化 | ❌ U-Boot 还没运行 |
| lowlevel_init | ✅ 可用 | ❌ 不知道大小 |
| board_init_f::dram_init() | ✅ 可用 | ✅ 知道大小了 |
| relocate_code | ✅ 可用 | ✅ 知道布局 |
❓ 为什么 lowlevel_init 不用 DDR 做栈?
虽然此时 DDR 硬件已可用,但:
- U-Boot 还不知道 DDR 的大小和布局
- 代码还在 Flash/低地址执行,未重定位
- OCRAM 是 CPU 内部 SRAM,更快更可靠
- 后续会通过
dram_init()探测 DDR,再进行重定位
其他平台可能不同:
在没有 DCD 机制的平台(如某些 ARM Cortex-A 芯片),DDR 初始化确实在 s_init() 或 SPL 中完成:
/* 示例:其他平台的 s_init() 可能包含 DDR 初始化 */
void s_init(void)
{
/* 时钟初始化 */
setup_clocks();
/* DDR 初始化(非 i.MX6) */
ddr_init(); // 配置 DDR 控制器寄存器
/* GPIO 初始化 */
setup_gpios();
}
4. Stage 3: 进入 C 环境 (_main)
4.1 _main 函数流程
文件位置: arch/arm/lib/crt0.S
flowchart TD
A[_main 入口] --> B[设置栈指针<br/>sp = CONFIG_SYS_INIT_SP_ADDR]
B --> C[board_init_f_alloc_reserve<br/>为 GD/BD 预留空间]
C --> D[调整栈指针<br/>sp = sp - GD_SIZE - BD_SIZE]
D --> E[board_init_f_init_reserve<br/>初始化 GD/BD 结构]
E --> F[清零 GD 结构<br/>memset gd, 0]
F --> G[board_init_f<br/>重定位前初始化]
G --> H[遍历 init_sequence_f 数组]
H --> I[dram_init<br/>探测 DDR 大小]
I --> J[reserve_xxx<br/>预留内存空间]
J --> K[setup_reloc<br/>计算重定位地址]
K --> L[relocate_code<br/>代码重定位]
L --> M[重定位到 DDR 高地址]
M --> N[c_runtime_cpu_setup<br/>设置新的 GD 地址]
N --> O[board_init_r<br/>重定位后初始化]
O --> P[run_main_loop<br/>进入主循环]
style A fill:#f99,stroke:#333,stroke-width:3px
style G fill:#9f9,stroke:#333,stroke-width:2px
style L fill:#99f,stroke:#333,stroke-width:2px
style P fill:#fcf,stroke:#333,stroke-width:3px
_main 函数代码:
ENTRY(_main)
/*
* 设置初始 C 运行时环境并调用 board_init_f()
*/
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8字节对齐 */
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* 清零 GD (global data) */
mov r0, sp
bl board_init_f_init_reserve
/* 调用重定位前初始化函数 */
mov r0, #0
bl board_init_f
/* 此处不应该返回,除非出错 */
/*
* board_init_f() 不会返回
* 而是跳转到 relocate_code,然后调用 board_init_r
*/
ENDPROC(_main)
❓ 为什么 _main 是汇编而不是 C?
因为此时 C 运行时环境还未完全建立:
- 栈指针未设置
- BSS 段未清零
- 全局变量未初始化
- 需要精确控制栈和 GD 的位置
4.1.1 _main 详细执行流程
sequenceDiagram
participant Start as start.S
participant Main as _main (crt0.S)
participant Alloc as board_init_f_alloc_reserve
participant InitRes as board_init_f_init_reserve
participant InitF as board_init_f
participant Reloc as relocate_code
participant InitR as board_init_r
Start->>Main: bl _main
Note over Main: 1. 设置初始栈
Main->>Main: ldr sp, CONFIG_SYS_INIT_SP_ADDR
Note over Main: 2. 预留 GD/BD 空间
Main->>Alloc: bl board_init_f_alloc_reserve
Alloc-->>Main: 返回新栈地址
Main->>Main: mov sp, r0
Note over Main: 3. 初始化 GD/BD 结构
Main->>InitRes: bl board_init_f_init_reserve
InitRes->>InitRes: 清零 GD 结构
InitRes->>InitRes: 设置 gd->bd 指针
InitRes-->>Main: 返回
Note over Main: 4. 重定位前初始化
Main->>InitF: bl board_init_f(0)
InitF->>InitF: 执行 init_sequence_f[]
InitF->>InitF: dram_init() - 探测 DDR
InitF->>InitF: reserve_xxx() - 预留内存
InitF->>InitF: setup_reloc() - 计算重定位地址
InitF-->>Main: 不返回,跳转到重定位
Note over Main: 5. 代码重定位(ARM 架构)
InitF->>Reloc: 跳转到 relocate_code
Reloc->>Reloc: 复制代码到 DDR 高地址
Reloc->>Reloc: 修正地址引用
Note over Main: 6. 重定位后初始化
Reloc->>InitR: 跳转到 board_init_r
InitR->>InitR: 执行 init_sequence_r[]
InitR->>InitR: 初始化外设驱动
InitR->>InitR: 加载环境变量
Note over Main: 7. 进入主循环
InitR->>InitR: run_main_loop()
Note over InitR: 永不返回
4.1.2 栈空间分配
SRAM (片上 SRAM / OCRAM)
0x00900000 ┌─────────────────────┐ IRAM_BASE_ADDR
│ │
│ 未使用空间 │
│ │
0x0091E000 ├─────────────────────┤
│ 栈空间 (8KB) │
│ ↓↓↓ 向下增长 │ ← sp 从这里开始
0x0091FE00 ├─────────────────────┤
│ Board Data (bd_t) │ (约 256 字节)
0x0091FF00 ├─────────────────────┤
│ Global Data (gd_t)│ (约 256 字节)
0x00920000 └─────────────────────┘ IRAM_BASE_ADDR + IRAM_SIZE
/* 栈指针计算 */
sp = CONFIG_SYS_INIT_SP_ADDR // 0x00920000
sp = sp - sizeof(gd_t) // 预留 GD
sp = sp - sizeof(bd_t) // 预留 BD
sp = sp & ~7 // 8 字节对齐
关键点:
- 栈向下增长(从高地址向低地址)
- GD 和 BD 在栈空间的顶部
- r9 寄存器指向 GD 结构
4.2 内存布局(重定位前)
graph TD
A[Flash 0x00000000] --> B[U-Boot 代码段<br/>.text .rodata]
B --> C[U-Boot 数据段<br/>.data]
D[SRAM CONFIG_SYS_INIT_SP_ADDR] --> E[栈空间<br/>向下增长]
E --> F[GD 全局数据结构<br/>sizeof gd_t]
F --> G[BD 板级数据]
H[DDR 内存<br/>尚未初始化] --> I[将来重定位的目标]
style A fill:#fcc,stroke:#333,stroke-width:2px
style D fill:#cfc,stroke:#333,stroke-width:2px
style H fill:#ccc,stroke:#333,stroke-width:2px
4.3 全局数据结构 (gd_t)
💡 重要概念:
gd_t是 U-Boot 中最核心的全局数据结构,贯穿整个启动流程。详细说明请参考:10-U-Boot核心全局数据结构详解.md
结构体定义: include/asm-generic/global_data.h
typedef struct global_data {
bd_t *bd; /* 板级数据指针 */
unsigned long flags; /* 标志位(GD_FLG_RELOC 等)*/
unsigned int baudrate; /* 串口波特率 */
unsigned long cpu_clk; /* CPU 时钟频率 */
unsigned long bus_clk; /* 总线时钟频率 */
unsigned long ram_size; /* DDR 大小 */
unsigned long ram_top; /* DDR 顶部地址 */
unsigned long relocaddr; /* U-Boot 重定位目标地址 */
unsigned long start_addr_sp; /* 新栈地址 */
unsigned long reloc_off; /* 重定位偏移量 */
const void *fdt_blob; /* 设备树地址 */
struct udevice *cur_serial_dev; /* 当前串口设备 */
void **jt; /* 跳转表指针 */
char env_buf[32]; /* 环境变量缓冲区 */
// ... 更多字段,详见完整结构体文档
} gd_t;
GD 在寄存器中的存放:
- ARM 架构:
r9寄存器固定指向gd - 访问方式: 通过
DECLARE_GLOBAL_DATA_PTR宏
/* arch/arm/include/asm/global_data.h */
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
/* 使用示例 */
void some_function(void)
{
DECLARE_GLOBAL_DATA_PTR; /* 声明 gd 指针 */
printf("RAM size: 0x%lx\n", gd->ram_size);
printf("Relocated to: 0x%lx\n", gd->relocaddr);
}
gd 的作用:
- 启动参数传递 – 在各初始化阶段传递关键信息
- 内存布局管理 – 记录 DDR 大小、重定位地址等
- 硬件状态记录 – CPU 频率、时钟、外设状态
- 跨阶段通信 – 重定位前后的数据传递桥梁
gd 在启动流程中的演变:
| 阶段 | gd 的状态 | 关键字段 |
|---|---|---|
_main 开始 | 分配空间,清零 | gd 地址固定在 SRAM 顶部 |
board_init_f | 填充基本信息 | baudrate, ram_size, ram_top |
setup_reloc() | 计算重定位参数 | relocaddr, reloc_off, start_addr_sp |
relocate_code | 复制到新地址 | r9 更新为新 gd 地址 |
board_init_r | 完整可用 | `flags |
常用标志位:
#define GD_FLG_RELOC 0x00001 /* 代码已重定位 */
#define GD_FLG_ENV_READY 0x00080 /* 环境变量已加载 */
#define GD_FLG_SERIAL_READY 0x00100 /* 串口已就绪 */
/* 检查标志位 */
if (gd->flags & GD_FLG_RELOC) {
printf("Code has been relocated\n");
}
5. Stage 4: 重定位前初始化 (board_init_f)
5.1 初始化序列
文件位置: common/board_f.c
static init_fnc_t init_sequence_f[] = {
setup_mon_len, /* 设置 monitor 长度 */
fdtdec_setup, /* 设备树解析 */
initf_malloc, /* 早期 malloc 初始化 */
arch_cpu_init, /* 架构相关 CPU 初始化 */
mark_bootstage, /* 启动阶段标记 */
board_early_init_f, /* 板级早期初始化 */
timer_init, /* 定时器初始化 */
env_init, /* 环境变量初始化 */
init_baud_rate, /* 波特率初始化 */
serial_init, /* 串口初始化 */
console_init_f, /* 控制台初始化(阶段1) */
display_options, /* 显示版本信息 */
print_cpuinfo, /* 打印CPU信息 */
dram_init, /* DRAM 初始化 */
setup_dest_addr, /* 设置重定位目标地址 */
reserve_mmu, /* 预留 MMU 页表空间 */
reserve_uboot, /* 预留 U-Boot 代码空间 */
reserve_malloc, /* 预留 malloc 空间 */
reserve_global_data, /* 预留全局数据空间 */
reserve_fdt, /* 预留设备树空间 */
reserve_stacks, /* 预留栈空间 */
setup_dram_config, /* 设置 DRAM 配置 */
display_new_sp, /* 显示新的栈指针 */
reloc_fdt, /* 重定位设备树 */
setup_reloc, /* 设置重定位参数 */
NULL,
};
5.2 初始化流程图
flowchart TD
A[board_init_f 开始] --> B[setup_mon_len<br/>计算U-Boot大小]
B --> C[arch_cpu_init<br/>CPU架构初始化]
C --> D[timer_init<br/>定时器初始化]
D --> E[serial_init<br/>串口初始化]
E --> F[console_init_f<br/>控制台初始化]
F --> G[display_options<br/>显示版本信息]
G --> H[print_cpuinfo<br/>打印CPU信息]
H --> I[dram_init<br/>DRAM初始化]
I --> J[计算内存布局]
J --> K[reserve_xxx<br/>预留各种空间]
K --> L[setup_reloc<br/>设置重定位参数]
style A fill:#f99,stroke:#333,stroke-width:2px
style I fill:#9f9,stroke:#333,stroke-width:2px
style L fill:#99f,stroke:#333,stroke-width:2px
5.3 内存预留计算
graph TD
A[RAM 顶部<br/>gd->ram_top] --> B[预留 TLB<br/>MMU页表 16KB]
B --> C[预留 U-Boot<br/>代码+数据]
C --> D[预留 Malloc<br/>堆空间]
D --> E[预留 Board Info<br/>bd_t结构]
E --> F[预留 GD<br/>gd_t结构]
F --> G[预留 FDT<br/>设备树]
G --> H[预留栈空间<br/>16KB]
H --> I[最终可用RAM底部]
style A fill:#f99,stroke:#333,stroke-width:2px
style C fill:#9f9,stroke:#333,stroke-width:2px
style I fill:#99f,stroke:#333,stroke-width:2px
计算示例:
假设 DDR 内存为 512MB (0x80000000 – 0xA0000000)
RAM 顶部: 0xA0000000
- TLB (16KB): 0x9FFFC000
- U-Boot (512KB): 0x9FF7C000
- Malloc (32MB): 0x9DF7C000
- Board Info (256B): 0x9DF7BF00
- Global Data (256B): 0x9DF7BE00
- FDT (64KB): 0x9DF6BE00
- Stack (16KB): 0x9DF67E00
= 新的栈顶: 0x9DF67E00
5.4 关键函数详解
dram_init() – DRAM 初始化:
int dram_init(void)
{
/* 从设备树或板级配置获取 DRAM 大小 */
gd->ram_size = get_ram_size((long *)PHYS_SDRAM, PHYS_SDRAM_SIZE);
return 0;
}
setup_dest_addr() – 计算重定位地址:
static int setup_dest_addr(void)
{
gd->ram_top = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
gd->relocaddr = gd->ram_top;
return 0;
}
6. Stage 5: 代码重定位 (relocate_code)
6.1 重定位流程
文件位置: arch/arm/lib/relocate.S
ENTRY(relocate_code)
/*
* r0 = 新的 GD 地址
* r1 = 保留(旧参数)
* r2 = 重定位目标地址
*/
ldr r1, =__image_copy_start /* 源地址起始 */
subs r4, r2, r1 /* r4 = 重定位偏移 */
beq relocate_done /* 如果不需要重定位则跳过 */
ldr r1, =__image_copy_start /* 源起始 */
ldr r2, =__image_copy_end /* 源结束 */
copy_loop:
ldmia r1!, {r10-r11} /* 从源加载 */
stmia r6!, {r10-r11} /* 写入目标 */
cmp r1, r2 /* 是否完成? */
blo copy_loop
/* 修正重定位表 */
ldr r2, =__rel_dyn_start
ldr r3, =__rel_dyn_end
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) = (地址,信息) */
and r1, r1, #0xff
cmp r1, #23 /* R_ARM_RELATIVE? */
bne fixnext
ldr r1, [r0] /* 读取需要修正的地址 */
add r1, r1, r4 /* 加上偏移量 */
str r1, [r0] /* 写回 */
fixnext:
cmp r2, r3
blo fixloop
relocate_done:
bx lr
6.2 重定位过程图解
sequenceDiagram
participant Flash as Flash空间
participant Old as 旧位置(SRAM)
participant New as 新位置(DDR高地址)
participant Rel as 重定位表
Flash->>Old: 1. 初始加载
Note over Old: 在Flash/SRAM中执行
Old->>New: 2. 复制代码段
Note over New: 复制.text .rodata .data
Old->>New: 3. 复制数据段
Old->>Rel: 4. 读取重定位表
Rel->>New: 5. 修正地址引用
Note over New: 修正全局变量、函数指针
New->>New: 6. 跳转到新位置继续执行
6.3 内存布局变化
重定位前:
+-------------------+ 0x00000000
| Flash/ROM |
| U-Boot 代码 | ← 当前执行位置
+-------------------+ 0x00100000
| |
| ... |
| |
+-------------------+ CONFIG_SYS_INIT_SP_ADDR
| 栈 (SRAM) |
| GD |
+-------------------+
重定位后:
+-------------------+ 0x80000000 (DDR起始)
| Linux 内核区 |
| |
+-------------------+
| ... |
| |
+-------------------+ 0x9DF67E00
| U-Boot 栈 | ← 新的栈位置
+-------------------+
| GD |
| BD |
+-------------------+
| 设备树 FDT |
+-------------------+
| Malloc 堆 |
+-------------------+
| U-Boot 代码 | ← 重定位后的执行位置
| .text |
| .rodata |
| .data |
+-------------------+ gd->relocaddr
| MMU 页表 |
+-------------------+ 0xA0000000 (DDR顶部)
6.4 重定位表 (.rel.dyn)
重定位表记录了所有需要修正的地址:
struct elf32_rel {
uint32_t r_offset; /* 需要修正的地址 */
uint32_t r_info; /* 重定位类型 */
};
重定位类型:
R_ARM_RELATIVE (23): 相对地址,需要加上重定位偏移R_ARM_ABS32 (2): 绝对地址
示例:
原始代码中的全局变量引用:
ldr r0, =global_var
重定位前: global_var 地址 = 0x00012345
重定位后: global_var 地址 = 0x9FF8E345
偏移量 = 0x9FF7C000
7. Stage 6: 重定位后初始化 (board_init_r)
7.1 初始化序列
文件位置: common/board_r.c
init_fnc_t init_sequence_r[] = {
initr_trace, /* 跟踪初始化 */
initr_reloc, /* 重定位完成标记 */
initr_caches, /* 使能 Cache */
initr_reloc_global_data, /* 重定位全局数据 */
initr_malloc, /* malloc 初始化 */
initr_dm, /* 驱动模型初始化 */
board_init, /* 板级初始化 */
stdio_init_tables, /* stdio 设备表初始化 */
initr_serial, /* 串口完全初始化 */
initr_announce, /* 打印重定位信息 */
power_init_board, /* 电源管理初始化 */
initr_flash, /* Flash 初始化 */
initr_nand, /* NAND 初始化 */
initr_mmc, /* MMC/SD 初始化 */
initr_env, /* 环境变量完全初始化 */
stdio_add_devices, /* 添加 stdio 设备 */
console_init_r, /* 控制台完全初始化 */
interrupt_init, /* 中断初始化 */
initr_enable_interrupts, /* 使能中断 */
initr_net, /* 网络初始化 */
run_main_loop, /* 进入主循环 */
NULL,
};
7.2 关键初始化详解
flowchart TD
A[board_init_r 开始] --> B[initr_caches<br/>使能I/D Cache]
B --> C[initr_malloc<br/>完整malloc初始化]
C --> D[board_init<br/>板级硬件初始化]
D --> E[initr_serial<br/>串口完全配置]
E --> F[initr_flash<br/>NOR Flash初始化]
F --> G[initr_mmc<br/>SD/eMMC初始化]
G --> H[initr_env<br/>从存储加载环境变量]
H --> I[console_init_r<br/>控制台完整功能]
I --> J[initr_net<br/>网络协议栈]
J --> K[run_main_loop<br/>进入命令循环]
style A fill:#f99,stroke:#333,stroke-width:2px
style H fill:#9f9,stroke:#333,stroke-width:2px
style K fill:#99f,stroke:#333,stroke-width:3px
7.3 使能 Cache
static int initr_caches(void)
{
/* 使能 D-Cache */
dcache_enable();
/* 使能 I-Cache (如果未在 start.S 中使能)*/
icache_enable();
return 0;
}
Cache 的作用:
graph LR
A[CPU] --> B{Cache?}
B -->|命中| C[从Cache读取<br/>1个周期]
B -->|未命中| D[从DDR读取<br/>~100个周期]
D --> E[更新Cache]
E --> C
style C fill:#9f9,stroke:#333,stroke-width:2px
style D fill:#f99,stroke:#333,stroke-width:2px
7.4 环境变量初始化
static int initr_env(void)
{
/* 从存储设备加载环境变量 */
env_relocate();
/* 设置 bootdelay 等关键变量 */
if (getenv("bootdelay") != NULL)
gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
return 0;
}
环境变量存储位置:
| 存储介质 | 位置 | 备份 |
|---|---|---|
| NOR Flash | 专用分区 | 支持冗余 |
| NAND Flash | 专用块 | 支持冗余 |
| eMMC/SD | 专用分区 | 支持冗余 |
| SPI Flash | 固定偏移 | 可选冗余 |
8. Stage 7: 主循环 (main_loop)
8.1 主循环流程
文件位置: common/main.c
void main_loop(void)
{
/* 自动启动处理 */
autoboot_command(stored_bootdelay);
/* 进入命令循环 */
cli_loop();
}
8.2 自动启动流程
flowchart TD
A[main_loop 开始] --> B{检查 bootdelay}
B -->|>0| C[倒计时]
B -->|=-1| D[跳过自动启动]
B -->|=0| E[立即启动]
C --> F{用户按键?}
F -->|是| D
F -->|超时| G[执行 bootcmd]
G --> H{启动成功?}
H -->|是| I[启动 Linux]
H -->|否| D
D --> J[命令行循环]
style A fill:#f99,stroke:#333,stroke-width:2px
style G fill:#9f9,stroke:#333,stroke-width:2px
style J fill:#99f,stroke:#333,stroke-width:2px
8.3 自动启动代码
const char *s;
int bootdelay;
/* 获取 bootdelay 环境变量 */
bootdelay = getenv_ulong("bootdelay", 10, CONFIG_BOOTDELAY);
if (bootdelay >= 0) {
s = getenv("bootcmd");
/* 倒计时 */
if (abortboot(bootdelay)) {
/* 用户中断,进入命令行 */
return;
}
/* 执行启动命令 */
run_command_list(s, -1, 0);
}
8.4 命令行循环
void cli_loop(void)
{
/* 解析器初始化 */
parse_file_outer();
/* 永不返回 */
}
命令行交互:
sequenceDiagram
participant User as 用户
participant CLI as 命令行
participant Parser as 解析器
participant Cmd as 命令处理
loop 命令循环
CLI->>User: 显示提示符 "=> "
User->>CLI: 输入命令
CLI->>Parser: 解析命令行
Parser->>Cmd: 查找命令
Cmd->>Cmd: 执行命令函数
Cmd->>CLI: 返回结果
CLI->>User: 显示输出
end
9. 完整启动时序图
sequenceDiagram
participant HW as 硬件
participant Boot as BootROM
participant Start as start.S
participant Main as _main
participant BoardF as board_init_f
participant Reloc as relocate_code
participant BoardR as board_init_r
participant MainLoop as main_loop
HW->>Boot: 1. 上电复位
Boot->>Start: 2. 跳转到 U-Boot
Start->>Start: 3. CPU模式设置
Start->>Start: 4. CP15初始化
Start->>Start: 5. 底层硬件初始化
Start->>Main: 6. bl _main
Main->>Main: 7. 设置栈
Main->>BoardF: 8. bl board_init_f
BoardF->>BoardF: 9. 串口初始化
BoardF->>BoardF: 10. DRAM初始化
BoardF->>BoardF: 11. 计算内存布局
BoardF->>Main: 12. 返回
Main->>Reloc: 13. bl relocate_code
Reloc->>Reloc: 14. 复制代码到DDR
Reloc->>Reloc: 15. 修正地址
Reloc->>Main: 16. 返回(在新位置)
Main->>BoardR: 17. bl board_init_r
BoardR->>BoardR: 18. 使能Cache
BoardR->>BoardR: 19. 初始化外设
BoardR->>BoardR: 20. 加载环境变量
BoardR->>MainLoop: 21. run_main_loop
MainLoop->>MainLoop: 22. 自动启动/命令行
Note over HW,MainLoop: 启动完成,进入交互模式
10. 内存布局演变全景
graph TD
A[阶段1: 复位后<br/>PC=0x00000000] --> B[Flash中执行]
C[阶段2: lowlevel_init<br/>设置栈] --> D[SRAM栈]
E[阶段3: board_init_f<br/>DDR初始化] --> F[计算重定位地址]
G[阶段4: relocate_code<br/>代码重定位] --> H[DDR高地址执行]
I[阶段5: board_init_r<br/>完整初始化] --> J[所有资源可用]
K[最终布局] --> L[0x80000000: Kernel加载区]
K --> M[0x9DF00000: Malloc堆]
K --> N[0x9FF00000: U-Boot代码]
K --> O[0xA0000000: DDR顶部]
style A fill:#f99,stroke:#333,stroke-width:2px
style G fill:#9f9,stroke:#333,stroke-width:2px
style K fill:#99f,stroke:#333,stroke-width:2px
11. 启动日志示例
U-Boot 2016.03 (Aug 20 2019 - 19:46:00 +0800)
CPU: Freescale i.MX6DL rev1.3 at 792 MHz
Reset cause: POR
Board: MX6SABRESD
I2C: ready
DRAM: 512 MiB
MMC: FSL_SDHC: 0, FSL_SDHC: 1
*** Warning - bad CRC, using default environment
Display: HDMI (1024x768)
In: serial
Out: serial
Err: serial
Net: FEC [PRIME]
Normal Boot
Hit any key to stop autoboot: 0
=>
日志解析:
| 日志行 | 对应阶段 |
|---|---|
U-Boot 2016.03 | 版本信息(board_init_f) |
CPU: Freescale i.MX6DL | print_cpuinfo() |
DRAM: 512 MiB | dram_init() |
MMC: FSL_SDHC | initr_mmc() |
In: serial | console_init_r() |
Net: FEC | initr_net() |
Hit any key | autoboot 倒计时 |
=> | 命令行提示符 |
12. 启动流程关键函数总结
mindmap
root((U-Boot启动))
汇编阶段
reset
cpu_init_cp15
lowlevel_init
_main
重定位前
board_init_f
dram_init
reserve_xxx
setup_reloc
重定位
relocate_code
修正地址引用
重定位后
board_init_r
initr_caches
initr_env
board_init
主循环
main_loop
autoboot
cli_loop
下一步
接下来我们将进行 U-Boot 移植实战教学,手把手教你如何为一个虚拟的开发板移植 U-Boot。
13. U-Boot 启动 Linux 内核
💡 重要概念: 启动内核时使用
bootm_headers_t结构体管理镜像信息。详细说明请参考:10-U-Boot核心全局数据结构详解.md
13.1 启动流程概览
flowchart TD
A[U-Boot 命令行] --> B[加载内核镜像]
B --> C[加载设备树]
C --> D[准备启动参数]
D --> E[设置寄存器]
E --> F[跳转到内核]
F --> G[Linux 内核接管]
style A fill:#f99,stroke:#333,stroke-width:2px
style D fill:#9f9,stroke:#333,stroke-width:2px
style F fill:#99f,stroke:#333,stroke-width:3px
style G fill:#fcf,stroke:#333,stroke-width:3px
13.2 bootm_headers_t 结构体在内核启动中的作用
文件位置: include/image.h
全局变量: bootm_headers_t images; (定义在 common/bootm.c)
核心字段:
typedef struct bootm_headers {
/* 内核镜像信息 */
image_info_t os; /* OS 镜像信息 */
ulong ep; /* 内核入口点地址 */
/* Ramdisk 信息 */
ulong rd_start, rd_end; /* ramdisk 起始/结束地址 */
/* 设备树信息 */
char *ft_addr; /* 设备树地址 */
ulong ft_len; /* 设备树长度 */
/* 启动状态机 */
int state; /* BOOTM_STATE_* 标志 */
/* 内存管理 */
struct lmb lmb; /* 逻辑内存块管理 */
} bootm_headers_t;
启动状态机:
#define BOOTM_STATE_START 0x00000001 /* 开始启动 */
#define BOOTM_STATE_FINDOS 0x00000002 /* 查找 OS 镜像 */
#define BOOTM_STATE_FINDOTHER 0x00000004 /* 查找 ramdisk/fdt */
#define BOOTM_STATE_LOADOS 0x00000008 /* 加载 OS 镜像 */
#define BOOTM_STATE_RAMDISK 0x00000010 /* 处理 ramdisk */
#define BOOTM_STATE_FDT 0x00000020 /* 处理设备树 */
#define BOOTM_STATE_OS_GO 0x00000400 /* 跳转到 OS */
13.3 bootz 命令
命令格式:
bootz [kernel_addr] [initrd_addr] [fdt_addr]
参数说明:
kernel_addr: 内核镜像地址(zImage)initrd_addr: initramfs 地址(可选,用-表示无)fdt_addr: 设备树地址(.dtb)
示例:
# 基本启动
=> bootz 0x80800000 - 0x83000000
# 带 ramdisk 启动
=> bootz 0x80800000 0x84000000 0x83000000
13.3 启动参数传递
bootargs 环境变量
# 查看启动参数
=> printenv bootargs
bootargs=console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
# 设置启动参数
=> setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
常用参数说明:
| 参数 | 含义 | 示例 |
|---|---|---|
| console= | 控制台设备 | ttymxc0,115200 |
| root= | 根文件系统 | /dev/mmcblk1p2 |
| rootwait | 等待根设备 | 无值 |
| rw | 读写挂载 | 无值 |
| ip= | 网络配置 | dhcp |
ARM 寄存器约定
/* Linux 内核启动时寄存器状态 */
r0 = 0 // 必须为 0
r1 = 0xFFFFFFFF // 使用设备树标记(或 machid)
r2 = 0x83000000 // 设备树物理地址
代码实现: arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
DECLARE_GLOBAL_DATA_PTR;
unsigned long machid = gd->bd->bi_arch_number; /* 从 gd 获取机器 ID */
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
/* 1. 获取内核入口点(从 images 结构体)*/
kernel_entry = (void (*)(int, int, uint))images->ep;
/* 2. 准备 r2 寄存器(设备树地址)*/
if (images->ft_len)
r2 = (unsigned long)images->ft_addr; /* 从 images 获取 */
else
r2 = gd->bd->bi_boot_params; /* 从 gd 获取 */
printf("## Transferring control to Linux (at address %08lx)...\n",
(ulong)kernel_entry);
printf(" r0: 0x%08x\n", 0);
printf(" r1: 0x%08lx (machine id)\n", machid);
printf(" r2: 0x%08lx (device tree)\n", r2);
/* 3. 清理 CPU 状态 */
cleanup_before_linux();
/* 4. 跳转到内核 */
kernel_entry(0, machid, r2);
/* 此处永不返回 */
}
gd 和 bootm_headers 的协作:
gd->bd->bi_arch_number→ 提供机器类型 IDgd->bd->bi_boot_params→ 备用启动参数地址images->ep→ 内核入口点images->ft_addr→ 设备树地址images->rd_start/rd_end→ ramdisk 地址范围
13.4 完整启动脚本示例
SD 卡启动
# 配置变量
=> setenv kernel_addr 0x80800000
=> setenv fdt_addr 0x83000000
=> setenv kernel_file zImage
=> setenv fdt_file imx6ull-myboard.dtb
# 启动参数
=> setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
# 启动命令
=> setenv bootcmd 'mmc dev 1; fatload mmc 1:1 ${kernel_addr} ${kernel_file}; fatload mmc 1:1 ${fdt_addr} ${fdt_file}; bootz ${kernel_addr} - ${fdt_addr}'
=> saveenv
=> reset
网络启动(TFTP + NFS)
# 网络配置
=> setenv ipaddr 192.168.1.100
=> setenv serverip 192.168.1.10
=> setenv netmask 255.255.255.0
# NFS 根文件系统
=> setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=${serverip}:/nfsroot,v3,tcp ip=${ipaddr}'
# 启动命令
=> setenv netboot 'tftp ${kernel_addr} zImage; tftp ${fdt_addr} imx6ull-myboard.dtb; bootz ${kernel_addr} - ${fdt_addr}'
=> run netboot
13.5 启动过程内存布局
DDR 物理地址(启动 Linux 时)
0x80000000 ┌─────────────────┐
│ 预留区域 │
0x80800000 ├─────────────────┤ ← kernel_addr
│ zImage 内核 │ (4-8 MB)
0x82000000 ├─────────────────┤
│ │
│ 未使用空间 │
│ │
0x83000000 ├─────────────────┤ ← fdt_addr
│ Device Tree │ (64 KB)
0x83010000 ├─────────────────┤
│ │
0x84000000 ├─────────────────┤ ← ramdisk_addr (可选)
│ Ramdisk │ (8-32 MB)
0x86000000 ├─────────────────┤
│ │
│ 空闲空间 │
│ │
0x9FF00000 ├─────────────────┤
│ U-Boot 代码 │
0xA0000000 └─────────────────┘
13.6 设备树传递
U-Boot 修改设备树的 /chosen 节点:
chosen {
bootargs = "console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw";
linux,initrd-start = <0x84000000>; /* 如果有 ramdisk */
linux,initrd-end = <0x84800000>;
stdout-path = &uart1;
};
13.7 启动前清理
/*
* U-Boot 跳转到内核前的清理工作
*/
void cleanup_before_linux(void)
{
disable_interrupts(); // 禁用中断
icache_disable(); // 禁用 I-Cache
dcache_disable(); // 禁用 D-Cache
mmu_disable(); // 禁用 MMU
flush_cache_all(); // 刷新所有缓存
}
/*
* 跳转到内核
*/
kernel_entry(0, 0xFFFFFFFF, 0x83000000);
13.8 调试启动问题
常见问题:
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 卡在 “Starting kernel…” | 内核损坏 | 重新加载内核 |
| 无输出 | console 参数错误 | 检查 bootargs |
| Kernel panic | 根文件系统问题 | 检查 root= 参数 |
| 设备不工作 | 设备树不匹配 | 使用正确的 .dtb |
调试命令:
# 查看内核是否正确加载
=> md 0x80800000 10
# 查看设备树
=> fdt addr 0x83000000
=> fdt print /chosen
# 检查内存
=> mtest 0x80000000 0x90000000
# 单步执行
=> mmc dev 1
=> fatload mmc 1:1 0x80800000 zImage
=> fatload mmc 1:1 0x83000000 imx6ull-myboard.dtb
=> bootz 0x80800000 - 0x83000000
13.9 启动流程关键代码
bootz 命令实现:
/* cmd/bootm.c */
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
/* 解析参数 */
if (argc < 2)
return CMD_RET_USAGE;
/* 调用启动状态机 */
return do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_START |
BOOTM_STATE_FINDOS |
BOOTM_STATE_LOADOS |
BOOTM_STATE_OS_GO,
&images, 1);
}
/* arch/arm/lib/bootm.c */
static void boot_jump_linux(bootm_headers_t *images)
{
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
kernel_entry = (void (*)(int, int, uint))images->ep;
/* 清理环境 */
cleanup_before_linux();
/* 设备树地址 */
r2 = (unsigned long)images->ft_addr;
printf("Starting kernel ...\n\n");
/* 跳转!r0=0, r1=0xFFFFFFFF, r2=DTB地址 */
kernel_entry(0, 0xFFFFFFFF, r2);
}
13.10 Linux 内核接管
内核启动入口(汇编):
/* arch/arm/kernel/head.S */
ENTRY(stext)
/* 检查 CPU */
mrc p15, 0, r9, c0, c0
/* 创建页表 */
bl __create_page_tables
/* 启用 MMU */
bl __enable_mmu
/* 跳转到 C 代码 */
b start_kernel
ENDPROC(stext)
内核 C 入口:
/* init/main.c */
asmlinkage __visible void __init start_kernel(void)
{
setup_arch(&command_line); // 架构初始化
setup_command_line(command_line); // 解析启动参数
parse_early_param(); // 早期参数解析
mm_init(); // 内存管理初始化
sched_init(); // 调度器初始化
console_init(); // 控制台初始化
rest_init(); // 启动 init 进程
}
14. 总结
完整启动链路
上电 → BootROM → DCD初始化DDR → 加载U-Boot →
汇编初始化 → C环境准备 → DDR探测 → 代码重定位 →
外设初始化 → 命令行 → 加载内核 → 设置参数 →
跳转内核 → Linux启动 → 用户空间
关键阶段
- 硬件初始化 – BootROM + DCD (DDR 初始化)
- 汇编启动 – 设置 CPU 模式、栈、异常向量
- C 初始化 – board_init_f (重定位前)
- 代码重定位 – 从 Flash 到 DDR 高地址
- 完整初始化 – board_init_r (重定位后)
- 启动内核 – bootz + 参数传递 + 跳转
核心数据结构
- gd_t: 全局数据(r9 寄存器)
- bd_t: 板级信息
- bootargs: 内核启动参数
- 设备树: 硬件描述
🎉 U-Boot 完整启动流程详解完成!
从上电复位到 Linux 内核启动,整个嵌入式系统启动过程已全部呈现。
提示: 理解启动流程是 U-Boot 开发和调试的基础,建议结合实际硬件跟踪启动过程。
详细调用过程整理
u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)
|–>_start:(arch/arm/lib/vectors.S)
|–>reset(arch/arm/cpu/armv7/start.S)
|–>save_boot_params(arch/arm/cpu/armv7/start.S)/将引导参数保存到内存中/
|–>save_boot_params_ret(arch/arm/cpu/armv7/start.S)
|–>cpu_init_cp15(arch/arm/cpu/armv7/start.S)/初始化/
|–>cpu_init_crit(arch/arm/cpu/armv7/start.S)
|–>lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)
|–>_main(arch/arm/lib/crt0.S)
|–>board_init_f_alloc_reserve(common/init/board_init.c)/为u-boot的gd结构体分配空间/
|–>board_init_f_init_reserve(common/init/board_init.c) /将gd结构体清零/
|–>board_init_f(common/board_f.c)
|–>initcall_run_list(include/initcall.h) /初始化序列函数/
|–>init_sequence_f /* 初始化序列函数数组 */
|–>board_early_init_f(board/freescale/mx6ull_toto/mx6ull_toto.c)/初始化串口的IO配置/
|–>timer_init(arch/arm/imx-common/timer.c) /初始化内核定时器,为uboot提供时钟节拍/
|–>init_baud_rate(common/board_f.c) /初始化波特率/
|–>serial_init(drivers/serial/serial.c) /初始化串口通信设置/
|–>console_init_f(common/console.c) /初始化控制台/
|–>…
|–>relocate_code(arch/arm/lib/relocate.S) /主要完成镜像拷贝和重定位/
|–>relocate_vectors(arch/arm/lib/relocate.S)/重定位向量表/
|–>board_init_r(common/board_r.c)/板级初始化/
|–>initcall_run_list(include/initcall.h)/初始化序列函数/
|–>init_sequence_r/序列函数/
|–>initr_reloc(common/board_r.c) /设置 gd->flags,标记重定位完成/
|–>serial_initialize(drivers/serial/serial-uclass.c)/初始化串口/
|–>serial_init(drivers/serial/serial-uclass.c) /初始化串口/
|–>initr_mmc(common/board_r.c) /初始化emmc/
|–>mmc_initialize(drivers/mmc/mmc.c)
|–>mmc_do_preinit(drivers/mmc/mmc.c)
|–>mmc_start_init(drivers/mmc/mmc.c)
|–>console_init_r(common/console.c) /初始化控制台/
|–>interrupt_init(arch/arm/lib/interrupts.c) /初始化中断/
|–>initr_net(common/board_r.c) /初始化网络设备/
|–>eth_initialize(net/eth-uclass.c)
|–>eth_common_init(net/eth_common.c)
|–>phy_init(drivers/net/phy/phy.c)
|–>uclass_first_device_check(drivers/core/uclass.c)
|–>uclass_find_first_device(drivers/core/uclass.c)
|–>device_probe(drivers/core/device.c)
|–>device_of_to_plat(drivers/core/device.c)
|–>drv->of_to_plat
|–>fecmxc_of_to_plat(drivers/net/fec_mxc.c)/解析设备树信息/
|–>device_get_uclass_id(drivers/core/device.c)
|–>uclass_pre_probe_device(drivers/core/uclass.c)
|–>drv->probe(dev)
/drivers/net/fec_mxc.c/
U_BOOT_DRIVER(fecmxc_gem) = {
.name = “fecmxc”,
.id = UCLASS_ETH,
.of_match = fecmxc_ids,
.of_to_plat = fecmxc_of_to_plat,
.probe = fecmxc_probe,
.remove = fecmxc_remove,
.ops = &fecmxc_ops,
.priv_auto = sizeof(struct fec_priv),
.plat_auto = sizeof(struct eth_pdata),
};
|–>fecmxc_probe(drivers/net/fec_mxc.c)/探测和初始化/
|–>fec_get_miibus(drivers/net/fec_mxc.c)
|–>mdio_alloc(drivers/net/fec_mxc.c)
|–>bus->read = fec_phy_read;
|–>bus->write = fec_phy_write;
|–>mdio_register(common/miiphyutil.c)
|–>fec_mii_setspeed(drivers/net/fec_mxc.c)
|–>fec_phy_init(drivers/net/fec_mxc.c)
|–>device_get_phy_addr(drivers/net/fec_mxc.c)
|–>phy_connect(drivers/net/phy/phy.c)
|–>phy_find_by_mask(drivers/net/phy/phy.c)
|–>bus->reset(bus)
|–>get_phy_device_by_mask(drivers/net/phy/phy.c)
|–>create_phy_by_mask(drivers/net/phy/phy.c)
|–>phy_device_create(drivers/net/phy/phy.c)
|–>phy_probe(drivers/net/phy/phy.c)
|–>phy_connect_dev(drivers/net/phy/phy.c)
|–>phy_reset(drivers/net/phy/phy.c)
|–>phy_config(drivers/net/phy/phy.c)
|–>board_phy_config(drivers/net/phy/phy.c)
|–>phydev->drv->config(phydev)
/drivers/net/phy/smsc.c/
static struct phy_driver lan8710_driver = {
.name = “SMSC LAN8710/LAN8720”,
.uid = 0x0007c0f0,
.mask = 0xffff0,
.features = PHY_BASIC_FEATURES,
.config = &genphy_config_aneg,
.startup = &genphy_startup,
.shutdown = &genphy_shutdown,
};
|–>genphy_config_aneg(drivers/net/phy/phy.c)
|–>phy_reset(需要手动调用)(drivers/net/phy/phy.c)
|–>genphy_setup_forced(drivers/net/phy/phy.c)
|–>genphy_config_advert(drivers/net/phy/phy.c)
|–>genphy_restart_aneg(drivers/net/phy/phy.c)
|–>uclass_post_probe_device(drivers/core/uclass.c)
|–>uc_drv->post_probe(drivers/core/uclass.c)
/net/eth-uclass.c/
UCLASS_DRIVER(ethernet) = {
.name = “ethernet”,
.id = UCLASS_ETH,
.post_bind = eth_post_bind,
.pre_unbind = eth_pre_unbind,
.post_probe = eth_post_probe,
.pre_remove = eth_pre_remove,
.priv_auto = sizeof(struct eth_uclass_priv),
.per_device_auto = sizeof(struct eth_device_priv),
.flags = DM_UC_FLAG_SEQ_ALIAS,
};
|–>eth_post_probe(net/eth-uclass.c)
|–>eth_write_hwaddr(drivers/core/uclass.c)
|–>…
|–>run_main_loop(common/board_r.c)/主循环,处理命令/
|–>main_loop(common/main.c)
|–>bootdelay_process(common/autoboot.c) /读取环境变量bootdelay和bootcmd的内容/
|–>autoboot_command(common/autoboot.c) /倒计时按下执行,没有操作执行bootcmd的参数/
|–>abortboot(common/autoboot.c)
|–>printf(“Hit any key to stop autoboot: %2d “, bootdelay);
/到这里就是我们看到uboot延时3s启动内核的地方/
|–>cli_loop(common/cli.c) /倒计时按下space键,执行用户输入命令/