From c698075157c231a75c5fee0ba9e1c61290157364 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Mar 2026 16:02:40 +0100 Subject: [PATCH] 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 --- vmm/src/device_manager.rs | 7 +++++++ vmm/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ vmm/src/vm.rs | 6 ++++++ 3 files changed, 48 insertions(+) diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 39281e53e..193b411d4 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -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>, 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, reset_evt: EventFd, exit_evt: EventFd, + _guest_exit_evt: EventFd, ) -> DeviceManagerResult>>> { let vcpus_kill_signalled = self .cpu_manager diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 66a2a104d..eec56c5c1 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -250,6 +250,7 @@ pub enum EpollDispatch { Api = 2, ActivateVirtioDevices = 3, Debug = 4, + GuestExit = 5, Unknown, } @@ -262,6 +263,7 @@ impl From 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 { 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, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 04bd2d595..6b3f6c42e 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -536,6 +536,7 @@ impl Vm { vm: Arc, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, seccomp_action: &SeccompAction, hypervisor: Arc, @@ -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>, 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>, exit_evt: EventFd, reset_evt: EventFd, + guest_exit_evt: EventFd, #[cfg(feature = "guest_debug")] vm_debug_evt: EventFd, seccomp_action: &SeccompAction, hypervisor: Arc, @@ -1319,6 +1324,7 @@ impl Vm { vm, exit_evt, reset_evt, + guest_exit_evt, #[cfg(feature = "guest_debug")] vm_debug_evt, seccomp_action,