QEMU ACPI 内存热插拔接口规范详解:IO 端口协议、事件语义与源码实现
2026/9/23 2:46:56 网站建设 项目流程
  • 虚拟化
  • 硬件仿真

【免费下载链接】qemu

Official 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_mem_hotplug.rst,完整讲解 QEMU 与 ACPI BIOS 之间内存热插拔(Memory Hotplug)的底层通信协议。文章首先给出 IO 端口 0xa00-0xa17 的逐字节寄存器布局与读写语义,然后结合 hw/acpi/memory_hotplug.c 与 include/hw/acpi/memory_hotplug.h 的源码实现,剖析 QEMU 侧状态机、ACPI AML 代码生成与热移除事件流。读者读完本文后,将能准确理解 QEMU 内存热插拔的 ACPI 接口全貌,并可直接对照源码与 docs/memory-hotplug.txt 中的 QMP/HMP 实际操作命令进行联调。

1. 协议概览:GPE.3 与内存热插拔 IO 区

QEMU 的内存热插拔功能自 v2.1.0 引入。从 ACPI 视角看,GPE.3 中断事件(General Purpose Event bit 3)被专门保留用于通知 OSPM(操作系统电源管理,即 guest 内 ACPI 驱动)内存热添加(hot-add)与热移除(hot-remove)事件。当 QEMU 检测到新的内存设备插入、或管理端发起移除请求时,都会通过该 GPE 向 guest 发出系统控制中断(SCI),OSPM 随后通过扫描内存热插拔 IO 区域来定位发生事件的设备槽位。

与 GPE.3 配套的是一个独占的 IO 端口区域,地址范围0xa00-0xa17(共 24 字节),支持 1-4 字节访问。该区域在源码中的定义如下:

  • include/hw/acpi/memory_hotplug.h 定义了MEMORY_HOTPLUG_IO_LEN 24,即 IO 区域总长度;
  • hw/acpi/memory_hotplug.c 通过acpi_memory_hotplug_ops注册读写回调,valid.min_access_size = 1max_access_size = 4,与规范中"1-4 字节访问"完全一致,字节序为DEVICE_LITTLE_ENDIAN
  • hw/acpi/memory_hotplug.c 的acpi_memory_hotplug_init()将上述 IO 区域注册为"acpi-mem-hotplug",其槽位数取自machine->ram_slots(即启动参数-m中的slots=N),若为 0 则整个热插拔功能不注册。

该 IO 区域在不同机器类型中的挂载位置不同,可从源码调用点确认:i440fx 挂载于 PIIX4 ACPI 设备(hw/acpi/piix4.c)、ICH9 挂载于 LPC PCI 的 IO 空间(hw/acpi/ich9.c)、GED(Generic Event Device)机器使用内存容器区域(hw/acpi/generic_event_device.c)。

2. 读访问行为(QEMU -> OSPM)

对 0xa00-0xa17 区域的读操作返回当前选中槽位(由写操作设置的 selector 决定)所对应的内存设备属性。规范定义了以下读偏移:

偏移长度含义
0x0-0x34 字节内存设备物理地址低 32 位(Lo part)
0x4-0x74 字节内存设备物理地址高 32 位(Hi part)
0x8-0xb4 字节内存设备大小(字节)低 32 位
0xc-0xf4 字节内存设备大小(字节)高 32 位
0x10-0x134 字节内存设备 proximity domain(NUMA 节点,供_PXM方法使用)
0x141 字节内存设备状态字段(status bits)
0x15-0x173 字节保留(reserved)

2.1 状态字段(0x14)位定义

偏移 0x14 的读回值按位定义如下:

  • bit 0:设备已启用(enabled),guest 可以使用该内存设备;
  • bit 1:设备插入事件(insert event),用于标识一个尚未向 OSPM 发出 Device Check 事件的设备;该位仅在 bit 1 置位时有效(原文此处语义为"用于区分未发出 device check 事件的设备",规范行文如此,实际由 OSPM 消费);
  • bit 2:设备移除事件(remove event),用于标识一个尚未向 OSPM 发出 Eject Request 请求的设备;
  • bit 3-7:保留位,OSPM 必须忽略。

