3. ACPI 基础概念:ACPI 表结构(RSDP、RSDT、XSDT、DSDT、SSDT)与命名空间
ACPI(Advanced Configuration and Power Interface)是现代 x86/ARM64 平台固件与操作系统之间的核心接口规范。在设备模型匹配流程中,ACPI 承担了与 Device Tree 类似的角色——向内核描述硬件拓扑、资源分配与电源管理策略。理解 ACPI 的表结构与命名空间,是调试 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 等) | 是 |
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));
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);
}
}
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 解析器(扁平二进制) |
3.6 ACPI 表与命名空间的关系图
下图展示了从 RSDP 到命名空间树的完整解析路径:
3.7 内核中 ACPI 表解析的关键路径
Linux 内核在启动过程中,ACPI 子系统的初始化顺序如下:
- 早期定位:
acpi_boot_table_init()扫描 RSDP,校验签名和校验和。 - 根表解析:
acpi_table_init()读取 RSDT/XSDT,注册所有子表。 - 固定表加载:加载 FADT,获取 PM 寄存器地址和 DSDT 地址。
- DSDT/SSDT 加载:
acpi_load_tables()将 DSDT 和所有 SSDT 加载到内存,AML 解释器开始解析。 - 命名空间构建:
acpi_ns_root_initialize()递归遍历 AML 字节码,构建对象树。 - 设备枚举:
acpi_bus_scan()遍历命名空间中的 Device 节点,创建acpi_device结构体,触发驱动匹配。
ls /sys/firmware/acpi/tables/ 可以查看已加载的 ACPI 表文件。使用 cat /sys/firmware/acpi/tables/DSDT > dsdt.dat 导出 DSDT 二进制,再用 iasl -d dsdt.dat 反编译为 ASL 源码。