3. ACPI 基础概念:ACPI 表结构(RSDP、RSDT、XSDT、DSDT、SSDT)与命名空间

ACPI(Advanced Configuration and Power Interface)是现代 x86/ARM64 平台固件与操作系统之间的核心接口规范。在设备模型匹配流程中,ACPI 承担了与 Device Tree 类似的角色——向内核描述硬件拓扑、资源分配与电源管理策略。理解 ACPI 的表结构与命名空间,是调试 ACPI 设备枚举与驱动匹配的前提。

核心认知:ACPI 不是单一的表,而是一组按层级组织的表集合。操作系统通过 Root System Description Pointer(RSDP)找到根表,再逐级解析出 DSDT/SSDT 等定义块,最终构建出统一的 ACPI 命名空间树。

3.1 ACPI 表结构总览

ACPI 规范定义了一系列固定格式的表,它们在系统启动时由固件(BIOS/UEFI)放置在内存中,并由内核通过特定方式定位。下表列出了最关键的几张表及其作用:

表名 全称 核心作用 是否必须
RSDP Root System Description Pointer ACPI 表的入口指针,位于 EBDA 或 BIOS 区域 是
RSDT Root System Description Table 32 位表指针数组,指向其他系统描述表 是(32位)
XSDT Extended System Description Table 64 位表指针数组,RSDT 的扩展版本 是(64位)
DSDT Differentiated System Description Table 主定义块,包含设备对象、方法、资源 是
SSDT Secondary System Description Table 辅助定义块,补充或覆盖 DSDT 中的定义 否
FADT Fixed ACPI Description Table 描述固定硬件寄存器(PM1a、PM1b 等) 是
MADT Multiple APIC Description Table 描述中断控制器拓扑(IOAPIC、GIC 等) 是
调试要点:在 Linux 启动早期,内核通过 acpi_rsdp 内核参数或 UEFI 配置表定位 RSDP。如果 RSDP 校验失败,整个 ACPI 子系统将无法初始化,设备枚举会回退到 legacy 或 Device Tree 路径。

3.2 RSDP:一切表的起点

RSDP 是 ACPI 表的“根指针”,其结构非常紧凑,仅包含签名、校验和、OEM ID 以及 RSDT/XSDT 的物理地址。内核在启动时扫描 EBDA(Extended BIOS Data Area)和主 BIOS 区域,寻找 "RSD PTR " 签名。

struct acpi_table_rsdp {
    char        signature[8];   /* "RSD PTR " */
    uint8_t     checksum;       /* 整个结构的校验和 */
    char        oem_id[6];      /* OEM ID */
    uint8_t     revision;       /* 0=ACPI 1.0, 2=ACPI 2.0+ */
    uint32_t    rsdt_physical_address; /* RSDT 物理地址(32位) */
    uint32_t    length;         /* RSDP 总长度 */
    uint64_t    xsdt_physical_address; /* XSDT 物理地址(64位) */
    uint8_t     extended_checksum;
    uint8_t     reserved[3];
} __attribute__((packed));
版本差异:ACPI 1.0 仅支持 32 位 RSDT,revision 字段为 0。ACPI 2.0 及以上版本同时提供 RSDT 和 XSDT,内核优先使用 XSDT(64 位地址空间)。如果 XSDT 校验失败,内核会回退到 RSDT。

3.3 RSDT 与 XSDT:表指针数组

RSDT 和 XSDT 本质上是“表的目录”。它们各自包含一个指向其他 ACPI 表(如 DSDT、FADT、MADT、SSDT 等)的指针数组。两者的区别仅在于指针宽度:

  • RSDT:每个表项是 32 位物理地址,适用于 32 位系统或兼容模式。
  • XSDT:每个表项是 64 位物理地址,适用于 64 位系统。

内核通过遍历 RSDT/XSDT 中的指针,逐一校验表签名(如 "DSDT"、"FACP"、"APIC"),并将有效的表注册到全局 ACPI 表链表中。

/* 内核遍历 XSDT 的简化逻辑 */
void __init acpi_table_init(void) {
    struct acpi_table_rsdp *rsdp = acpi_find_rsdp();
    struct acpi_table_xsdt *xsdt = acpi_os_map_memory(
        rsdp->xsdt_physical_address, sizeof(*xsdt));
    int count = (xsdt->header.length - sizeof(xsdt->header)) / sizeof(u64);
    for (int i = 0; i < count; i++) {
        phys_addr_t table_addr = xsdt->table_offset_entry[i];
        struct acpi_table_header *hdr = acpi_os_map_memory(table_addr, sizeof(*hdr));
        acpi_add_table(hdr);
    }
}
调试技巧:在 Linux 启动参数中添加 acpi=debug 或 acpi=rsdt,可以打印出内核找到的所有 ACPI 表地址和签名。使用 acpidump 工具可以导出完整表内容用于离线分析。

