vmm: add guest exit event path

Introduce a dedicated guest_exit_evt and a matching epoll dispatch
path for guest-triggered shutdowns.

This series is needed because managment software such as libvirt may
still need the Cloud Hypervisor process to stay alive after the guest
has shut down.
Today a guest-triggered shutdown can make the VMM disappear immediately,
which means the managment software can lose track of the VM run-state.

This must only apply to guest-triggered shutdowns. Fatal error paths
and other internal exit paths must keep using the existing VMM exit
handling.

For now GuestExit still calls vmm_shutdown(), so this commit only adds
the separate plumbing and keeps the current behavior unchanged.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
This commit is contained in:
Leander Kohler
2026-03-09 16:02:40 +01:00
committed by Rob Bradford
parent dc0e003be0
commit c698075157
3 changed files with 48 additions and 0 deletions

View File

@@ -1080,6 +1080,7 @@ pub struct DeviceManager {
// Exit event
exit_evt: EventFd,
reset_evt: EventFd,
guest_exit_evt: EventFd,
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
id_to_dev_info: HashMap<(DeviceType, String), MmioDeviceInfo>,
@@ -1206,6 +1207,7 @@ impl DeviceManager {
cpu_manager: Arc<Mutex<CpuManager>>,
exit_evt: EventFd,
reset_evt: EventFd,
guest_exit_evt: EventFd,
seccomp_action: SeccompAction,
numa_nodes: NumaNodes,
activate_evt: &EventFd,
@@ -1402,6 +1404,7 @@ impl DeviceManager {
device_tree,
exit_evt,
reset_evt,
guest_exit_evt,
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
id_to_dev_info: HashMap::new(),
seccomp_action,
@@ -1519,6 +1522,9 @@ impl DeviceManager {
self.exit_evt
.try_clone()
.map_err(DeviceManagerError::EventFd)?,
self.guest_exit_evt
.try_clone()
.map_err(DeviceManagerError::EventFd)?,
)?;
}
@@ -1889,6 +1895,7 @@ impl DeviceManager {
interrupt_manager: &dyn InterruptManager<GroupConfig = LegacyIrqGroupConfig>,
reset_evt: EventFd,
exit_evt: EventFd,
_guest_exit_evt: EventFd,
) -> DeviceManagerResult<Option<Arc<Mutex<devices::AcpiGedDevice>>>> {
let vcpus_kill_signalled = self
.cpu_manager

View File

@@ -250,6 +250,7 @@ pub enum EpollDispatch {
Api = 2,
ActivateVirtioDevices = 3,
Debug = 4,
GuestExit = 5,
Unknown,
}
@@ -262,6 +263,7 @@ impl From<u64> for EpollDispatch {
2 => Api,
3 => ActivateVirtioDevices,
4 => Debug,
5 => GuestExit,
_ => Unknown,
}
}
@@ -608,6 +610,7 @@ pub struct Vmm {
epoll: EpollContext,
exit_evt: EventFd,
reset_evt: EventFd,
guest_exit_evt: EventFd,
api_evt: EventFd,
#[cfg(feature = "guest_debug")]
debug_evt: EventFd,
@@ -781,6 +784,7 @@ impl Vmm {
) -> Result<Self> {
let mut epoll = EpollContext::new().map_err(Error::Epoll)?;
let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
let guest_exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
let activate_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
epoll
@@ -791,6 +795,10 @@ impl Vmm {
.add_event(&reset_evt, EpollDispatch::Reset)
.map_err(Error::Epoll)?;
epoll
.add_event(&guest_exit_evt, EpollDispatch::GuestExit)
.map_err(Error::Epoll)?;
epoll
.add_event(&activate_evt, EpollDispatch::ActivateVirtioDevices)
.map_err(Error::Epoll)?;
@@ -808,6 +816,7 @@ impl Vmm {
epoll,
exit_evt,
reset_evt,
guest_exit_evt,
api_evt,
#[cfg(feature = "guest_debug")]
debug_evt,
@@ -1109,6 +1118,9 @@ impl Vmm {
let reset_evt = self.reset_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning reset EventFd: {e}"))
})?;
let guest_exit_evt = self.guest_exit_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning guest exit EventFd: {e}"))
})?;
#[cfg(feature = "guest_debug")]
let debug_evt = self.vm_debug_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning debug EventFd: {e}"))
@@ -1128,6 +1140,7 @@ impl Vmm {
hypervisor_vm,
exit_evt,
reset_evt,
guest_exit_evt,
#[cfg(feature = "guest_debug")]
debug_evt,
&self.seccomp_action,
@@ -1569,6 +1582,10 @@ impl Vmm {
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
let guest_exit_evt = self
.guest_exit_evt
.try_clone()
.map_err(VmError::EventFdClone)?;
#[cfg(feature = "guest_debug")]
let debug_evt = self
.vm_debug_evt
@@ -1583,6 +1600,7 @@ impl Vmm {
vm_config,
exit_evt,
reset_evt,
guest_exit_evt,
#[cfg(feature = "guest_debug")]
debug_evt,
&self.seccomp_action,
@@ -1667,6 +1685,13 @@ impl Vmm {
self.reset_evt.read().map_err(Error::EventFdRead)?;
self.vm_reboot().map_err(Error::VmReboot)?;
}
EpollDispatch::GuestExit => {
info!("VM guest exit event");
self.guest_exit_evt.read().map_err(Error::EventFdRead)?;
self.vmm_shutdown().map_err(Error::VmmShutdown)?;
break 'outer;
}
EpollDispatch::ActivateVirtioDevices => {
if let Some(ref vm) = self.vm {
let count = self.activate_evt.read().map_err(Error::EventFdRead)?;
@@ -1775,6 +1800,10 @@ impl RequestHandler for Vmm {
if self.vm.is_none() {
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
let guest_exit_evt = self
.guest_exit_evt
.try_clone()
.map_err(VmError::EventFdClone)?;
#[cfg(feature = "guest_debug")]
let vm_debug_evt = self
.vm_debug_evt
@@ -1790,6 +1819,7 @@ impl RequestHandler for Vmm {
Arc::clone(vm_config),
exit_evt,
reset_evt,
guest_exit_evt,
#[cfg(feature = "guest_debug")]
vm_debug_evt,
&self.seccomp_action,
@@ -1955,6 +1985,10 @@ impl RequestHandler for Vmm {
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
let guest_exit_evt = self
.guest_exit_evt
.try_clone()
.map_err(VmError::EventFdClone)?;
#[cfg(feature = "guest_debug")]
let debug_evt = self
.vm_debug_evt
@@ -1980,6 +2014,7 @@ impl RequestHandler for Vmm {
config,
exit_evt,
reset_evt,
guest_exit_evt,
#[cfg(feature = "guest_debug")]
debug_evt,
&self.seccomp_action,

View File

@@ -536,6 +536,7 @@ impl Vm {
vm: Arc<dyn hypervisor::Vm>,
exit_evt: EventFd,
reset_evt: EventFd,
guest_exit_evt: EventFd,
#[cfg(feature = "guest_debug")] vm_debug_evt: EventFd,
seccomp_action: &SeccompAction,
hypervisor: Arc<dyn hypervisor::Hypervisor>,
@@ -604,6 +605,7 @@ impl Vm {
cpu_manager.clone(),
exit_evt.try_clone().map_err(Error::EventFdClone)?,
reset_evt,
guest_exit_evt,
seccomp_action.clone(),
numa_nodes.clone(),
&activate_evt,
@@ -790,6 +792,7 @@ impl Vm {
cpu_manager: Arc<Mutex<cpu::CpuManager>>,
exit_evt: EventFd,
reset_evt: EventFd,
guest_exit_evt: EventFd,
seccomp_action: SeccompAction,
numa_nodes: NumaNodes,
activate_evt: &EventFd,
@@ -812,6 +815,7 @@ impl Vm {
cpu_manager,
exit_evt,
reset_evt,
guest_exit_evt,
seccomp_action,
numa_nodes,
activate_evt,
@@ -1247,6 +1251,7 @@ impl Vm {
vm_config: Arc<Mutex<VmConfig>>,
exit_evt: EventFd,
reset_evt: EventFd,
guest_exit_evt: EventFd,
#[cfg(feature = "guest_debug")] vm_debug_evt: EventFd,
seccomp_action: &SeccompAction,
hypervisor: Arc<dyn hypervisor::Hypervisor>,
@@ -1319,6 +1324,7 @@ impl Vm {
vm,
exit_evt,
reset_evt,
guest_exit_evt,
#[cfg(feature = "guest_debug")]
vm_debug_evt,
seccomp_action,