ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

QEMU ACPI PCI 热插拔接口规范:从 IO 端口协议到 AML 生成的完整解析

2026/9/23 5:39:34 拓冰建站 浏览量
QEMU ACPI PCI 热插拔接口规范:从 IO 端口协议到 AML 生成的完整解析 QEMU ACPI PCI 热插拔接口规范从 IO 端口协议到 AML 生成的完整解析【免费下载链接】qemuOfficial QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.项目地址: https://gitcode.com/gh_mirrors/qe/qemu导读本文以 QEMU 仓库中的官方接口规范文档 docs/specs/acpi_pci_hotplug.rst 为核心骨架完整解析 QEMU 与 ACPI BIOS以及 ACPI 客户操作系统之间用于 PCI 总线 0 热插拔协作的底层协议ACPI GPE 事件块、四个 4 字节槽位寄存器up / down / eject / 可移除性的地址布局与语义并结合 hw/acpi/pcihp.c、hw/acpi/piix4.c、hw/acpi/ich9.c 等源码深入讲解这些寄存器在 QEMU 内部如何被读写、如何触发 SCI 中断、如何生成对应的 AML 方法_EJ0、_RMV、PCEJ、PDSM等以及在不同芯片组PIIX4 / ICH9上的地址差异。读完本文你将能读懂 QEMU ACPI 热插拔的完整数据通路并掌握在pc、q35、virt等机型上排查 PCI 热插拔问题所需的底层知识。一、协议定位为什么需要 QEMU 与 ACPI BIOS 之间的专用接口QEMU 在模拟 PCI 总线时支持设备热插拔而客户机中的操作系统是通过ACPI 事件机制感知设备插入与移除的。整个过程分属两个世界QEMU 侧模拟器负责模拟 PCI 设备、维护槽位状态、在设备插入/移除时产生事件ACPI BIOS 侧客户机固件负责提供 ACPI 表DSDT 等与 AML 方法_EJ0、_RMV等把 QEMU 模拟出来的状态翻译成操作系统可理解的 ACPI 事件与查询。hw/acpi/pcihp.c 文件头的注释明确指出该模块实现的就是 QEMU - ACPI BIOS PCI hotplug interface其接口规范即本文依托的 docs/specs/acpi_pci_hotplug.rst。整个协议在 PIIX4pc类机型上由三个 IO 端口区域构成IO 端口访问宽度用途0xafe0 - 0xafe3字节访问ACPI GPE 块Bit 1GPE.1承载 PCI 热插拔/弹出事件0xae00 - 0xae0f4 字节DWORD访问PCI 热插拔寄存器组up / down / eject / removability注意本文档描述的地址布局是 PIIX4 默认布局ACPI_PCIHP_ADDR_PIIX4即0xae00见 hw/acpi/piix4.cQ35ICH9机型的 PCI 热插拔寄存器基址不同0x0cc0见 include/hw/acpi/ich9.h这一点在第六节专门讨论。二、ACPI GPE 块IO 端口 0xafe0-0xafe3字节访问这是通用的 ACPI GPEGeneral Purpose Event块。协议规定Bit 1GPE.1用于通过 SCI 中断向 ACPI BIOS 通知 PCI 热插拔/弹出事件。也就是说QEMU 每检测到一个热插拔事件设备插入或移除请求就把 GPE 状态寄存器中的 bit 1 置位并触发 SCISystem Control InterruptACPI BIOS 的 GPE.1 事件处理器被唤醒后再去读取下文的 PCI 槽位寄存器确定是哪些槽位发生了插入up或移除down事件进而向操作系统发出Device Check/Eject Request通知。在源码中PIIX4 的 GPE 块定义如下hw/acpi/piix4.c#define GPE_BASE 0xafe0 #define GPE_LEN 4piix4_pm_realize阶段将这段 IO 空间注册为名为acpi-gpe0的 MemoryRegionhw/acpi/piix4.cmemory_region_init_io(s-io_gpe, OBJECT(s), piix4_gpe_ops, s, acpi-gpe0, GPE_LEN); memory_region_add_subregion(parent, GPE_BASE, s-io_gpe);事件发送链路的入口是acpi_send_event()当 hw/acpi/pcihp.c 中设备插入完成acpi_pcihp_device_plug_cb或移除请求被登记acpi_pcihp_device_unplug_request_cb后都会调用acpi_send_event(DEVICE(hotplug_dev), ACPI_PCI_HOTPLUG_STATUS)该调用最终经由piix4_send_gpe()→acpi_send_gpe_event()把事件写入 GPE 寄存器并拉起 SCI见 hw/acpi/piix4.c。GPE 块采用字节访问、长度仅 4 字节因此 GPE.1 对应的是该寄存器第二个字节的 bit 1。三、PCI 槽位寄存器组0xae00-0xae0f协议核心PCI 热插拔寄存器组由四个 DWORD4 字节寄存器组成每个寄存器一个 bit 对应一个 PCI 槽位32 位寄存器因此最多直接表达 32 个槽位。整个寄存器组的内部偏移在 hw/acpi/pcihp.c 中定义为#define PCI_UP_BASE 0x0000 /* offset 0x00: 插入通知挂起 */ #define PCI_DOWN_BASE 0x0004 /* offset 0x04: 移除通知挂起 */ #define PCI_EJ_BASE 0x0008 /* offset 0x08: 弹出请求 / 特性集 */ #define PCI_RMV_BASE 0x000c /* offset 0x0c: 可移除性状态 */ #define PCI_SEL_BASE 0x0010 /* offset 0x10: 总线选择BSEL */ #define PCI_AIDX_BASE 0x0014 /* offset 0x14: acpi-index 辅助寄存器 */说明后两个寄存器PCI_SEL_BASE、PCI_AIDX_BASE是寄存器组向 0x18 字节长度扩展后引入的辅助寄存器用于多总线场景与acpi-index查询详见第五、七节本节先聚焦规范文档描述的四个核心寄存器。3.1 PCI 槽位插入通知挂起IO 端口 0xae00-0xae034 字节访问只读语义每个槽位对应一个 bit表示该槽位存在插入事件待通知。读取者ACPI BIOS 的 GPE.1 处理器。GPE.1 被触发后BIOS 读取此寄存器将置位的槽位翻译为对 OS 的Device Check通知。访问属性只读Read-only。源码实现hw/acpi/pcihp.ccase PCI_UP_BASE: val s-acpi_pcihp_pci_status[bsel].up; if (s-use_acpi_hotplug_bridge) { s-acpi_pcihp_pci_status[bsel].up 0; } trace_acpi_pci_up_read(val); break;up位由acpi_pcihp_device_plug_cb()在设备插入时置位hw/acpi/pcihp.cs-acpi_pcihp_pci_status[bsel].up | (1U slot); acpi_send_event(DEVICE(hotplug_dev), ACPI_PCI_HOTPLUG_STATUS);即置 up 位 发 GPE 事件两步BIOS 收到 SCI 后读 up 寄存器即可知道是哪个槽位插入了设备。值得注意的是在启用桥接热插拔use_acpi_hotplug_bridge时读 up 寄存器会产生清零副作用读后即清避免同一事件被重复通知。3.2 PCI 槽位移除通知IO 端口 0xae04-0xae074 字节访问只读语义每个槽位对应一个 bit表示该槽位有移除请求待通知。读取者ACPI BIOS 的 GPE.1 处理器用于向 OS 发送Eject Request通知。访问属性只读Read-only。源码实现hw/acpi/pcihp.ccase PCI_DOWN_BASE: val s-acpi_pcihp_pci_status[bsel].down; trace_acpi_pci_down_read(val); break;down位在acpi_pcihp_device_unplug_request_cb()中登记hw/acpi/pcihp.cs-acpi_pcihp_pci_status[bsel].down | (1U slot); acpi_send_event(DEVICE(hotplug_dev), ACPI_PCI_HOTPLUG_STATUS);与 up 寄存器不同down 寄存器读取后不会被清空——它代表待执行的移除请求要等 OS 侧_EJ0真正发起弹出后才由acpi_pcihp_eject_slot()清除见 3.3 与 3.4。3.3 PCI 设备弹出IO 端口 0xae08-0xae0b4 字节访问这是寄存器组中唯一的读写双用途寄存器也是 ACPI 与 QEMU 交互最核心的一个写WriteACPI BIOS 的_EJ0方法请求移除设备时写入。每个槽位一个 bit。读Read热插拔特性寄存器Hotplug features register供平台识别可用特性。当前基础特性集为无任何 bit 置位即读取恒为 0。规范文档列出的当前基础特性集如下偏移名称访问说明0xae00up 寄存器只读4 字节访问每槽一位插入通知挂起0xae04down 寄存器只读4 字节访问每槽一位移除通知挂起0xae08eject 寄存器读/写4 字节访问写每槽一位弹出请求读热插拔特性集0xae0c热插拔能力寄存器只读4 字节访问每槽一位源码实现hw/acpi/pcihp.ccase PCI_EJ_BASE: trace_acpi_pci_features_read(val); /* 读val 保持 0即基础特性集无 bit 置位 */ break;写入路径hw/acpi/pcihp.ccase PCI_EJ_BASE: if (s-hotplug_select ACPI_PCIHP_MAX_HOTPLUG_BUS) { break; } acpi_pcihp_eject_slot(s, s-hotplug_select, data); trace_acpi_pci_ej_write(addr, data); break;acpi_pcihp_eject_slot()是弹出动作的实际执行者hw/acpi/pcihp.c它先清除该槽位的 down 与 up 挂起位表示请求已完成然后沿总线的子设备链表找到该槽位上的 PCI 设备经hotplug_handler_unplug()完成设备注销并从 QOM 树中卸载。源码中还处理了若干边界桥设备与 SR-IOV 虚拟功能不可热拔acpi_pcihp_pc_no_hotplug()见 hw/acpi/pcihp.c以及 virtio-net failover 场景下partially_hotplugged设备的特殊处理保留设备引用以便故障回切。3.4 PCI 可移除性状态IO 端口 0xae0c-0xae0f4 字节访问只读语义每个槽位一个 bit表示该槽位上的设备是否可移除。使用者ACPI BIOS 的_RMV方法向操作系统指示可移除性状态。访问属性只读Read-only。源码实现hw/acpi/pcihp.ccase PCI_RMV_BASE: val s-acpi_pcihp_pci_status[bsel].hotplug_enable; trace_acpi_pci_rmv_read(val); break;hotplug_enable位图在acpi_pcihp_update_hotplug_bus()中维护hw/acpi/pcihp.c重置时先执行挂起的移除再默认将全部槽位置 1hotplug_enable ~0随后遍历总线上的现有设备把不可热拔设备桥、非hotpluggable类、SR-IOV VF对应的槽位清零。这样 OS 通过_RMV查询到的可移除性就是该槽位是否允许弹出的准确答案。四、GPE.1 与槽位寄存器一次热插拔事件的完整数据通路综合上述寄存器与 hw/acpi/pcihp.c 的源码一次热插入事件的完整流程为QEMU 侧执行device_addacpi_pcihp_device_plug_cb()被调用置位up[slot]acpi_send_event(ACPI_PCI_HOTPLUG_STATUS)→ GPE 寄存器 bit 1 置位 → SCI 中断ACPI BIOS GPE.1 处理器运行读取 up 寄存器0xae00得到置位的槽位位图BIOS 对每个置位槽位调用 AML 中的DVNT(PCIU, 1)Device CheckOS 收到设备插入通知扫描并枚举新设备。一次热移除事件的流程为QEMU 侧执行device_delacpi_pcihp_device_unplug_request_cb()被调用登记down[slot]并设置pending_deleted_event防止重复删除见 hw/acpi/pcihp.c发送 GPE.1 事件BIOS GPE.1 处理器读取 down 寄存器0xae04得到待移除槽位BIOS 调用DVNT(PCID, 3)Eject RequestOS 关闭设备驱动OS 调用设备的_EJ0方法BIOS 向 eject 寄存器0xae08写入对应槽位 bitacpi_pcihp_eject_slot()执行真正的弹出清除 up/down 位并注销设备。这个置位 → 发 GPE → 读位图 → 通知 OS → OS 回调_EJ0的闭环就是整个 PCI 热插拔协议的精髓而它完全由上述 4 个 IO 寄存器 1 个 GPE bit 承载。五、AML 侧寄存器如何变成 OS 能用的 ACPI 对象QEMU 不仅在 IO 层面实现寄存器还负责把它们暴露到 ACPI 表中。build_acpi_pci_hotplug()hw/acpi/pcihp.c在_SB.PCI0作用域内生成如下 AML 对象PCSTOperationRegion覆盖pcihp_addr起的 8 字节内含两个 DWORD 字段PCIU映射到 up 寄存器0xae00与PCID映射到 down 寄存器0xae04供DVNT通知用SEJOperationRegion覆盖pcihp_addr ACPI_PCIHP_SEJ_BASESEJ_BASE 0x8见 include/hw/acpi/pcihp.h起的 4 字节内含 DWORD 字段B0EJ对应 eject 寄存器0xae08BNMROperationRegion覆盖pcihp_addr ACPI_PCIHP_BNMR_BASEBNMR_BASE 0x10见 include/hw/acpi/pcihp.h起的 8 字节内含BNUM总线号/BSEL 选择与PIDXacpi-index 查询两个 DWORDBLCKMutex保护上述寄存器的多方法并发访问PCEJ(Bus, Slot)方法OS 的设备_EJ0最终调用它内部加锁后写BNUM Bus、B0EJ 1 Slot从而把弹出槽位落到 eject 寄存器触发第 3.3 节的acpi_pcihp_eject_slot()AIDX(Bus, Slot)方法写PIDX后读取向 OS 返回设备acpi-indexPDSM方法实现 PCI Firmware Specification 3.1 的_DSM函数 0 查询支持能力、函数 7 为设备命名帮助 Windows 等系统为 PCI 设备建立稳定的acpi-index命名。此外build_append_pcihp_resources()hw/acpi/pcihp.c会向 ACPI 表追加一个名为PHPR、_HID为PNP0A06的假设备用_CRS声明0xae00起始、长度ACPI_PCIHP_SIZE0x1824 字节见 include/hw/acpi/pcihp.h的 IO 资源确保操作系统不会把这段热插拔寄存器空间当作普通设备资源冲突处理。六、地址布局差异PIIX4、ICH9 与 ARM virt规范文档给出的是 PIIX4 布局基址0xae00、GPE0xafe0但 QEMU 并非所有机型都使用同一地址这一点在阅读源码时必须注意机型/芯片组PCI 热插拔寄存器基址定义位置PIIX4pc默认0xae00ACPI_PCIHP_ADDR_PIIX4hw/acpi/piix4.cICH9q350x0cc0ACPI_PCIHP_ADDR_ICH9include/hw/acpi/ich9.hARMvirtGED 设备动态分配hw/acpi/generic_event_device.c其中 ICH9 的 GPE 块长度与 PIIX4 也不同ICH9_PMIO_GPE0_LEN见 hw/acpi/ich9.c。因此0xae00 是 PCI 热插拔寄存器这一说法严格成立的范围是 PIIX4Q35 上需要到0x0cc0附近查找同样的 up/down/eject/rmv 布局。IO 基址还通过acpi-pcihp-io-base、acpi-pcihp-io-len两个属性暴露见 include/hw/acpi/pcihp.h支持在需要时重新定位该寄存器组。七、向多总线的扩展BSEL 与 hotplug_select基础协议只描述了一个 32 位位图对应根总线PCI bus 0上的 32 个槽位。随着 PCIe 桥与多总线拓扑的出现QEMU 通过BSELBus SELect机制扩展了协议每个支持 ACPI 热插拔的 PCI 总线被分配一个acpi-pcihp-bsel属性值见 include/hw/acpi/pcihp.h上限ACPI_PCIHP_MAX_HOTPLUG_BUS 256根总线默认ACPI_PCIHP_BSEL_DEFAULT 0PCI_SEL_BASE0xae10寄存器保存当前选中的 bsel写入后后续对 up/down/eject/rmv 寄存器的访问就都作用于该总线pci_read/pci_write中的s-hotplug_select索引见 hw/acpi/pcihp.c状态数组acpi_pcihp_pci_status[ACPI_PCIHP_MAX_HOTPLUG_BUS]为每条可能的热插拔总线维护独立的 up/down/hotplug_enable 位图include/hw/acpi/pcihp.h。桥接热插拔由acpi-pci-hotplug-with-bridge-support属性控制include/hw/acpi/acpi.h启用后acpi_set_bsel()会为桥后面的从属总线也分配 BSELhw/acpi/pcihp.c并在 AML 中递归生成各级PCNT通知方法build_append_notification_callback()hw/acpi/pcihp.c使桥下槽位的插入/移除事件能够沿 PCI 拓扑逐级上报。八、状态迁移与兼容性保障热插拔状态属于设备运行时状态必须纳入迁移live migration体系。QEMU 为此提供了vmstate_acpi_pcihp_pci_status与VMSTATE_PCI_HOTPLUG宏见 include/hw/acpi/pcihp.h序列化hotplug_select、各总线的acpi_pcihp_pci_status[]位图与acpi_indexPIIX4 的 VMState 中即包含该段状态hw/acpi/piix4.c从而在迁移目标端恢复插入通知待处理 / 移除请求待执行的现场acpi_pcihp_reset()hw/acpi/pcihp.c在复位时重算 BSEL 并执行挂起的移除保证复位后位图与真实总线拓扑一致。此外ACPI_PCIHP_SIZE0x18为后续协议扩展预留了空间——当前特性寄存器读回值为 0基础特性集未来若引入新特性可通过读0xae08返回的位图进行能力协商而不会破坏既有 ACPI BIOS 的兼容性。九、小结QEMU 的 ACPI PCI 热插拔接口是一套极简但完备的寄存器级协议事件通道GPE 块0xafe0-0xafe3的 bit 1 通过 SCI 通知 ACPI BIOS有 PCI 热插拔事件状态通道0xae00up插入通知、0xae04down移除通知、0xae08eject弹出请求/特性集、0xae0c可移除性四个 4 字节寄存器各以每槽一位的位图承载语义实现载体QEMU 侧由 hw/acpi/pcihp.c 完成寄存器读写、事件产生与弹出执行AML 侧由build_acpi_pci_hotplug()生成PCIU/PCID/B0EJ/BNUM/PIDX字段与PCEJ/AIDX/PDSM方法配合_EJ0、_RMV让操作系统完成真正的热插拔平台差异PIIX4 基址0xae00ICH9Q35基址0x0cc0ARM virt 通过 GED 动态分配排查问题前先确认目标机型扩展机制BSEL/hotplug_select把 32 槽位位图扩展到 256 条总线acpi-index/_DSM为设备命名与固件兼容提供支撑。对于想要深入调试 QEMU 中 PCI 热插拔行为的开发者建议从 hw/acpi/pcihp.c 的pci_read/pci_write入手可用-d trace:acpi_pci_*打开对应 trace 事件观察寄存器访问再对照本文的寄存器语义逐项核对即可快速定位是事件未发出、通知未送达还是_EJ0弹出链路异常。【免费下载链接】qemuOfficial QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.项目地址: https://gitcode.com/gh_mirrors/qe/qemu创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考