2.2 源码实现对照

上述读语义在 hw/acpi/memory_hotplug.c 的acpi_memory_hotplug_read()中逐偏移实现:

  • 0x0/0x4:读取 DIMM 的PC_DIMM_ADDR_PROP属性(物理地址低/高 32 位);
  • 0x8/0xc:读取PC_DIMM_SIZE_PROP(大小低/高 32 位);
  • 0x10:读取PC_DIMM_NODE_PROP(NUMA 节点号);
  • 0x14:将MemStatus结构中的is_enabledis_insertingis_removing三个布尔值按 1/2/4 位打包返回;
  • 其余偏移返回~0(全 1)。

MemStatus结构体在 include/hw/acpi/memory_hotplug.h 中定义,包含dimm(关联的设备对象)、is_enabledis_insertingis_removingost_eventost_status六个字段,是 QEMU 侧每个内存槽位的完整状态载体。

3. 写访问行为(OSPM -> QEMU)

对 0xa00-0xa17 区域的写操作用于选择槽位、上报 OST 事件/状态码、以及触发设备移除。规范定义的写偏移如下:

偏移长度含义
0x0-0x34 字节内存设备槽位选择器(slot selector),选中活动内存设备;此后对该区域其他寄存器的访问都作用于该设备
0x4-0x74 字节OSPM 上报的_OST事件码(event code)
0x8-0xb4 字节OSPM 上报的_OST状态码(status code)
0xc-0x138 字节保留,写入被忽略
0x141 字节内存设备控制字段(control bits)

3.1 控制字段(0x14)位定义

  • bit 0:保留。OSPM 在写入前必须将其清零。规范特别强调:由于 2.4 之前的版本存在 BUG(写入其他字段时该位不会被清除),因此该位必须保持保留状态,不得尝试复用;
  • bit 1:置 1 清除选中设备的插入事件。OSPM 在为其发出 Device Check 事件后写入;
  • bit 2:置 1 清除选中设备的移除事件。OSPM 在为其发出 Eject Request 请求后写入;
  • bit 3:置 1 发起设备弹出(initiates device eject)。OSPM 触发内存设备移除并调用_EJ0方法时写入;
  • bit 4-7:保留,OSPM 在写入前必须清零。

3.2 越界槽位行为

规范明确:选择超出当前范围的设备槽位(slot selector 越界)不会对平台产生任何副作用——

  • 对未在上述文档中列出的内存热插拔寄存器进行写访问,被直接忽略;
  • 对未列出的寄存器进行读访问,返回全 1(所有位均为 1)。

源码佐证:acpi_memory_hotplug_read()中当mem_st->selector >= mem_st->dev_count时直接return 0并记录 trace(hw/acpi/memory_hotplug.c),而 default 分支返回~0(hw/acpi/memory_hotplug.c);写路径则在 selector 越界时直接返回(hw/acpi/memory_hotplug.c)。

3.3 写路径源码对照

acpi_memory_hotplug_write()(hw/acpi/memory_hotplug.c)实现如下关键逻辑:

  • 偏移 0x0:保存datamem_st->selector,完成槽位选择;
  • 偏移 0x4:保存 OST 事件码到mdev->ost_event(对值为 1/3 的事件目前仅留有 TODO 注释,未做额外处理);
  • 偏移 0x8:保存 OST 状态码到mdev->ost_status,并构造ACPIOSTInfo通过qapi_event_send_acpi_device_ost()向管理端发送ACPI_DEVICE_OST QMP 事件
  • 偏移 0x14:按优先级处理清除插入事件(bit 1)、清除移除事件(bit 2)、触发弹出(bit 3)。弹出路径校验is_enabled后通过qdev_get_hotplug_handler()拿到热插拔控制器并调用hotplug_handler_unplug(),失败时发送DEVICE_UNPLUG_GUEST_ERROR事件,成功则object_unparent()删除设备并发送设备删除事件。

