4、ACPI与热插拔:ACPI _EJ0/_STA方法、ACPI热插拔事件通知机制、ACPI与PCI子系统的交互
ACPI(Advanced Configuration and Power Interface)是x86/ARM64平台固件与操作系统之间的标准接口。在PCIe热插拔场景中,ACPI承担两个关键角色:电源/存在状态查询(通过AML方法)和热插拔事件通知(通过GPE/Notify)。本章深入分析ACPI与PCI子系统的交互机制,以及驱动开发者必须掌握的_EJ0、_STA等核心方法。
核心认知:ACPI热插拔是“固件告知OS,OS驱动执行”的协作模型。固件通过AML方法暴露槽位能力,OS通过ACPIdriver解析并触发PCI核心层完成枚举/销毁。
4.1 ACPI _EJ0与_STA方法:槽位能力描述
ACPI命名空间中的每个PCIe槽位设备(通常位于\_SB.PCI0.RP01等路径下)必须实现以下两个控制方法,OS才能识别其为热插拔槽位:
| AML方法 | 功能定义 | 返回值 | 驱动调用时机 |
|---|---|---|---|
| _EJ0 | Eject(弹出)请求,OS调用此方法触发槽位断电/物理弹出 | 无(Void) | 用户触发弹出操作时 |
| _STA | Status(状态),返回槽位当前是否存在设备、是否使能 | Bitmask(0x0F标准位) | 枚举/轮询/事件处理时 |
| _PS0/_PS3 | 电源状态控制(可选,用于D0/D3hot切换) | 无 | 电源管理事件 |
_STA返回值解析:
- Bit 0:设备存在(1=存在)
- Bit 1:设备使能(1=使能)
- Bit 2:设备在UI中显示(1=显示)
- Bit 3:设备功能正常(1=正常)
- 完整值
0x0F表示设备存在且正常工作
驱动开发者注意:在pciehp或acpiphp驱动中,_STA返回0x00表示槽位为空,驱动应销毁对应PCI设备;_STA返回0x0F表示设备在位,驱动应触发枚举。
// ASL示例:PCIe根端口RP01下的热插拔槽位
Device (RP01)
{
Name (_ADR, 0x001C0000) // PCI设备地址
Method (_STA, 0, NotSerialized)
{
// 检查GPIO状态或槽位存在寄存器
If (LEqual (SSTS, 0x01))
{
Return (0x0F) // 设备存在且正常
}
Else
{
Return (0x00) // 槽位为空
}
}
Method (_EJ0, 1, NotSerialized)
{
// 执行物理弹出:断电、释放锁存器
Store (0x00, PWRG)
Sleep (100)
Store (0x01, EJCTL)
}
}
4.2 ACPI热插拔事件通知机制
ACPI通过两种方式向OS传递热插拔事件:
- GPE(General Purpose Event):硬件中断方式,槽位状态变化(如存在检测引脚电平跳变)触发GPE,ACPIdriver查询对应设备的_OST或_EXL方法。
- Notify():AML代码主动调用Notify(Device, 0x00)或Notify(Device, 0x03),告知OS设备插入/移除。
| 通知类型 | Notify值 | 含义 | OS响应动作 |
|---|---|---|---|
| Bus Check | 0x00 | 设备插入或重新枚举 | 重新扫描PCI总线,枚举新设备 |
| Device Check | 0x01 | 特定设备状态变化 | 查询_STA,决定添加或移除 |
| Eject Request | 0x03 | 固件请求弹出设备 | 执行_EJ0,销毁PCI设备 |
| Device Wake | 0x02 | 设备唤醒系统 | 电源管理恢复 |
常见陷阱:部分固件在Notify(0x00)后不更新_STA返回值,导致驱动误判设备状态。驱动应在收到Notify后主动调用_STA确认,而非依赖缓存。
// 内核ACPIdriver热插拔事件处理核心路径
static void acpiphp_check_bridge(struct acpiphp_bridge *bridge)
{
struct acpiphp_slot *slot;
list_for_each_entry(slot, &bridge->slots, node) {
unsigned long long sta;
acpi_status status;
// 1. 调用_STA获取当前状态
status = acpi_evaluate_integer(slot->handle, "_STA", NULL, &sta);
if (ACPI_FAILURE(status))
continue;
// 2. 判断设备是否存在
if (sta & ACPI_STA_DEVICE_PRESENT) {
if (!slot->device) // 新插入
acpiphp_enable_slot(slot);
} else {
if (slot->device) // 已移除
acpiphp_disable_slot(slot);
}
}
}
4.3 ACPI与PCI子系统的交互接口
Linux内核通过以下数据结构将ACPI设备与PCI设备绑定:
| 内核组件 | 文件/模块 | 职责 |
|---|---|---|
| acpiphp | drivers/pci/hotplug/acpiphp_*.c | ACPI热插拔驱动,监听ACPI事件并调用PCI核心 |
| pci-acpi | drivers/pci/pci-acpi.c | PCI设备与ACPI句柄绑定,电源管理 |
| pciehp | drivers/pci/hotplug/pciehp*.c | 原生PCIe热插拔(不依赖ACPI,但可共存) |
交互关键函数:
acpi_pci_find_root():根据PCI域号查找对应ACPI根桥对象acpi_bus_trim():移除ACPI设备及其子设备,触发PCI设备销毁acpi_bus_scan():扫描ACPI命名空间,发现新设备并触发PCI枚举pci_acpi_scan_root():ACPI描述PCI根桥时调用,创建PCI域
// ACPI事件到PCI动作的完整链路(以设备插入为例)
// 1. ACPIdriver收到Notify(0x00)
acpi_os_execute_deferred(acpi_ev_notify_dispatch);
// 2. 调用acpiphp的notify回调
acpiphp_notify(acpi_handle handle, u32 type, void *data)
{
struct acpiphp_context *context = data;
acpiphp_check_bridge(context->bridge);
}
// 3. 内部调用pci_scan_slot()枚举新设备
void acpiphp_enable_slot(struct acpiphp_slot *slot)
{
struct pci_bus *bus = slot->bridge->pci_bus;
struct pci_dev *dev;
pci_lock_rescan_remove();
dev = pci_scan_single_device(bus, slot->device);
if (dev) {
pci_bus_assign_resources(bus);
pci_bus_add_device(dev);
}
pci_unlock_rescan_remove();
}
工程经验:在调试ACPI热插拔问题时,优先检查/sys/firmware/acpi/tables/DSDT中的槽位定义。使用
acpidump导出AML,用iasl反编译后搜索_EJ0和_STA方法,确认固件是否正确实现了槽位控制。
4.4 实际工程中的ACPI热插拔调试
在嵌入式平台(如ARM64服务器)上,ACPI热插拔是主要方案。以下是调试检查清单:
- 确认ACPI表:检查DSDT中PCIe根端口下是否有_EJ0和_STA方法
- 确认驱动加载:
lsmod | grep acpiphp确保acpiphp模块已加载 - 查看槽位状态:
cat /sys/bus/pci/slots/*/power - 模拟热插拔:
echo 1 > /sys/bus/pci/slots/N/remove触发_EJ0 - 跟踪事件:
echo 'file drivers/pci/hotplug/acpiphp* +p' > /sys/kernel/debug/dynamic_debug/control
快速验证:在系统启动后,执行
acpi_listen命令,然后物理插入/拔出PCIe设备,观察是否有ACPI事件输出。若无输出,说明固件未正确生成GPE或Notify。
通过本章学习,你应该掌握ACPI _EJ0/_STA方法的作用、ACPI热插拔事件通知的两种途径(GPE和Notify),以及ACPI与PCI子系统通过pci-acpi桥接模块的交互机制。这些知识是编写可靠PCIe热插拔驱动的理论基础。