vmm: cpu: Re-sync vCPU TSC offsets after restore

Restoring a snapshot (snapshot restore or live-migration receive) sets
each vCPU's TSC by writing MSR_IA32_TSC as the vCPU is created.

However because CpuManager creates and restores vCPUs one at a time, the
host TSC advances between the per-vCPU writes and KVM derives a slightly
different TSC offset for each vCPU.

KVM only engages its masterclock when every offset matches. This has a
side effect of breaking the HyperV TSC reference clock page resulting in
significantly reduced performance on Windows.

After restore synchronise all vCPU's TSC offset to the boot vCPU's via
the KVM_VCPU_TSC_CTRL device attribute group (Linux 5.16+) this allows
the KVM TSC masterclock to engage and mitigates performance issues with
the KVM HyperV emulation.

See: #8383

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude <claude-opus-4-8>
This commit is contained in:
Rob Bradford
2026-06-18 19:57:29 +01:00
committed by Bo Chen
parent 08c82a7352
commit 993ac5c90b
3 changed files with 139 additions and 1 deletions

View File

@@ -311,6 +311,18 @@ pub enum HypervisorCpuError {
///
#[error("Failed to set TSC frequency")]
SetTscKhz(#[source] anyhow::Error),
#[cfg(target_arch = "x86_64")]
///
/// Error getting TSC offset
///
#[error("Failed to get TSC offset")]
GetTscOffset(#[source] anyhow::Error),
#[cfg(target_arch = "x86_64")]
///
/// Error setting TSC offset
///
#[error("Failed to set TSC offset")]
SetTscOffset(#[source] anyhow::Error),
///
/// Error reading value at given GPA
///
@@ -614,6 +626,21 @@ pub trait Vcpu: Send + Sync {
}
#[cfg(target_arch = "x86_64")]
///
/// Read the vCPU's TSC offset, or `None` when the hypervisor does not
/// expose it.
///
fn tsc_offset(&self) -> Result<Option<u64>> {
Ok(None)
}
#[cfg(target_arch = "x86_64")]
///
/// Set the vCPU's TSC offset if available.
///
fn set_tsc_offset(&self, _offset: u64) -> Result<()> {
Ok(())
}
#[cfg(target_arch = "x86_64")]
///
/// X86 specific call to retrieve cpuid leaf
///
fn get_cpuid_values(