由此可以看到,写偏移 0x14 是 QEMU 侧唯一"主动动作"的入口,OSPM 的每一次_EJ0调用最终都收敛到这一处写操作。

4. 内存热移除流程时序图

规范给出了完整的内存热移除(hot remove)流程,涵盖 QEMU、OSPM、Guest OS 三方协作。以下为原文流程图的忠实还原:

+-------------+ +-----------------------+ +------------------+ | 1. QEMU | | 2. QEMU | |3. QEMU | | device_del +---->+ device unplug request +----->+Send SCI to guest,| | | | cb | |return control to | | | | | |management | +-------------+ +-----------------------+ +------------------+ +---------------------------------------------------------------------+ +---------------------+ +-------------------------+ | OSPM: | remove event | OSPM: | | send Eject Request, | | Scan memory devices | | clear remove event +<-------------+ for event flags | | | | | +---------------------+ +-------------------------+ | | +---------v--------+ +-----------------------+ | Guest OS: | success | OSPM: | | process Ejection +----------->+ Execute _EJ0 method, | | request | | set eject bit in flags| +------------------+ +-----------------------+ |failure | v v +------------------------+ +-----------------------+ | OSPM: | | QEMU: | | set OST event & status | | call device unplug cb | | fields | | | +------------------------+ +-----------------------+ | | v v +------------------+ +-------------------+ |QEMU: | |QEMU: | |Send OST QMP event| |Send device deleted| | | |QMP event | +------------------+ | | +-------------------+

该流程与源码回调一一对应:

  1. 管理端执行device_del后,QEMU 调用acpi_memory_unplug_request_cb()(hw/acpi/memory_hotplug.c),将对应槽位is_removing置真,并通过acpi_send_event(..., ACPI_MEMORY_HOTPLUG_STATUS)触发 GPE.3 向 guest 发送 SCI;
  2. OSPM 收到中断后扫描各内存设备的移除事件标志(对应 AMLMSCN扫描方法),对置位槽位发出 Eject Request 通知,并写控制字段 bit 2 清除移除事件;
  3. Guest OS 处理弹出请求,成功后将调用_EJ0方法——即写控制字段 bit 3,触发 QEMU 侧弹出逻辑;失败则通过_OST写 OST 事件/状态码上报失败原因,QEMU 侧发送ACPI_DEVICE_OSTQMP 事件;
  4. 弹出成功后 QEMU 调用acpi_memory_unplug_cb()(hw/acpi/memory_hotplug.c)将is_enabled置假、dimm置空,并向管理端发送设备删除 QMP 事件,管理端随后可执行object_del释放内存后端。

5. 底层机制:AML 代码生成与槽位状态机

5.1 ACPI 表生成

QEMU 的内存热插拔 AML 由build_memory_hotplug_aml()(hw/acpi/memory_hotplug.c)动态生成,核心内容包括:

  • 一个PNP0A06内存热插拔控制器设备(MHPD),声明_CRS资源模板与HPMROperationRegion(IO 或 MMIO 方式,由AmlRegionSpace rs参数决定);
  • 一个PNP0A06的 DIMM 设备容器(\_SB.MHPC),声明MDNR槽位数量、MLCK互斥锁以及完整的字段布局(MRBL/MRBH/MRLL/MRLH/MPX/MES/MINS/MRMV/MEJ/MSEL/MOEV/MOSC);
  • MSCN扫描方法:循环所有槽位,对插入事件置位的槽位发出 Device Check(值 1)通知、对移除事件置位的槽位发出 Eject Request(值 3)通知,并清除对应事件位——这正是 OSPM 收到 GPE 后执行的入口(hw/acpi/memory_hotplug.c);
  • 每个槽位对应一个MP%02X设备(PNP0C80),其_CRS/_STA/_PXM/_OST/_EJ0方法统一委托给容器内的共享方法(MCRS/MRST/MPXM/MOST/MEJ0),访问前均获取MLCK互斥锁以保证并发安全(hw/acpi/memory_hotplug.c);
  • MTFY通知分发方法按槽位索引将Notify()定向到对应MP%02X设备。

