vmm: introduce ACPI CPU hotplug controller (fix deadlock)

Extract AcpiCpuHotplugController from CpuManager and move the BusDevice
implementation to the new type. This separates VMM-internal vCPU
management from the guest-visible ACPI CPU hotplug MMIO interface.

Besides clarifying responsibilities and reducing technical debt, this
fixes a rare deadlock involving pause handling and MMIO access.

New responsibilities:
- CpuManager manages VMM-internal vCPU lifecycle and coordination
- AcpiCpuHotplugController implements the guest-visible ACPI CPU hotplug
  MMIO interface

A vCPU thread may exit KVM_RUN to perform an MMIO access previously
handled by CpuManager. If the VMM thread begins processing a `pause`
event before that MMIO operation acquires access to CpuManager,
CpuManager::pause() will block waiting for the vCPU thread to ACK
the pause, while the vCPU thread is blocked waiting to complete the MMIO
operation through the same CpuManager - which it can never lock - the
VMM is deadlocked.

This can occur during early boot or CPU hotplug when pause events race
with MMIO accesses. The issue is rare and timing-dependent, but real.
For reproducing: run `ch-remote pause|resume` in a loop while booting
a Linux VM (via direct kernel boot).

With the new design, these MMIO operations no longer depend on
CpuManager, which removes the deadlock path entirely.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit is contained in:
Philipp Schuster
2026-04-09 22:17:32 +02:00
committed by Rob Bradford
parent 6d0d4bc5e2
commit 5ff4696cea
2 changed files with 138 additions and 108 deletions

View File

@@ -124,7 +124,7 @@ use vm_virtio::{AccessPlatform, VirtioDeviceType};
use vmm_sys_util::eventfd::EventFd;
use crate::console_devices::{ConsoleDeviceError, ConsoleInfo, ConsoleTransport};
use crate::cpu::{CPU_MANAGER_ACPI_SIZE, CpuManager};
use crate::cpu::{AcpiCpuHotplugController, CPU_MANAGER_ACPI_SIZE, CpuManager};
use crate::device_tree::{DeviceNode, DeviceTree};
use crate::interrupt::{LegacyUserspaceInterruptManager, MsiInterruptManager};
use crate::memory_manager::{Error as MemoryManagerError, MEMORY_MANAGER_ACPI_SIZE, MemoryManager};
@@ -1026,6 +1026,10 @@ pub struct DeviceManager {
// CPU Manager
cpu_manager: Arc<Mutex<CpuManager>>,
/// Owned version needed to keep the bus device alive (the bus only holds
/// a weak reference).
_acpi_cpu_hotplug_controller: Arc<Mutex<AcpiCpuHotplugController>>,
// The virtio devices on the system
virtio_devices: Vec<MetaVirtioDevice>,
@@ -1324,6 +1328,10 @@ impl DeviceManager {
)?);
}
let acpi_cpu_hotplug_controller =
AcpiCpuHotplugController::new(&cpu_manager.lock().unwrap());
let acpi_cpu_hotplug_controller = Arc::new(Mutex::new(acpi_cpu_hotplug_controller));
if dynamic {
let acpi_address = address_manager
.allocator
@@ -1335,7 +1343,7 @@ impl DeviceManager {
address_manager
.mmio_bus
.insert(
cpu_manager.clone(),
acpi_cpu_hotplug_controller.clone(),
acpi_address.0,
CPU_MANAGER_ACPI_SIZE as u64,
)
@@ -1429,6 +1437,7 @@ impl DeviceManager {
fw_cfg: None,
#[cfg(feature = "ivshmem")]
ivshmem_device: None,
_acpi_cpu_hotplug_controller: acpi_cpu_hotplug_controller,
};
let device_manager = Arc::new(Mutex::new(device_manager));