ACPI表定制开发实战 - 第2节

2.1 ACPI 表结构:系统固件与 OS 的契约书

ACPI 表是系统固件(BIOS/UEFI)传递给操作系统的一组结构化数据块,它们描述了硬件拓扑、电源管理能力、设备配置和运行时接口。OS 在引导早期通过 RSDP(Root System Description Pointer) 找到这些表,然后按需解析。

2.1.1 核心表分类

表名 全称 作用 是否必选
RSDP Root System Description Pointer ACPI 入口指针,位于 EBDA 或 UEFI 配置表 是
RSDT Root System Description Table 32 位表指针数组,指向其他 ACPI 表 是
XSDT Extended System Description Table 64 位版本的 RSDT,现代系统优先使用 是
FADT Fixed ACPI Description Table 定义电源管理硬件寄存器、中断模型、SCI 路由 是
DSDT Differentiated System Description Table 主命名空间定义,包含设备对象和控制方法 是
SSDT Secondary System Description Table 辅助命名空间,用于扩展或覆盖 DSDT 否
MADT Multiple APIC Description Table 描述中断控制器(APIC、I/O APIC、GIC)拓扑 是
SLIT System Locality Information Table NUMA 节点间的距离矩阵 否
SRAT System Resource Affinity Table CPU/内存与 NUMA 节点的亲和关系 否
实战要点:定制开发中最常修改的表是 DSDT 和 SSDT。DSDT 是主表,SSDT 用于打补丁或添加新设备。FADT 中的 SCI 中断号和 SMI 命令端口在调试时经常需要核对。

2.1.2 表头通用结构

每个 ACPI 表(除 RSDP 外)都以相同的 36 字节表头开始:

typedef struct {
    char     Signature[4];    // 表签名,如 "DSDT"
    uint32_t Length;          // 表总长度(含表头)
    uint8_t  Revision;        // 表版本
    uint8_t  Checksum;        // 校验和,使整个表所有字节和为 0
    char     OEMID[6];        // OEM ID
    char     OEMTableID[8];   // OEM 表 ID
    uint32_t OEMRevision;     // OEM 版本
    uint32_t CreatorID;       // 创建者工具 ID
    uint32_t CreatorRevision; // 创建者工具版本
} __attribute__((packed)) ACPI_TABLE_HEADER;
校验和陷阱:修改 DSDT/SSDT 后必须重新计算 Checksum,否则 OS 会拒绝加载整张表,导致系统无法进入 ACPI 模式。计算方式:所有字节累加后取低 8 位,结果为 0 则校验通过。

2.2 命名空间:设备与对象的层次树

ACPI 命名空间(Namespace)是一棵以 \(根)为起点的树形结构,所有设备、对象、控制方法都挂载在这棵树上。DSDT 和 SSDT 的内容共同构成完整的命名空间。

2.2.1 命名规则

  • 根节点:\(反斜杠),命名空间的起点。
  • 分隔符:.(点号),用于分隔路径层级。
  • 名称长度:每个名称段固定为 4 个字符(如 _SB_、PCI0)。
  • 绝对路径:从根开始,如 \_SB_.PCI0.I2C0。
  • 相对路径:从当前作用域开始,如 PCI0.I2C0。

2.2.2 标准命名空间分支

路径 含义 常见子对象
\_SB_ 系统总线(System Bus) PCI0、I2C0、SPI0、UAR0
\_PR_ 处理器(Processor) CPU0、CPU1、…
\_TZ_ 热区(Thermal Zone) TZ00、TZ01、…
\_GPE 通用事件(General Purpose Events) L01、L02、…
\_SI_ 系统指示器(System Indicators) 用于睡眠/唤醒状态
开发习惯:自定义设备建议放在 \_SB_ 下,避免与标准分支冲突。例如添加一个虚拟传感器设备:\_SB_.VSEN。
ACPI 命名空间树结构 \ (根) \_SB_ \_PR_ \_TZ_ \_GPE \_SI_ PCI0 I2C0 I2C1 UAR0 CPU0 CPU1 TZ00 TZ01 L01 L02 说明: • 根节点 \ 是命名空间的起点,所有路径都从它派生。 • 一级分支(_SB_、_PR_、_TZ_ 等)是 ACPI 规范定义的标准作用域。 • 二级节点(PCI0、CPU0、TZ00)是具体的设备或对象,可包含三级子对象。

2.3 设备对象:硬件在 ACPI 中的化身

设备对象(Device Object)是命名空间中代表物理或逻辑硬件单元的节点。每个设备对象可以包含 _ADR(地址)、_HID(硬件 ID)、_CID(兼容 ID)、_STA(状态) 等控制方法,以及子设备。

2.3.1 设备对象声明示例

Device (\_SB_.PCI0.I2C0)
{
    Name (_HID, "ACPI0011")      // I2C 控制器硬件 ID
    Name (_CID, "PNP0C02")       // 兼容 ID
    Name (_ADR, 0x001F0001)      // PCI 地址
    Method (_STA, 0, NotSerialized)
    {
        Return (0x0F)            // 设备存在且启用
    }

    Device (TPAD)                // 子设备:触摸板
    {
        Name (_HID, "ELAN0100")
        Name (_CRS, ResourceTemplate()
        {
            I2cSerialBus (0x15, ControllerInitiated, 400000, AddressingMode7Bit, "\\_SB_.PCI0.I2C0", 0, ResourceConsumer)
        })
    }
}

