QEMU 与 ACPI BIOS CPU 热插拔接口

QEMU 通过 ACPI 支持 CPU 热插拔。本文描述 QEMU 与 ACPI BIOS 的接口。

ACPI BIOS 的 GPE.2 处理程序专门向操作系统通知 CPU 热添加和热移除事件。

现代 ACPI CPU 热插拔接口寄存器

寄存器块基址:

  • ICH9-LPC I/O 端口 0x0cd8

  • PIIX-PM I/O 端口 0xaf00

寄存器块大小:

  • ACPI_CPU_HOTPLUG_REG_LEN = 12

以下所有寄存器访问均采用小端字节序。

保留寄存器的行为:

  • 忽略写入。

  • 读取返回全 0。

CPU selector 最后写入的值必须指向可能存在的 CPU,否则:

  • 读取任意寄存器都返回 0。

  • 忽略其他寄存器写入,直到选择器重新写入有效值。

QEMU 启动时将 CPU selector 初始化为有效值,复位时保留当前值。

读取行为

偏移 [0x0-0x3]

Command data 2,DWORD 访问。

根据 Command field 最后写入值:

0:

读取为 0x0。

3:

架构专用 CPU ID 的高 32 位。

其他值:

保留。

偏移 [0x4]

CPU 设备状态字段,1 字节访问。

位定义:

0:

设备已启用,客户机可使用。

1:

设备插入事件,识别尚未向 OSPM 发送设备检查事件的设备。 仅在位 0 已设置时有效。

2:

设备移除事件,识别尚未向 OSPM 发送弹出请求的设备。固件必须忽略此位。

3:

保留,OSPM 应忽略。

4:

为 1 时,OSPM 请求固件执行设备弹出。

5-7:

保留,OSPM 应忽略。

偏移 [0x5-0x7]

保留。

偏移 [0x8]

Command data,DWORD 访问。

根据 Command field 最后写入值:

0:

包含有待处理事件的 CPU 的选择器值。

3:

架构专用 CPU ID 的低 32 位,x86 为 APIC ID。

其他值:

包含 0。

写入行为

偏移 [0x0-0x3]

CPU selector,DWORD 访问。

选择活动 CPU,此后其他寄存器访问均读写所选 CPU 数据。 有效范围为 [0 .. max_cpus)。

偏移 [0x4]

CPU 设备控制字段,1 字节访问。

位定义:

0:

保留,OSPM 写入前必须清零。

1:

写 1 清除插入事件,OSPM 为所选 CPU 发出设备检查事件后设置。

2:

写 1 清除移除事件,OSPM 为所选 CPU 发出弹出请求后设置。

3:

写 1 发起弹出。OSPM 触发 CPU 移除并调用 _EJ0 时设置, 或在位 4 已设置时由固件设置。若位 4 已设置,弹出时将其清零。

4:

写 1 表示 OSPM 将弹出交给固件。固件应按上述位 3 发起弹出请求。 已要求固件执行时,OSPM 不应再操作位 3。

5-7:

保留,OSPM 写入前必须清零。

偏移 [0x5]

Command field,1 字节访问。

值定义:

0:

选择有插入/移除事件的 CPU,后续读取 Command data 返回所选 CPU 的选择器值。 若无待处理事件,当前选择器不变,也不修改相应插入/移除标志。

1:

后续写入 Command data 设置 QEMU 的 OST 事件寄存器。

2:

后续写入 Command data 设置 QEMU 的 OST 状态寄存器。

3:

后续读取 Command data 和 Command data 2 返回当前 CPU 的架构专用 ID。

其他值:

保留。

偏移 [0x6-0x7]

保留。

偏移 [0x8]

Command data,DWORD 访问。

根据 Command field 最后写入值:

1:

将值保存到 OST 事件寄存器。

2:

将值保存到 OST 状态寄存器,触发 QEMU 向外部应用发送 ACPI_DEVICE_OST QMP 事件, 包含当前 OST 事件和状态寄存器值。

其他值:

保留。

典型用例

x86:检测并启用现代 CPU 热插拔接口

QEMU 启动时已启用现代接口,可按以下步骤检测:

  1. 向 CPU selector 写 0x0,确保选择器有效。

  2. 向 Command field 写 0x0。

  3. 读取 Command data 2;若值为 0x0,现代接口已启用,否则没有可用 CPU 热插拔接口。

获取有待处理事件的 CPU

  1. 向 CPU selector 写 0x0。

  2. 向 Command field 写 0x0。

  3. 读取 CPU 设备状态字段。

  4. 若位 1、2 均为 0,则没有待处理事件,所选 CPU 保持不变。

  5. 否则读取 Command data,其值为有待处理事件且已被选中的 CPU 选择器。

枚举存在与不存在的 CPU

  1. 将现有 CPU 计数设为 0。

  2. 将迭代器设为 0。

  3. 向 CPU selector 写 0x0,确保有效,避免其他寄存器访问被忽略。

  4. 向 Command field 写 0x0,使 Command data 返回所选 CPU 选择器值。

  5. 读取 CPU 设备状态字段。

  6. 位 0 为 1 时递增现有 CPU 计数。

  7. 递增迭代器。

  8. 将迭代器写入 CPU selector。

  9. 读取 Command data。

  10. 若值非 0,跳转到第 5 步。

  11. 否则向 CPU selector 写 0x0 恢复有效状态并退出。 此时迭代器等于 max_cpus。