RK3588 U-Boot 启动介质识别与分区信息获取机制详解
目录
1. 概述
RK3588 是瑞芯微(Rockchip)推出的一款高性能 ARM SoC,支持从多种存储介质启动:
| 存储介质 | 设备类型 | 设备号 | 用途 |
|---|---|---|---|
| eMMC | MMC | dev 0 | 内置存储,最常见的启动介质 |
| SD 卡 | MMC | dev 1 | 外部可移动存储 |
| NAND Flash | RKNAND | dev 0 | Raw NAND Flash |
| SPI NAND | SPINAND | dev 0 | 通过 SPI 接口的 NAND |
| SPI NOR | SPINOR | dev 1 | 通过 SPI 接口的 NOR |
U-Boot 如何识别这些介质? 答案在于以下三方面的协同工作:
- 设备树(Device Tree):定义硬件启动顺序
- SPL(Secondary Program Loader):按顺序尝试各个介质
- 环境变量:保存最终的启动设备信息
2. 分区表基础:GPT 与 MBR
在深入 U-Boot 启动流程之前,我们需要先理解分区表的基本概念。RK3588 平台主要使用 GPT (GUID Partition Table) 分区格式,但也支持传统的 MBR (Master Boot Record) 分区格式。
2.1 磁盘布局与 LBA
LBA (Logical Block Addressing) 是逻辑块寻址的简称,它是磁盘上扇区的编号方式,从 0 开始。
┌─────────────────────────────────────────────────────────────────┐
│ 磁盘 (eMMC/SD 卡) │
│ │
│ LBA 0 │ 第一个扇区 (512 字节) │
│ LBA 1 │ 第二个扇区 (512 字节) │
│ LBA 2 │ 第三个扇区 (512 字节) │
│ ... │ ... │
│ LBA N │ 最后一个扇区 (512 字节) │
│ │
│ 总扇区数 = 磁盘容量 ÷ 512 字节 │
└─────────────────────────────────────────────────────────────────┘
关键转换公式:
- 字节偏移 → LBA:
LBA = 字节偏移 ÷ 512 - LBA → 字节偏移:
字节偏移 = LBA × 512 - 例如:32KB = 32768 字节 = LBA 64
2.2 MBR 分区表结构
存储位置:LBA 0(磁盘的第一个扇区)
┌────────────────────────────────────────────────────────────────┐
│ LBA 0 (512 字节) - MBR 扇区 │
├────────────────────────────────────────────────────────────────┤
│ 偏移 0x000 (446 字节) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ MBR 引导代码 (Boot Code) │ │
│ │ - 如果磁盘可启动,这里包含引导加载程序的第一阶段代码 │ │
│ │ - 对于 GPT 磁盘,这部分通常为 0 │ │
│ └──────────────────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────────┤
│ 偏移 0x1BE (64 字节) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 分区表 (4 个分区条目,每个 16 字节) │ │
│ │ │ │
│ │ 分区条目 1 (偏移 0x1BE, 16 字节): │ │
│ │ - 0x00: 引导标志 (0x80=可启动, 0x00=不可) │ │
│ │ - 0x01-0x03: 起始 CHS 地址 │ │
│ │ - 0x04: 分区类型 (0x07=NTFS, 0x83=Linux, 0xEE=GPT) │ │
│ │ - 0x05-0x07: 结束 CHS 地址 │ │
│ │ - 0x08-0x0B: 起始 LBA (4 字节) │ │
│ │ - 0x0C-0x0F: 分区大小(扇区数, 4 字节) │ │
│ │ │ │
│ │ 分区条目 2 (偏移 0x1CE, 16 字节) │ │
│ │ 分区条目 3 (偏移 0x1DE, 16 字节) │ │
│ │ 分区条目 4 (偏移 0x1EE, 16 字节) │ │
│ └──────────────────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────────┤
│ 偏移 0x1FE (2 字节) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 签名 (Signature): 0x55AA │ │
│ │ - 用于验证 MBR 的有效性 │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
MBR 分区条目结构:
// MBR 分区条目 (16 字节)
struct mbr_partition_entry {
uint8_t boot_indicator; // 0x80 = 可启动, 0x00 = 不可启动
uint8_t starting_chs[3]; // 起始 CHS 地址 (已过时)
uint8_t partition_type; // 分区类型
// 0x01 = FAT12
// 0x06 = FAT16
// 0x07 = NTFS/exFAT
// 0x83 = Linux
// 0xEE = GPT 保护
uint8_t ending_chs[3]; // 结束 CHS 地址 (已过时)
uint32_t starting_lba; // 起始 LBA
uint32_t size_in_lba; // 分区大小(以 LBA 为单位)
} __attribute__((packed));
2.3 GPT 分区表结构
存储位置:分布在磁盘的开头和末尾
┌─────────────────────────────────────────────────────────────────────┐
│ 完整的 GPT 磁盘布局 │
├─────────────────────────────────────────────────────────────────────┤
│ LBA 0 (512 字节) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 保护性 MBR (Protective MBR) │ │
│ │ - 防止旧工具误认为磁盘是未分区的 │ │
│ │ - 只有一个分区条目,类型为 0xEE (GPT Protective) │ │
│ │ - 该分区"覆盖"整个磁盘(从 LBA 1 到磁盘末尾) │ │
│ └─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ LBA 1 (1 个扇区 = 512 字节) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 主 GPT 头部 (Primary GPT Header) │ │
│ │ │ │
│ │ struct gpt_header { │ │
│ │ signature: "EFI PART" │ │
│ │ revision: 0x00010000 │ │
│ │ header_size: 92 字节 │ │
│ │ header_crc32: 头部 CRC32 校验 │ │
│ │ reserved: 保留 │ │
│ │ my_lba: 1 (当前头部位置) │ │
│ │ alternate_lba: 磁盘最后一个扇区 │ │
│ │ first_usable_lba: 34 (第一个可用数据扇区) │ │
│ │ last_usable_lba: 磁盘总扇区数 - 34 │ │
│ │ disk_guid: 磁盘唯一 GUID │ │
│ │ partition_entry_lba: 2 (分区表项起始位置) │ │
│ │ num_partition_entries: 128 (分区条目数量) │ │
│ │ sizeof_partition_entry: 128 (每个条目字节数) │ │
│ │ partition_entry_array_crc32: 分区表 CRC32 │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ LBA 2 ~ LBA 33 (32 个扇区 = 16384 字节) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 分区表项 (Partition Entries) │ │
│ │ │ │
│ │ 每个分区条目 128 字节,总共可存储 128 个分区 │ │
│ │ │ │
│ │ 条目 0 (128 字节): │ │
│ │ - partition_type_guid: 分区类型 GUID │ │
│ │ - unique_partition_guid: 唯一分区 GUID │ │
│ │ - starting_lba: 起始 LBA │ │
│ │ - ending_lba: 结束 LBA │ │
│ │ - attributes: 属性标志 │ │
│ │ - partition_name: 分区名称 (UTF-16, 36 字符) │ │
│ │ │ │
│ │ 条目 1 ~ 127: ... │ │
│ └─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ LBA 34 ~ LBA (磁盘末尾 - 34) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 实际数据分区区域 │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 分区 1 │ │ 分区 2 │ │ 分区 3 │ │ │
│ │ │ (loader1) │ │ (uboot) │ │ (trust) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ ... 更多分区 ... │ │
│ └─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ LBA (磁盘末尾 - 33) ~ LBA (磁盘末尾 - 2) (32 个扇区) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 备份分区表项 (Backup Partition Entries) │ │
│ │ - 与主分区表项完全相同 │ │
│ └─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ LBA (磁盘末尾 - 1) (最后一个扇区) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 备份 GPT 头部 (Backup GPT Header) │ │
│ │ - my_lba: 磁盘最后一个扇区 │ │
│ │ - alternate_lba: 1 │ │
│ │ - 其他字段与主头部相同 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
为什么 LBA 34 才是第一个可用分区?
LBA 0: 保护性 MBR (1 扇区)
LBA 1: 主 GPT 头部 (1 扇区)
LBA 2-33: 分区表项 (32 扇区)
─────────────────────────────────────
LBA 34: 第一个实际分区的起始位置
这就是为什么在 RK3588 的分区布局中,loader1 分区从 start=32K (即 LBA 64) 开始,留出了足够的空间给 GPT 结构。
关键常量定义 (U-Boot 源码):
// 文件: u-boot/include/part_efi.h:29-31
#define GPT_PRIMARY_PARTITION_TABLE_LBA 1ULL
#define GPT_ENTRY_NUMBERS 128
#define GPT_ENTRY_SIZE 128
2.4 GPT 与 MBR 对比
| 特性 | MBR | GPT |
|---|---|---|
| 分区表位置 | LBA 0 | LBA 1 (主) + 磁盘末尾 (备份) |
| 最大分区数 | 4 个主分区 | 128 个分区(可扩展) |
| 分区大小限制 | 最大 2TB | 最大 8ZB |
| 备份机制 | 无 | 主分区表 + 备份分区表 |
| 兼容性 | BIOS/UEFI | UEFI(主要),也可用 BIOS |
| 分区标识 | 1 字节类型码 | 16 字节 GUID |
| 安全性 | 无保护 | CRC32 校验 |
| 启动支持 | 支持 Legacy BIOS | 支持 UEFI,部分支持 Legacy BIOS |
RK3588 平台使用 GPT 的原因:
- 大容量支持:eMMC 容量通常超过 2TB 限制
- 多分区需求:Android 系统需要大量分区(system、vendor、boot、recovery 等)
- 数据可靠性:双分区表提供冗余保护
- 唯一标识:GUID 确保分区唯一性,便于管理
2.5 U-Boot 读取分区表的流程
// 文件: disk/part_efi.c:233-245
void part_print_efi(struct blk_desc *dev_desc)
{
// 1. 尝试读取主 GPT 头部 (LBA 1)
if (is_gpt_valid(dev_desc, GPT_PRIMARY_PARTITION_TABLE_LBA, // LBA 1
gpt_head, &gpt_pte) != 1) {
// 2. 主 GPT 无效,尝试备份 GPT 头部 (磁盘最后 1 个扇区)
if (is_gpt_valid(dev_desc, (dev_desc->lba - 1), // 最后一个扇区
gpt_head, &gpt_pte) != 1) {
printf("Invalid Backup GPT\n");
return;
} else {
printf("Using Backup GPT\n");
}
}
// 3. 遍历分区条目并打印
for (i = 0; i < le32_to_cpu(gpt_head->num_partition_entries); i++) {
if (!is_pte_valid(&gpt_pte[i]))
break;
// 打印分区信息...
}
}
读取流程图:
┌─────────────────────────────────────────────────────────────┐
│ mmc part 命令执行 │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 读取 LBA 0 │
│ - 检查签名 0x55AA │
│ - 如果分区类型为 0xEE,则是 GPT 磁盘 │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 读取 LBA 1 (主 GPT 头部) │
│ - 验证签名 "EFI PART" │
│ - 验证 CRC32 │
└─────────────────────────┬───────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
主 GPT 有效 主 GPT 无效
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────────┐
│ 读取 LBA 2-33 │ │ 读取磁盘最后 1 个扇区 │
│ (分区表项) │ │ (备份 GPT 头部) │
└─────────────────────┘ └─────────────────────────────┘
│
┌───────┴───────┐
│ │
备份有效 备份无效
│ │
▼ ▼
使用备份 GPT 报错退出
3. U-Boot 启动流程
3.1 整体流程图
┌─────────────────────────────────────────────────────────────────┐
│ RK3588 上电复位 │
└─────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 内部 ROM 启动代码 │
│ (从 Bootrom 启动,尝试加载 SPL) │
└─────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SPL 阶段 │
│ 1. 读取设备树中的 u-boot,spl-boot-order │
│ 2. 按顺序尝试:SD卡 → eMMC → SPI NAND → SPI NOR │
│ 3. 加载 U-Boot proper 到 DDR │
└─────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ U-Boot proper 阶段 │
│ 1. 初始化所有硬件驱动 │
│ 2. 执行 boot_devtype_init() 检测启动设备 │
│ 3. 解析分区表(GPT 或 MTD) │
│ 4. 加载内核和设备树 │
└─────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Linux 内核启动 │
└─────────────────────────────────────────────────────────────────┘
3.2 SPL 阶段的设备选择
关键文件:arch/arm/mach-rockchip/spl-boot-order.c
SPL 根据设备树定义的顺序尝试各个启动介质:
// 文件: arch/arm/dts/rk3588-u-boot.dtsi:15-18
chosen {
u-boot,spl-boot-order = &sdmmc, &sdhci, &spi_nand, &spi_nor;
};
这告诉 SPL 按以下顺序尝试:
- sdmmc (SD 卡)
- sdhci (eMMC)
- spi_nand (SPI NAND Flash)
- spi_nor (SPI NOR Flash)
3.3 U-Boot proper 的启动设备检测
关键文件:arch/arm/mach-rockchip/boot_rkimg.c:103-151
static void boot_devtype_init(void)
{
char *devtype = NULL, *devnum = NULL;
char *src = "scan";
// 优先级 1: 配置指定
if (!param_parse_assign_bootdev(&devtype, &devnum)) {
if (!bootdev_init(devtype, devnum)) {
src = "assign";
goto finish;
}
}
// 优先级 2: ATAGS 传递(来自前面的加载器)
if (!param_parse_atags_bootdev(&devtype, &devnum)) {
if (!bootdev_init(devtype, devnum)) {
src = "


2807

被折叠的 条评论
为什么被折叠?



