3. RSDP与RSDT解析

RSDP(Root System Description Pointer)是 ACPI 子系统的入口点,操作系统通过它找到 RSDT(Root System Description Table)或 XSDT(Extended System Description Table),进而遍历所有 ACPI 表。本章将深入解析 RSDP 的签名校验、校验和计算,以及 RSDT/XSDT 的地址解析与表遍历方法。

核心要点:RSDP 是 ACPI 表的“根指针”,其校验和机制保证了引导时数据的完整性;RSDT 与 XSDT 分别提供 32 位和 64 位的表地址数组,内核通过它们完成 ACPI 表的注册与遍历。

3.1 RSDP 结构解析与签名校验

RSDP 位于系统内存的低 1MB 区域(通常由 BIOS/UEFI 在引导时填充),其结构在 ACPI 1.0 中定义,ACPI 2.0 后扩展为 36 字节。内核通过扫描特定内存区域并验证签名来定位 RSDP。

偏移 长度(字节) 字段 说明
0 8 Signature 固定为 "RSD PTR "(含尾随空格)
8 1 Checksum 前 20 字节的校验和,必须为 0
9 6 OEMID OEM 厂商标识
15 1 Revision 0 表示 ACPI 1.0,2 表示 ACPI 2.0+
16 4 RsdtAddress RSDT 的 32 位物理地址
20 4 Length RSDP 总长度(ACPI 2.0+ 扩展字段)
24 8 XsdtAddress XSDT 的 64 位物理地址
32 1 Extended Checksum 整个 RSDP 的校验和,必须为 0
33 3 Reserved 保留字段

签名校验流程:内核在物理内存 0x000E0000 ~ 0x000FFFFF(EBDA 区域)以及 0x000F0000 ~ 0x000FFFFF(系统 BIOS 区域)中,以 16 字节对齐扫描 "RSD PTR " 签名。找到后,立即对前 20 字节进行校验和验证。

实践提示:签名校验时务必检查尾随空格字符(0x20),部分固件可能填充其他空白字符,但标准要求严格匹配 8 字节 "RSD PTR "。
RSDP 定位与签名校验流程 EBDA 区域 0x000E0000 ~ 0x000FFFFF BIOS 区域 0x000F0000 ~ 0x000FFFFF 签名匹配 "RSD PTR " 校验和验证 前 20 字节求和为 0 通过 失败 说明:扫描步长为 16 字节对齐, 校验和 = (0x00 + ... + 0x13) & 0xFF == 0

3.2 校验和计算与验证

ACPI 规范要求 RSDP 包含两个校验和字段:

  • Checksum(偏移 8):覆盖 RSDP 前 20 字节(Signature + Checksum + OEMID + Revision + RsdtAddress)。计算方式为:所有字节相加后取低 8 位,结果必须为 0。
  • Extended Checksum(偏移 32):覆盖整个 RSDP(Length 字段指示的实际长度,通常为 36 字节)。同样要求求和结果为 0。
常见陷阱:部分固件在 ACPI 2.0+ 模式下,RsdtAddress 可能为 0,此时必须使用 XsdtAddress。校验和验证时,如果 Revision < 2,则只验证前 20 字节的 Checksum。

校验和计算示例代码:

// RSDP 校验和验证函数
int acpi_verify_rsdp(struct acpi_table_rsdp *rsdp) {
    u8 *bytes = (u8 *)rsdp;
    u8 sum = 0;
    u32 length;

    // 验证前 20 字节的校验和
    for (int i = 0; i < 20; i++)
        sum += bytes[i];
    if (sum != 0) {
        pr_err("RSDP: 前 20 字节校验和无效\n");
        return -EINVAL;
    }

    // 如果 Revision >= 2,验证扩展校验和
    if (rsdp->revision >= 2) {
        length = rsdp->length;
        if (length < 36 || length > 4096) {
            pr_err("RSDP: 无效的长度字段 %u\n", length);
            return -EINVAL;
        }
        sum = 0;
        for (int i = 0; i < length; i++)
            sum += bytes[i];
        if (sum != 0) {
            pr_err("RSDP: 扩展校验和无效\n");
            return -EINVAL;
        }
    }
    return 0;
}

3.3 RSDT 与 XSDT 结构解析

RSDT 和 XSDT 是 ACPI 表的“目录”,它们包含指向其他 ACPI 表(如 DSDT、FACP、APIC 等)的物理地址数组。两者的区别在于地址宽度:

