SoC 冷启动全过程硬核教程
·
从芯片上电到操作系统运行的完整链路
一、教程大纲:SoC 冷启动全链路
┌─────────────────────────────────────────────────────────────────────────────┐
│ SoC 冷启动 10 阶段硬核教程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ Phase 0: 上电时序与硬件复位 │
│ Phase 1: BootROM 执行 (芯片固化代码) │
│ Phase 2: SPL/FSBL 第一阶段引导 (DDR 初始化) │
│ Phase 3: 可信启动与安全引导 (Secure Boot / TF-A) │
│ Phase 4: U-Boot 第二阶段引导 (驱动初始化、环境变量) │
│ Phase 5: 设备树传递与内核加载 (DTB + Kernel) │
│ Phase 6: Linux 内核解压与启动 (setup_arch -> start_kernel) │
│ Phase 7: 根文件系统挂载 (initramfs / NFS / eMMC / SD) │
│ Phase 8: init 进程与用户空间初始化 (systemd / busybox / procd) │
│ Phase 9: 产品级优化与量产烧录 (OTA、A/B 分区、安全启动链) │
└─────────────────────────────────────────────────────────────────────────────┘
二、各阶段详解与开源 Demo
Phase 0: 上电时序与硬件复位
核心知识点:
- Power-on Reset (POR) 电路原理
- 电源时序:VDD_CORE → VDD_DDR → VDD_IO 的先后关系
- 时钟树:外部晶振 → PLL → 各模块分频
- 复位信号链:POR → nRESET → WDT → Software Reset
硬件信号分析:
VDD_CORE ──┬───┐
│ │ t1: Core 稳定 (通常 1-10ms)
VDD_DDR ──┘ ├───┐
│ │ t2: DDR 稳定
VDD_IO ──────┘ ├───┐
│ │ t3: IO 稳定
nRESET ──────────┘ └─── 释放复位,CPU 开始取指
开源 Demo:
- 项目:
qemu-system-arm模拟复位序列 - 代码: 使用逻辑分析仪捕获树莓派/全志上电波形
# 使用 PulseView + 逻辑分析仪捕获上电时序
sigrok-cli --driver fx2lafw --config samplerate=24mhz \
--channels 0=VDD_CORE,1=VDD_DDR,2=nRESET,3=CLK_24M \
--triggers 2=falling --wait-trigger -o power_on.sr
Phase 1: BootROM 执行
核心知识点:
- BootROM 是芯片掩膜 ROM,不可修改,通常 32KB-128KB
- 启动介质检测顺序(以全志为例):SPI NOR → SPI NAND → SD/eMMC → USB
- 启动签名验证(Secure Boot 时)
- 加载 SPL 到 SRAM(通常 32KB-64KB 内部 RAM)
全志 H6 BootROM 流程:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ POR 释放 │ --> │ 初始化时钟 │ --> │ 检测启动介质 │
│ │ │ (24MHz->PLL)│ │ (FEL/SPI/SD)│
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌───────────────────────────┘
▼
┌─────────────┐ ┌─────────────┐
│ 加载 SPL │ --> │ 跳转到 SPL │
│ 到 SRAM │ │ (0x20000) │
│ (校验签名) │ │ │
└─────────────┘ └─────────────┘
开源 Demo:
- 项目:
sunxi-tools(全志 BootROM 工具集) - 代码: 读取 BootROM 并进行反汇编分析
# 1. 进入 FEL 模式(全志 USB 烧录模式)
sudo sunxi-fel version
# AWUSBFEX soc=00001667(H6) 00000001 ver=0001 44 08 scratchpad=00007e00 00000000
# 2. 读取 BootROM
sudo sunxi-fel read 0x0000 0x10000 bootrom_h6.bin
# 3. 反汇编 (ARM64)
aarch64-linux-gnu-objdump -b binary -m aarch64 -D bootrom_h6.bin | head -100
Phase 2: SPL / FSBL (First Stage Boot Loader)
核心知识点:
- SPL (Secondary Program Loader) = U-Boot 第一阶段
- FSBL (First Stage Boot Loader) = Xilinx Zynq 术语
- 核心任务:初始化 DDR、时钟、电源,加载 U-Boot 到 DDR
- 代码位置:
arch/arm/cpu/armv8/spl.c
SPL 内存布局(以 512MB DDR 为例):
0x40000000 ├─────────────────────┤ DDR 起始
│ U-Boot │ (通常 1-2MB)
│ (由 SPL 加载) │
0x4A000000 ├─────────────────────┤
│ Kernel │ (FIT Image 或 zImage)
│ (由 U-Boot 加载) │
0x48000000 ├─────────────────────┤
│ DTB │ (设备树二进制)
0x47000000 ├─────────────────────┤
│ U-Boot ENV │ (环境变量)
0x46000000 ├─────────────────────┤
│ SPL │ <-- 最初运行在 SRAM
│ (运行在 SRAM) │
0x00020000 ├─────────────────────┤ SRAM (内部 64KB)
开源 Demo:
- 项目:
u-boot官方 SPL 代码 - 代码: 自定义 SPL 添加 LED 闪烁调试
// arch/arm/mach-sunxi/board.c (SPL 入口)
void board_init_f(ulong dummy)
{
// 1. 初始化串口 (最早的可调试输出)
sunxi_serial_init();
printf("SPL: Hello from SRAM!\n");
// 2. 初始化时钟
clock_init_safe();
// 3. 初始化 DRAM
dram_init();
printf("SPL: DDR initialized, size=%dMB\n",
sunxi_dram_size() / 1024 / 1024);
// 4. 从 eMMC/SD 加载 U-Boot
mmc_initialize();
struct mmc *mmc = find_mmc_device(0);
mmc_init(mmc);
// 读取 U-Boot 到 DDR
ulong uboot_load_addr = CONFIG_SYS_TEXT_BASE; // 0x4A000000
mmc_read(mmc, UBOOT_SECTOR_START, uboot_load_addr, UBOOT_SIZE);
printf("SPL: Jumping to U-Boot at 0x%lx\n", uboot_load_addr);
// 5. 跳转到 U-Boot
((void (*)(void))uboot_load_addr)();
}
Phase 3: 可信启动与安全引导 (TF-A / OP-TEE)
核心知识点:
- TF-A (Trusted Firmware-A): ARM 官方安全固件
- BL1 → BL2 → BL31 → BL32 (OP-TEE) → BL33 (U-Boot)
- TrustZone: Secure World (EL3/Secure EL1) vs Normal World (EL2/EL1/EL0)
- 安全启动链: 每个阶段验证下一阶段的签名
TF-A 启动流程:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ BL1 │ --> │ BL2 │ --> │ BL31 │ --> │ BL32 │ --> │ BL33 │
│ (BootROM)│ │ (SCP/SPL)│ │ (EL3 │ │ (OP-TEE │ │ (U-Boot │
│ │ │ │ │ Runtime)│ │ Secure │ │ Normal)│
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
验证 BL2 验证 BL31 验证 BL32 验证 BL33
开源 Demo:
- 项目:
trusted-firmware-a(ARM 官方) - 代码: 在 QEMU 上运行完整 TrustZone 流程
# 1. 编译 TF-A for QEMU ARM64
git clone https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git
cd trusted-firmware-a
make CROSS_COMPILE=aarch64-linux-gnu- PLAT=qemu \
BL33=../u-boot.bin \
all fip
# 2. 使用 QEMU 运行 (启用 EL3/TrustZone)
qemu-system-aarch64 \
-machine virt,secure=on \
-cpu cortex-a53 \
-smp 4 -m 2G \
-bios build/qemu/release/bl1.bin \
-device loader,file=build/qemu/release/fip.bin,addr=0x44000000 \
-nographic
OP-TEE 安全世界示例:
// optee_examples/hello_world/ta/hello_world_ta.c (Trusted Application)
#include <tee_internal_api.h>
TEE_Result TA_InvokeCommandEntryPoint(void *sess_ctx, uint32_t cmd_id,
uint32_t param_types, TEE_Param params[4])
{
switch (cmd_id) {
case TA_HELLO_WORLD_CMD_INC_VALUE:
// 安全世界操作:加密、密钥存储、安全存储
params[0].value.a++;
break;
}
return TEE_SUCCESS;
}
Phase 4: U-Boot 第二阶段引导
核心知识点:
- U-Boot 是完整的 bootloader,支持驱动、网络、文件系统
- 关键组件:命令行、环境变量、驱动模型 (Driver Model)、FIT Image
- 启动脚本:
bootcmd环境变量定义自动启动流程
U-Boot 启动流程:
board_init_f() -> relocate_code() -> board_init_r() -> main_loop()
│ │ │ │
▼ ▼ ▼ ▼
初始化全局数据 重定位到 DDR 驱动初始化 执行 bootcmd
(GD结构体) (从Flash复制) (MMC/Net/FS) 或进入命令行
开源 Demo:
- 项目:
u-boot官方仓库 - 代码: 自定义 U-Boot 命令和网络启动
// cmd/my_cmd.c (自定义 U-Boot 命令)
#include <common.h>
#include <command.h>
static int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
printf("Hello from U-Boot!\n");
printf("DRAM size: %ld MB\n", gd->ram_size / 1024 / 1024);
printf("Board: %s\n", CONFIG_SYS_BOARD);
// 读取并打印环境变量
char *bootargs = env_get("bootargs");
printf("bootargs: %s\n", bootargs ? bootargs : "(not set)");
return CMD_RET_SUCCESS;
}
U_BOOT_CMD(
hello, 1, 0, do_hello,
"Print hello message and system info",
""
);
// 编译后在 U-Boot 命令行执行:
// => hello
// Hello from U-Boot!
// DRAM size: 1024 MB
// Board: my_board
U-Boot 网络启动脚本:
# 设置环境变量自动从 TFTP 启动
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.1
setenv bootcmd 'tftp 0x42000000 zImage; tftp 0x43000000 dtb; bootz 0x42000000 - 0x43000000'
saveenv
reset
Phase 5: 设备树传递与内核加载
核心知识点:
- 设备树 (DTB): 描述硬件配置,替代硬编码的 machine 类型
- FIT Image (Flattened Image Tree): 将 Kernel + DTB + Initramfs 打包为一个镜像
- U-Boot 加载内核:
bootz(zImage) /booti(Image) /bootm(uImage/FIT)
FIT Image 结构:
/ {
description = "Kernel + DTB + Initramfs";
#address-cells = <1>;
images {
kernel {
description = "Linux Kernel";
data = /incbin/("arch/arm64/boot/Image.gz");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "gzip";
load = <0x40080000>;
entry = <0x40080000>;
hash-1 { algo = "sha256"; };
};
fdt {
description = "Device Tree";
data = /incbin/("arch/arm64/boot/dts/myboard.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x4FA00000>;
hash-1 { algo = "sha256"; };
};
ramdisk {
description = "Initramfs";
data = /incbin/("initramfs.cpio.gz");
type = "ramdisk";
arch = "arm64";
os = "linux";
compression = "gzip";
load = <0x4FE00000>;
hash-1 { algo = "sha256"; };
};
};
configurations {
default = "conf-1";
conf-1 {
description = "Default config";
kernel = "kernel";
fdt = "fdt";
ramdisk = "ramdisk";
signature-1 {
algo = "sha256,rsa2048";
key-name-hint = "dev";
sign-images = "kernel", "fdt", "ramdisk";
};
};
};
};
开源 Demo:
- 项目:
u-boot+mkimage工具 - 代码: 生成并启动 FIT Image
# 1. 生成 FIT Image
mkimage -f kernel.its kernel.itb
# 2. U-Boot 加载 FIT Image
=> tftp 0x42000000 kernel.itb
=> bootm 0x42000000#conf-1
## Loading kernel from FIT Image at 42000000 ...
## Loading fdt from FIT Image ...
## Loading ramdisk from FIT Image ...
Phase 6: Linux 内核解压与启动
核心知识点:
- 入口点:
arch/arm64/kernel/head.S→stext - 启动阶段:
setup_arch()→start_kernel()→rest_init()→kernel_init() - MMU 开启: 从物理地址运行切换到虚拟地址运行
- 设备树解析:
unflatten_device_tree()构建设备节点
ARM64 内核启动汇编:
// arch/arm64/kernel/head.S
ENTRY(stext)
// 1. 关闭中断,确保处于安全状态
msr daifset, #0xf
// 2. 保存设备树物理地址 (x0 = dtb_phys)
mov x21, x0
// 3. 初始化页表 (identity mapping)
bl __create_page_tables
// 4. 使能 MMU
msr sctlr_el1, x0
isb
// 5. 跳转到 C 代码 (虚拟地址)
ldr x8, =__primary_switched
br x8
ENDPROC(stext)
C 语言启动流程:
// init/main.c
asmlinkage __visible void __init start_kernel(void)
{
// 1. 架构相关初始化
setup_arch(&command_line); // 解析设备树,初始化内存
// 2. 核心子系统初始化
mm_init(); // 内存管理
sched_init(); // 调度器
preempt_disable(); // 关闭抢占
// 3. 中断和时钟
early_irq_init();
init_IRQ();
tick_init();
init_timers();
// 4. 控制台和驱动
console_init();
vfs_caches_init();
// 5. 创建 init 进程
rest_init();
}
// rest_init 中:
pid_t kernel_init(void *unused)
{
// 挂载根文件系统
kernel_init_freeable();
// 尝试执行 /sbin/init, /etc/init, /bin/init, /bin/sh
if (execute_command) {
ret = run_init_process(execute_command);
}
// ...
}
开源 Demo:
- 项目:
linux内核源码 - 代码: 添加自定义 early printk 调试
// arch/arm64/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
pr_info("=== My SoC Setup ===\n");
pr_info("DTB phys: 0x%llx\n", __pa(initial_boot_params));
pr_info("Memory: %ld MB\n", memblock_phys_mem_size() / 1024 / 1024);
// 打印设备树节点
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
of_scan_flat_dt(early_init_dt_scan_root, NULL);
// 标准初始化
setup_machine_fdt(__pa(initial_boot_params));
arm64_memblock_init();
paging_init();
}
Phase 7: 根文件系统挂载
核心知识点:
- Initramfs: 内置到内核的临时根文件系统 (cpio 格式)
- 根文件系统类型: ext4, squashfs, jffs2, ubifs, nfs
- 挂载流程:
prepare_namespace()→mount_root()→sys_mount()
Initramfs 构建:
# 1. 创建最小根文件系统
mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,lib,lib64}
# 2. 使用 busybox 或 toybox
cp /path/to/busybox initramfs/bin/
initramfs/bin/busybox --install initramfs/bin/
# 3. 创建 init 脚本
cat > initramfs/init << 'EOF'
#!/bin/sh
echo "Initramfs started!"
# 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
# 查找真实根文件系统
echo "Looking for rootfs on /dev/mmcblk0p2..."
if [ -b /dev/mmcblk0p2 ]; then
mount -t ext4 /dev/mmcblk0p2 /mnt
exec switch_root /mnt /sbin/init
fi
# 如果找不到,进入救援 shell
echo "Rootfs not found, dropping to shell..."
exec /bin/sh
EOF
chmod +x initramfs/init
# 4. 打包为 cpio
cd initramfs && find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
开源 Demo:
- 项目:
buildroot自动生成 initramfs - 代码: 自定义 initramfs 实现根文件系统切换
// init/do_mounts.c (内核源码)
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
int err = sys_mount(name, "/root", fs, flags, data);
if (err)
return err;
sys_chdir("/root");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
return 0;
}
void __init prepare_namespace(void)
{
// 等待设备就绪
wait_for_device_probe();
// 挂载根文件系统
if (initrd_load()) // 如果有 initrd/initramfs
goto out;
// 否则挂载块设备
mount_block_root(root_device_name, root_mountflags);
out:
initrd_end = 0;
mount_root(); // 实际执行 sys_mount
}
Phase 8: Init 进程与用户空间
核心知识点:
- 传统 init: SysVinit (
/sbin/init) → 串行启动 - 现代 init: systemd (并行) / procd (OpenWrt) / busybox init
- 启动目标:
default.target(systemd) /rcS(SysV)
Systemd 启动流程:
systemd (PID 1)
│
├── basic.target (基础系统)
│ ├── sysinit.target (初始化)
│ │ ├── systemd-modules-load.service
│ │ ├── systemd-sysctl.service
│ │ └── ...
│ └── sockets.target / timers.target
│
├── sysinit.target
│ └── ...
│
└── default.target (多用户图形界面)
├── network.target
├── ssh.service
├── my-app.service <-- 你的应用
└── ...
OpenWrt procd 启动:
# /etc/rc.common 示例
#!/bin/sh /etc/rc.common
START=99
STOP=10
start() {
echo "Starting my application..."
/usr/bin/my-app &
}
stop() {
killall my-app
}
开源 Demo:
- 项目:
systemd或procd服务单元 - 代码: 创建自定义 systemd 服务
# /etc/systemd/system/my-embedded-app.service
[Unit]
Description=My Embedded Application
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/my-app
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
# 资源限制(嵌入式关键)
MemoryMax=50M
CPUQuota=50%
OOMScoreAdjust=-100
[Install]
WantedBy=multi-user.target
Phase 9: 产品级优化与量产
核心知识点:
- 启动时间优化: 内核裁剪、异步初始化、精简 initramfs
- A/B 分区升级: 冗余分区,原子更新,失败回滚
- 安全启动链: eFuse 烧录公钥哈希,验证每个启动阶段
- OTA 机制: 双分区 + 恢复模式 + 签名验证
启动时间优化数据:
| 优化项 | 时间节省 | 方法 |
|---|---|---|
| 禁用 printk | ~200ms | quiet 内核参数 |
| 异步驱动 probe | ~300ms | driver_async_probe |
| 精简内核 | ~500ms | 移除未使用驱动 |
| 预读取 DTB | ~50ms | 内嵌到内核 |
| XIP (Execute In Place) | ~100ms | 从 Flash 直接执行 |
| 总计 | ~1-2s |
A/B 分区布局:
eMMC/SD 布局:
┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
│ SPL (1MB) │ U-Boot (2MB)│ Env (1MB) │ Boot A (32MB)│ Boot B (32MB)│
│ │ │ │ Kernel+DTB │ Kernel+DTB │
├─────────────┴─────────────┴─────────────┼─────────────┼─────────────┤
│ Rootfs A (500MB) │ Rootfs B (500MB)│
│ (当前运行) │ (升级目标) │
└─────────────────────────────────────────┴─────────────┘
开源 Demo:
- 项目:
u-boot+swupdate或RAUC - 代码: 实现 A/B 分区切换
// U-Boot 环境变量控制 A/B 启动
// fw_printenv / fw_setenv 工具
// 升级脚本 (在运行系统中)
#!/bin/sh
# update.sh
CURRENT_SLOT=$(fw_printenv -n bootslot)
if [ "$CURRENT_SLOT" = "A" ]; then
TARGET_SLOT="B"
TARGET_PART="mmcblk0p5"
else
TARGET_SLOT="A"
TARGET_PART="mmcblk0p4"
fi
# 下载并验证新镜像
wget -O /tmp/update.img "$UPDATE_URL"
veritysetup verify /tmp/update.img /tmp/update.img.verity
# 写入目标分区
dd if=/tmp/update.img of=/dev/$TARGET_PART bs=1M
# 标记新分区为可启动,但先不切换
fw_setenv bootslot_try $TARGET_SLOT
fw_setenv bootcount 0
# 重启
reboot
// U-Boot 启动逻辑 (env 脚本)
if test "${bootslot_try}" != ""; then
# 尝试新分区
if test "${bootcount}" -lt 3; then
setenv bootcount $((bootcount + 1))
setenv bootslot ${bootslot_try}
saveenv
else
# 尝试 3 次失败,回滚
setenv bootslot_try ""
setenv bootcount 0
saveenv
fi
fi
if test "${bootslot}" = "A"; then
load mmc 0:4 ${kernel_addr_r} Image
load mmc 0:4 ${fdt_addr_r} dtb
else
load mmc 0:5 ${kernel_addr_r} Image
load mmc 0:5 ${fdt_addr_r} dtb
fi
三、完整开源项目推荐
| 项目 | 用途 | 链接 |
|---|---|---|
| U-Boot | Bootloader | https://github.com/u-boot/u-boot |
| Linux Kernel | 操作系统内核 | https://github.com/torvalds/linux |
| TF-A | 可信固件 | https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git |
| OP-TEE | 安全操作系统 | https://github.com/OP-TEE/optee_os |
| Buildroot | 根文件系统构建 | https://github.com/buildroot/buildroot |
| Yocto | 完整发行版构建 | https://git.yoctoproject.org/ |
| swupdate | OTA 升级 | https://github.com/sbabic/swupdate |
| RAUC | 安全 A/B 升级 | https://github.com/rauc/rauc |
| sunxi-tools | 全志芯片工具 | https://github.com/linux-sunxi/sunxi-tools |
| Crust | 全志 SCP 固件 | https://github.com/crust-firmware/crust |
四、学习路径建议
第 1-2 周: 硬件基础
└─ 阅读芯片 datasheet,理解上电时序、时钟树、内存映射
└─ 用逻辑分析仪捕获实际波形
第 3-4 周: BootROM + SPL
└─ 用 sunxi-tools 读取并分析 BootROM
└─ 修改 U-Boot SPL,添加 LED/串口调试
第 5-6 周: U-Boot 深度定制
└─ 添加自定义命令、驱动、环境变量脚本
└─ 实现网络启动 (TFTP/NFS)
第 7-8 周: 内核启动与设备树
└─ 编写自定义设备树
└─ 添加 early printk,跟踪启动流程
第 9-10 周: 根文件系统与 Init
└─ 用 Buildroot 构建最小系统
└─ 实现 initramfs + pivot_root 切换
第 11-12 周: 安全启动与量产
└─ 配置 TF-A + OP-TEE
└─ 实现 Secure Boot 签名链
└─ 集成 OTA 升级机制
需要我针对某个具体芯片(如全志 H6/R528、树莓派 BCM2711、瑞芯微 RK3568)展开更详细的启动代码分析,或者提供完整的 QEMU 模拟环境搭建教程吗?
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)