From d68b93ea93c2cec9b128e4dedde571e79cc8630c Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Thu, 11 Jun 2026 14:51:01 -0700 Subject: [PATCH] hypervisor: add a generic guest-clock save/restore abstraction Preserving the guest clock across pause/resume and snapshot/restore is currently open-coded in the VMM against the x86-only get_clock/set_clock. aarch64 needs the same correction but via a different mechanism (i.e. the architected counter, CNTVCT). Having a common backend-agnostic interface that VMM can drive uniformly allows us to keep the architecture details behind the Hypervisor abstraction. This commit only introduces the abstraction while the future commits will actually move the implementation to use it. Use this opportunity to fix the full path to get SystemTime as well. Suggested-by: Sebastien Boeuf Signed-off-by: Atish Patra --- hypervisor/src/kvm/mod.rs | 14 +++++++++++++- hypervisor/src/lib.rs | 19 ++++++++++++++++++- hypervisor/src/mshv/mod.rs | 14 +++++++++++++- hypervisor/src/vm.rs | 13 ++++++++++++- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index e61987ea7..e4e2c4264 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -78,7 +78,7 @@ use x86_64::check_required_kvm_extensions; pub use x86_64::{CpuId, ExtendedControlRegisters, MsrEntries, VcpuKvmState}; #[cfg(target_arch = "x86_64")] -use crate::ClockData; +use crate::{ClockData, ClockState}; #[cfg(target_arch = "x86_64")] use crate::arch::x86::{ CpuIdEntry, FpuState, LapicState, MTRR_MSR_INDICES, MsrEntry, NUM_IOAPIC_PINS, @@ -1264,6 +1264,18 @@ impl vm::Vm for KvmVm { .map_err(|e| vm::HypervisorVmError::SetClock(e.into())) } + /// Capture kvmclock (filling realtime) for snapshot/migration. + #[cfg(target_arch = "x86_64")] + fn snapshot_clock(&self) -> vm::Result> { + Ok(Some(self.get_clock()?.with_realtime_filled())) + } + + /// Restore kvmclock before the vCPUs resume. + #[cfg(target_arch = "x86_64")] + fn restore_clock(&self, state: &ClockState) -> vm::Result<()> { + self.set_clock(state) + } + /// Create a device that is used for passthrough fn create_passthrough_device(&self) -> vm::Result { let mut vfio_dev = kvm_create_device { diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 299a32390..eb4ca6bac 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -49,6 +49,8 @@ mod cpu; mod device; use std::sync::Arc; +#[cfg(target_arch = "x86_64")] +use std::time::SystemTime; use anyhow::anyhow; use concat_idents::concat_idents; @@ -196,7 +198,7 @@ impl ClockData { } } - pub fn set_realtime(&mut self, realtime: std::time::SystemTime) { + pub fn set_realtime(&mut self, realtime: SystemTime) { match self { #[cfg(feature = "kvm")] ClockData::Kvm(s) => { @@ -211,8 +213,23 @@ impl ClockData { } } } + + /// Returns the clock with `CLOCK_REALTIME` filled from the host wall clock + /// when absent, so a later restore can advance the guest to current wall + /// time. No-op for backends without a realtime field (e.g. MSHV). + pub fn with_realtime_filled(mut self) -> Self { + if !self.has_realtime() { + self.set_realtime(SystemTime::now()); + } + self + } } +/// Guest clock state preserved across pause/resume and snapshot/restore +/// (`ClockData` on x86). +#[cfg(target_arch = "x86_64")] +pub type ClockState = ClockData; + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct HypervisorVmConfig { #[cfg(feature = "tdx")] diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 858317705..74f0029d0 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -78,7 +78,7 @@ pub use { }; #[cfg(target_arch = "x86_64")] -use crate::ClockData; +use crate::{ClockData, ClockState}; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::gic::{Vgic, VgicConfig}; #[cfg(target_arch = "aarch64")] @@ -2200,6 +2200,18 @@ impl vm::Vm for MshvVm { .map_err(|e| vm::HypervisorVmError::SetClock(e.into())) } + /// Capture the partition reference time for snapshot/migration. + #[cfg(target_arch = "x86_64")] + fn snapshot_clock(&self) -> vm::Result> { + Ok(Some(self.get_clock()?.with_realtime_filled())) + } + + /// Restore the partition reference time before the vCPUs resume. + #[cfg(target_arch = "x86_64")] + fn restore_clock(&self, state: &ClockState) -> vm::Result<()> { + self.set_clock(state) + } + /// Downcast to the underlying MshvVm type fn as_any(&self) -> &dyn Any { self diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index f671204c0..b6fbf5ae5 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -23,7 +23,7 @@ use thiserror::Error; use vmm_sys_util::eventfd::EventFd; #[cfg(target_arch = "x86_64")] -use crate::ClockData; +use crate::{ClockData, ClockState}; #[cfg(target_arch = "aarch64")] use crate::arch::aarch64::gic::{Vgic, VgicConfig}; #[cfg(target_arch = "riscv64")] @@ -384,6 +384,17 @@ pub trait Vm: Send + Sync + Any { /// Set guest clock. #[cfg(target_arch = "x86_64")] fn set_clock(&self, data: &ClockData) -> Result<()>; + /// Capture the guest clock for snapshot/migration while the VM is paused. + /// `Ok(None)` means this backend has no clock to preserve. + #[cfg(target_arch = "x86_64")] + fn snapshot_clock(&self) -> Result> { + Ok(None) + } + /// Re-establish the guest clock before the vCPUs resume. + #[cfg(target_arch = "x86_64")] + fn restore_clock(&self, _state: &ClockState) -> Result<()> { + Ok(()) + } /// Create a device that is used for passthrough fn create_passthrough_device(&self) -> Result; /// Start logging dirty pages