特性 RSDT XSDT
引入版本 ACPI 1.0 ACPI 2.0
地址宽度 32 位 64 位
表头结构 标准 ACPI 表头(36 字节) 标准 ACPI 表头(36 字节)
地址数组 u32 数组 u64 数组
适用系统 32 位系统 64 位系统
表数量 (Length - 36) / 4 (Length - 36) / 8

标准 ACPI 表头结构(36 字节):

偏移 长度(字节) 字段 说明
0 4 Signature 表签名(如 "RSDT"、"XSDT"、"FACP")
4 4 Length 表总长度(含表头)
8 1 Revision 表版本号
9 1 Checksum 整个表的校验和
10 6 OEMID OEM 标识
16 8 OEM Table ID OEM 表标识
24 4 OEM Revision OEM 版本
28 4 Creator ID 创建者 ID
32 4 Creator Revision 创建者版本

3.4 表遍历方法

内核通过 RSDP 获取 RSDT/XSDT 地址后,遍历其地址数组,对每个表地址进行签名匹配和校验和验证,然后注册到 ACPI 表链表中。

遍历步骤:

  1. 获取根表:从 RSDP 的 RsdtAddress(32 位)或 XsdtAddress(64 位)读取 RSDT/XSDT 的物理地址。
  2. 映射表头:将物理地址映射到内核虚拟地址空间,读取表头(前 36 字节)。
  3. 验证签名:检查 Signature 是否为 "RSDT" 或 "XSDT"。
  4. 计算表数量:根据 Length 和地址宽度计算表项数量。
  5. 遍历地址数组:对每个表项,映射并验证其表头,然后注册到 ACPI 子系统。
RSDT/XSDT 表遍历流程 RSDP Revision >= 2 ? RSDT (32 位地址) XSDT (64 位地址) 遍历 u32 地址数组 遍历 u64 地址数组 注册到 ACPI 表链表 表数量计算: (Length - 36) / 地址宽度

3.5 内核实现要点

Linux 内核中,ACPI 表遍历的核心代码位于 drivers/acpi/tables.c。关键函数包括:

  • acpi_find_root_pointer():扫描内存定位 RSDP。
  • acpi_tb_parse_root_table():解析 RSDT/XSDT 并注册所有子表。
  • acpi_tb_install_table():将单个 ACPI 表安装到全局表列表中。
性能调优关联:电源性能调优需要访问 DSDT(Differentiated System Description Table)和 SSDT(Secondary System Description Table),这些表都通过 RSDT/XSDT 遍历获得。正确的表遍历顺序和校验和验证是后续调优的基础。

表遍历核心代码片段:

// 遍历 RSDT 并注册所有 ACPI 表
void acpi_tb_parse_root_table(acpi_physical_address address, u8 revision) {
    struct acpi_table_header *table;
    u32 table_count, i;
    acpi_physical_address *entry;

    // 映射 RSDT/XSDT 表头
    table = acpi_os_map_memory(address, sizeof(struct acpi_table_header));
    if (!table)
        return;

    // 计算表项数量
    if (revision < 2) {
        // RSDT: 32 位地址
        table_count = (table->length - sizeof(struct acpi_table_header)) / 4;
        entry = (acpi_physical_address *)(table + 1);
    } else {
        // XSDT: 64 位地址
        table_count = (table->length - sizeof(struct acpi_table_header)) / 8;
        entry = (acpi_physical_address *)(table + 1);
    }

    // 遍历并注册每个表
    for (i = 0; i < table_count; i++) {
        acpi_tb_install_table(entry[i]);
    }

    acpi_os_unmap_memory(table, sizeof(struct acpi_table_header));
}
调试技巧:在引导时添加 acpi=debug 内核参数,可以打印 ACPI 表遍历的详细信息,包括每个表的签名、地址和校验和状态。这对于排查表解析失败问题非常有效。

3.6 常见问题与避坑指南

问题 原因 解决方案
RSDP 签名未找到 扫描区域被覆盖或固件损坏 检查内存映射,尝试扩展扫描范围
校验和失败 内存损坏或固件 bug 使用 acpi=rsdp 手动指定 RSDP 地址
RSDT/XSDT 表头无效 地址错误或表被覆盖 验证 RSDP 中的地址字段是否正确
表遍历顺序异常 固件未按规范排列表项 内核应支持乱序表项,但需校验签名