该函数由各平台 ACPI 构建代码调用:x86 的 DSDT(hw/i386/acpi-build.c)、ARM virt(hw/arm/virt-acpi-build.c)与 loongarch virt(hw/loongarch/virt-acpi-build.c)。若传入event_handler_method(如 x86 的 GPE 处理方法),还会生成调用MSCN的事件处理方法,将 GPE 事件与扫描逻辑绑定。

5.2 热插状态机与迁移

QEMU 侧每个槽位的状态流转通过四个回调维护:

  • acpi_memory_plug_cb():设备插入时置is_enabled = true,若dev->hotplugged为真(即运行期热插而非启动时冷插)则置is_inserting = true并发送 GPE(hw/acpi/memory_hotplug.c);
  • acpi_memory_unplug_request_cb():置is_removing = true并发送 GPE;
  • acpi_memory_unplug_cb():清is_enableddimm引用;
  • acpi_memory_ospm_status():将全部槽位的 OST 信息汇集成ACPIOSTInfo列表,供query-acpi-ospm-status类 QMP 命令查询。

值得注意的是,MemStatus中的is_removing字段被注释为"设备已被请求弹出",但该字段未纳入迁移状态vmstate_memhp_sts只迁移is_enabledis_insertingost_eventost_status(hw/acpi/memory_hotplug.c),is_removing属于运行期瞬时状态。此外,当平台未启用内存热插拔时,hw/acpi/acpi-mem-hotplug-stub.c 提供了全部接口的空实现(stub),保证构建与调用的一致性。

6. 联调实践:从 QMP 命令到 ACPI 事件

接口规范之外,内存热插拔的完整使用路径可参考 docs/memory-hotplug.txt。核心要点如下:

启动参数-m [size=]megs[,slots=n,maxmem=size]。其中slots决定热插槽位数(即ram_slots,直接决定 IO 区域中可寻址的设备数),maxmem决定 guest 最大可扩展内存。示例:

qemu [...] -m 1G,slots=3,maxmem=4G

热添加:通过两个 HMP 命令配合完成——

(qemu) object_add memory-backend-ram,id=mem1,size=1G (qemu) device_add pc-dimm,id=dimm1,memdev=mem1

设备插入后,QEMU 的acpi_memory_plug_cb()会置插入事件位并经 GPE.3 通知 guest,OSPM 扫描后完成内存上线。文件后端(如 hugetlbfs 大页)可改用memory-backend-file并指定mem-path

热移除:同样需要两个命令——

(qemu) device_del dimm1 (qemu) object_del mem1

device_del触发的正是第 4 节流程图中第 1-2 步(acpi_memory_unplug_request_cb与 GPE 通知),OSPM 完成 eject 握手后 QEMU 才真正销毁设备并向管理端发送删除事件,随后方可安全执行object_del释放后端。

7. 小结

QEMU 的 ACPI 内存热插拔接口是一套设计紧凑的 IO 端口协议:通过 0xa00-0xa17 区域内"写选择器 + 读写设备属性/状态"的方式,配合 GPE.3 中断、MSCN扫描方法与_EJ0/_OST方法,实现了 guest 与 QEMU 之间完整的热添加、热移除握手。规范中关于保留位(尤其 2.4 前 BUG 的历史包袱)、越界槽位"读全 1、写忽略"的容错约定,都在 hw/acpi/memory_hotplug.c 的读写回调中得到了逐字落实。理解这份规范,是调试 guest 内存热插拔行为、扩展新平台 ACPI 构建(如新增build_memory_hotplug_aml调用点)或深入 OSPM 侧驱动的必备基础。

  • 虚拟化
  • 硬件仿真

【免费下载链接】qemu

Official 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询