[Linux外设驱动详解]RK3588 U-Boot 启动介质识别与分区信息获取机制详解

实战派 ESP32-S3,双模无线开发板

ESP32-S3 原生支持 ESP-IDF,WiFi + 蓝牙一次搞定

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 如何识别这些介质? 答案在于以下三方面的协同工作:

  1. 设备树(Device Tree):定义硬件启动顺序
  2. SPL(Secondary Program Loader):按顺序尝试各个介质
  3. 环境变量:保存最终的启动设备信息

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 的原因

  1. 大容量支持:eMMC 容量通常超过 2TB 限制
  2. 多分区需求:Android 系统需要大量分区(system、vendor、boot、recovery 等)
  3. 数据可靠性:双分区表提供冗余保护
  4. 唯一标识: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 按以下顺序尝试:

  1. sdmmc (SD 卡)
  2. sdhci (eMMC)
  3. spi_nand (SPI NAND Flash)
  4. 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 = "

实战派 ESP32-S3,双模无线开发板

ESP32-S3 原生支持 ESP-IDF,WiFi + 蓝牙一次搞定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JustaUncle

一杯咖啡,换我肝到天亮!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值