2.3.2 常用设备对象属性

属性/方法 类型 说明
_HID Name (String) 硬件 ID,如 "PNP0C0C"(电源按钮)
_CID Name (String/List) 兼容 ID,用于驱动匹配
_ADR Name (Integer) 设备地址(PCI 总线号/设备号/功能号)
_STA Method 返回设备状态(0x0F=启用,0x00=禁用)
_CRS Name (ResourceTemplate) 当前资源设置(IRQ、I/O、内存、I2C 地址等)
_PRW Name (Package) 电源唤醒能力(GPE 位 + 睡眠状态)
调试技巧:在 Linux 下使用 acpidump 导出 DSDT,然后用 iasl -d 反编译。搜索 Device (XXX) 可以快速定位设备对象。Windows 下可用 RWEverything 查看 ACPI 表。

2.4 控制方法:ACPI 的“可执行代码”

控制方法(Control Method)是用 ACPI 源语言(ASL)编写的函数,由 OS 调用以执行硬件操作。方法名以 下划线开头 的是 ACPI 规范定义的标准方法(如 _STA、_ON、_OFF),以字母开头的为 OEM 自定义方法。

2.4.1 方法定义语法

Method (方法名, 参数个数, 序列化标志)
{
    // 局部变量:Local0 ~ Local7
    // 参数:Arg0 ~ Arg7
    // 操作:Store、Add、Subtract、Notify 等
    Return (结果)
}

2.4.2 标准控制方法速查

方法 参数 返回值 用途
_STA 0 Integer 查询设备状态(0x0F=启用)
_ON 0 Void 开启设备电源
_OFF 0 Void 关闭设备电源
_PS0 0 Void 进入 D0 状态(全功率)
_PS3 0 Void 进入 D3 状态(关闭)
_CRS 0 Buffer 返回当前资源描述
_SRS 1 (Buffer) Void 设置资源描述
_DSM 4 (UUID, Rev, Func, Args) Buffer/Integer 设备特定方法(通用扩展接口)
实战案例:自定义方法实现 GPIO 翻转。在 \_SB_.GPIO 设备下添加 Method (TOGG, 0, Serialized) { Store (0x01, Local0); Notify (\_SB_.GPIO, 0x80); Return (Local0); }。OS 调用 TOGG() 即可触发 GPIO 状态切换。

2.5 全局锁:多处理器环境下的同步机制

全局锁(Global Lock)是 ACPI 提供的一种硬件级互斥机制,用于在多个处理器(或 OS 与固件)之间同步对共享资源的访问。它通过 FADT 表中的 Global Lock 寄存器 实现。

2.5.1 全局锁的工作原理

  1. 请求锁:OS 或固件向 Global Lock 寄存器写入请求位。
  2. 仲裁:硬件仲裁器决定哪个请求者获得锁。
  3. 持有锁:获得锁的实体可以安全访问共享资源(如 ACPI 控制方法中的全局变量)。
  4. 释放锁:操作完成后,写入释放位,仲裁器将锁交给下一个等待者。

2.5.2 全局锁在 ASL 中的使用

// 在控制方法中获取全局锁
Method (LOCK_ACQ, 0, Serialized)
{
    Acquire (\_GL, 0xFFFF)   // 等待超时 0xFFFF 毫秒
    // 访问共享资源
    Store (0x01, Local0)
    Release (\_GL)           // 释放全局锁
    Return (Local0)
}
性能警告:全局锁的获取和释放有较大开销(通常需要 SMI 或 SCI 交互)。不要在频繁调用的方法(如 _STA)中使用全局锁。优先使用 ASL 的 Serialized 标志或局部变量 Local0~Local7 来避免竞争。

2.5.3 全局锁 vs 序列化方法

特性 全局锁(\_GL) 序列化方法(Serialized)
作用域 跨处理器、跨方法 仅单个方法实例
实现方式 硬件寄存器 + 仲裁器 AML 解释器内部互斥
开销 高(SMI/SCI 交互) 低(纯软件)
适用场景 固件与 OS 共享数据 防止同一方法重入
超时机制 支持(Acquire 超时参数) 不支持(等待当前实例完成)
全局锁获取与释放流程 OS/固件请求锁 硬件仲裁器裁决 获得锁(Acquire) 访问共享资源 释放锁(Release) 等待超时(Timeout) 重试或返回错误 超时值在 Acquire 方法中指定 单位:毫秒,0xFFFF 表示无限等待

2.6 本章知识结构总结

核心概念 关键要点 常见陷阱
ACPI 表结构 RSDP→RSDT/XSDT→DSDT/SSDT;表头含校验和 修改后未更新 Checksum 导致表被拒绝
命名空间 树形结构,根为 \,名称段固定 4 字符 路径大小写错误或名称段长度不对
设备对象 Device 声明,包含 _HID/_ADR/_STA/_CRS 缺少 _STA 导致 OS 认为设备不存在
控制方法 ASL 函数,标准方法以下划线开头 序列化标志选择不当导致重入问题
全局锁 硬件互斥,用于跨处理器同步 在频繁调用的方法中使用导致性能下降
下一步实践:使用 iasl 反编译你的机器 DSDT,找到 \_SB_ 下的 PCI0 设备,观察它的 _ADR 和 _CRS 内容。这是理解 ACPI 表结构最直接的方式。