3.4 DSDT 与 SSDT:设备定义的核心

DSDT 是 ACPI 命名空间的主要载体,它包含设备对象(Device)、方法(Method)、资源(Resource)等定义。SSDT 是 DSDT 的补充或覆盖,通常由 BIOS 动态生成(如主板上的外设控制器)。

DSDT/SSDT 使用 ACPI 源语言(ASL)编写,编译为 ACPI 机器语言(AML)后由内核中的 AML 解释器执行。设备模型匹配流程中,内核通过遍历命名空间树,查找与驱动匹配的硬件 ID(如 _HID、_CID)。

/* DSDT 中一个典型设备对象的 ASL 表示 */
DefinitionBlock ("dsdt.aml", "DSDT", 2, "OEMID", "OEMTABLE", 0x00000001) {
    Scope (_SB) {
        Device (PCI0) {
            Name (_HID, EisaId ("PNP0A08"))  /* PCI Express 总线 */
            Name (_CID, EisaId ("PNP0A03"))  /* 兼容 PCI 总线 */
            Name (_ADR, 0x00000000)
            Method (_OSC, 4, NotSerialized) {
                /* 操作系统能力协商 */
            }
            Device (I2C1) {
                Name (_HID, "ACPI0011")      /* I2C 控制器 */
                Name (_UID, 0x01)
                Name (_CRS, ResourceTemplate () {
                    Memory32Fixed (ReadWrite, 0xfe020000, 0x1000)
                    Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 42 }
                })
            }
        }
    }
}
匹配流程关键:驱动通过 acpi_match_device 函数,将驱动的 acpi_device_id 表与命名空间中设备的 _HID/_CID 进行比对。匹配成功后,内核调用驱动的 probe 函数,并传入 ACPI 句柄和资源列表。

3.5 ACPI 命名空间:设备树的逻辑等价物

ACPI 命名空间是一个树形结构,根节点为 \(反斜杠),下一级通常是 _SB_(System Bus),所有平台设备都挂载在 _SB_ 下。命名空间中的每个节点都是一个 ACPI 对象,可以是 Device、Method、Scope、Processor、ThermalZone 等。

与 Device Tree 的对比:

特性 ACPI 命名空间 Device Tree
根节点 \ 反斜杠 /
设备标识 _HID / _CID(字符串或 EISA ID) compatible 属性
资源描述 _CRS(Current Resource Settings) reg、interrupts、clocks 等
电源管理 _PS0/_PS3/_PR0 等方法 power-domains、operating-points
动态生成 SSDT 可热添加 overlay dtbo
解析方式 AML 解释器(内核态虚拟机) DTB 解析器(扁平二进制)
性能注意:AML 解释器是一个栈式虚拟机,执行效率低于 Device Tree 的直接解析。在嵌入式场景中,如果 ACPI 命名空间包含大量复杂方法(如 _DSM、_OST),可能会影响系统启动时间。

3.6 ACPI 表与命名空间的关系图

下图展示了从 RSDP 到命名空间树的完整解析路径:

ACPI 表结构与命名空间解析流程 ACPI 表结构与命名空间解析流程 RSDP RSDT (32-bit) XSDT (64-bit) 表指针数组 [DSDT, FADT, MADT, SSDT...] 表指针数组 [DSDT, FADT, MADT, SSDT...] DSDT SSDT (可选) ACPI 命名空间树 \ (Root) ├── _SB_ (System Bus) │ ├── PCI0 (_HID: PNP0A08) │ │ ├── I2C1 (_HID: ACPI0011) │ │ └── UAR0 (_HID: PNP0501) └── _PR (Processor)

3.7 内核中 ACPI 表解析的关键路径

Linux 内核在启动过程中,ACPI 子系统的初始化顺序如下:

  1. 早期定位:acpi_boot_table_init() 扫描 RSDP,校验签名和校验和。
  2. 根表解析:acpi_table_init() 读取 RSDT/XSDT,注册所有子表。
  3. 固定表加载:加载 FADT,获取 PM 寄存器地址和 DSDT 地址。
  4. DSDT/SSDT 加载:acpi_load_tables() 将 DSDT 和所有 SSDT 加载到内存,AML 解释器开始解析。
  5. 命名空间构建:acpi_ns_root_initialize() 递归遍历 AML 字节码,构建对象树。
  6. 设备枚举:acpi_bus_scan() 遍历命名空间中的 Device 节点,创建 acpi_device 结构体,触发驱动匹配。
调试命令:在 Linux 启动后,通过 ls /sys/firmware/acpi/tables/ 可以查看已加载的 ACPI 表文件。使用 cat /sys/firmware/acpi/tables/DSDT > dsdt.dat 导出 DSDT 二进制,再用 iasl -d dsdt.dat 反编译为 ASL 源码。

3.8 本章小结