4、RSDP与RSDT/XSDT解析:Root System Description Pointer (RSDP)的定位、Root System Description Table (RSDT)与Extended System Description Table (XSDT)的结构与解析
在Linux内核引导的早期阶段,ACPI子系统的初始化依赖于一个关键的“寻根”过程。内核必须首先在系统内存的特定区域找到Root System Description Pointer (RSDP),然后通过RSDP定位到描述系统硬件拓扑和配置的根级描述表——Root System Description Table (RSDT)或Extended System Description Table (XSDT)。本章将深入解析这一寻址与解析机制,这是理解ACPI电源状态初始化的基石。
4.1 RSDP的定位与结构
RSDP是ACPI规范定义的第一个数据结构,它不依赖于任何其他系统表。UEFI固件或BIOS在系统启动时,会将RSDP的物理地址放置在两个预定义的搜索区域内:
- 传统BIOS(Legacy BIOS)环境:搜索范围是系统低端内存(Extended BIOS Data Area,EBDA)以及主BIOS区域(0x000E0000 ~ 0x000FFFFF)。内核通过扫描这些区域中特定的签名“RSD PTR ”来定位。
- UEFI环境:UEFI固件通过配置表(Configuration Table)直接提供RSDP的指针,内核可以从UEFI系统表中直接获取,无需扫描内存。
RSDP的结构分为两个版本:ACPI 1.0的20字节版本和ACPI 2.0及以后的36字节扩展版本。内核在定位RSDP后,首先检查其签名和校验和,以确认其有效性。
| 偏移量(字节) | 字段名称 | 长度(字节) | 描述 |
|---|---|---|---|
| 0 | Signature | 8 | 固定签名“RSD PTR ”(包含一个尾随空格) |
| 8 | Checksum | 1 | 整个RSDP结构(前20字节)的校验和,必须为0 |
| 9 | OEM ID | 6 | OEM厂商标识字符串 |
| 15 | Revision | 1 | 版本号:0表示ACPI 1.0,2表示ACPI 2.0+ |
| 16 | RsdtAddress | 4 | RSDT的32位物理地址(ACPI 1.0兼容) |
| 20 | Length | 4 | 整个RSDP结构(包括扩展部分)的长度 |
| 24 | XsdtAddress | 8 | XSDT的64位物理地址(ACPI 2.0+) |
| 32 | Extended Checksum | 1 | 整个RSDP结构(包括扩展部分)的校验和,必须为0 |
| 33 | Reserved | 3 | 保留字段,必须为0 |
4.2 RSDT与XSDT的结构与解析
RSDT和XSDT本质上是相同的逻辑结构:它们都是一个指向其他ACPI描述表(如FADT、SSDT、APIC等)的指针数组。区别在于指针的宽度:
- RSDT (Root System Description Table):使用32位物理地址指针。适用于32位系统和ACPI 1.0规范。
- XSDT (Extended System Description Table):使用64位物理地址指针。适用于64位系统和ACPI 2.0及以上规范。在64位系统上,内核应优先使用XSDT,因为它能正确映射高于4GB的物理地址。
RSDT和XSDT的表头结构相同,都遵循ACPI标准系统描述表头(Standard Table Header)。
| 偏移量(字节) | 字段名称 | 长度(字节) | 描述 |
|---|---|---|---|
| 0 | Signature | 4 | 表签名,例如“RSDT”、“XSDT”、“FACP”等 |
| 4 | Length | 4 | 整个表的长度(包括表头) |
| 8 | Revision | 1 | 表的修订版本号 |
| 9 | Checksum | 1 | 整个表的校验和,必须为0 |
| 10 | OEM ID | 6 | OEM厂商标识 |
| 16 | OEM Table ID | 8 | OEM表标识 |
| 24 | OEM Revision | 4 | OEM修订版本 |
| 28 | Creator ID | 4 | 创建该表的工具厂商ID |
| 32 | Creator Revision | 4 | 创建工具的修订版本 |
在表头之后,RSDT和XSDT的主体部分是一个连续的指针数组。对于RSDT,每个指针占4字节;对于XSDT,每个指针占8字节。指针的数量可以通过 (Length - sizeof(TableHeader)) / sizeof(Pointer) 计算得出。
/* Linux内核中解析RSDT/XSDT的简化逻辑示例 */
void acpi_parse_root_table(void *rsdp_addr) {
struct acpi_table_header *header;
int i, count;
/* 假设已经通过RSDP获取到XSDT地址 */
header = (struct acpi_table_header *)xsdt_addr;
/* 校验表头签名和校验和 */
if (strncmp(header->signature, "XSDT", 4) != 0) {
pr_err("Invalid XSDT signature\n");
return;
}
/* 计算指针数量 */
count = (header->length - sizeof(struct acpi_table_header)) / sizeof(u64);
/* 遍历所有指针,处理每个子表 */
for (i = 0; i < count; i++) {
u64 table_addr = ((u64 *)xsdt_addr)[i];
struct acpi_table_header *sub_header = (struct acpi_table_header *)table_addr;
/* 根据子表的签名(如FADT、MADT、SSDT)进行分发处理 */
acpi_handle_sub_table(sub_header);
}
}
noacpi参数)干扰。此外,RSDT/XSDT中的指针如果指向无效或损坏的内存区域,会导致内核在解析子表时崩溃,此时应检查固件更新或使用acpi=off参数临时禁用ACPI进行故障隔离。
4.3 RSDP与RSDT/XSDT的层级关系
下图展示了从RSDP到具体ACPI表的完整层级关系,这是内核ACPI子系统初始化的核心数据流。
4.4 内核解析流程与故障排查
Linux内核在引导阶段解析RSDP和RSDT/XSDT的流程可以概括为以下步骤:
- 定位RSDP:通过UEFI配置表或内存扫描获取RSDP的物理地址。
- 验证RSDP:检查签名、校验和(前20字节和扩展校验和)。
- 选择根表:如果RSDP版本 >= 2 且 XsdtAddress 非零,则使用XSDT;否则使用RSDT。
- 映射并验证根表:将RSDT/XSDT映射到内核虚拟地址空间,检查表头签名和校验和。
- 遍历指针数组:解析每个指针,映射对应的子表,并根据子表签名调用相应的处理函数(如
acpi_tb_parse_fadt())。
- “ACPI: RSDP address not found”:通常发生在UEFI系统上,如果UEFI运行时服务没有正确暴露RSDP指针。检查内核启动参数是否包含
acpi=rsdp手动指定地址。 - “ACPI: Table validation failed”:RSDT/XSDT或其子表的校验和错误。这可能是由于固件bug或内存损坏导致。尝试更新BIOS/UEFI固件。
- “Unable to handle kernel paging request at virtual address”:RSDT/XSDT中的指针指向了无效的物理地址。这通常意味着固件提供了错误的表地址。使用
acpi=debug参数可以打印更详细的表地址信息,用于调试。
理解RSDP和RSDT/XSDT的解析过程,是诊断ACPI相关电源管理问题(如系统无法进入S3睡眠、CPU频率调节失效等)的第一步。内核通过这些表获取FADT(固定ACPI描述表)的地址,而FADT中包含了电源管理控制器(PM1a_CNT_BLK、PM1b_CNT_BLK等)的I/O或内存地址,这些地址直接用于控制系统的电源状态转换。