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

@@ -1120,9 +1120,36 @@ impl CpuManager {
)?);
}
#[cfg(target_arch = "x86_64")]
if snapshot.is_some() {
Self::synchronize_tsc_offsets(&vcpus);
}
Ok(vcpus)
}
// Synchronize TSC offsets (mitigates each vCPU being restored at a slightly different time)
// and allows the TSC masterclock to engage.
#[cfg(target_arch = "x86_64")]
fn synchronize_tsc_offsets(vcpus: &[Arc<Mutex<Vcpu>>]) {
let Some(reference) = vcpus.first() else {
return;
};
let offset = match reference.lock().unwrap().vcpu.tsc_offset() {
Ok(Some(offset)) => offset,
Ok(None) => return,
Err(e) => {
warn!("Could not read vCPU TSC offset for resync: {e}");
return;
}
};
for vcpu in vcpus {
if let Err(e) = vcpu.lock().unwrap().vcpu.set_tsc_offset(offset) {
warn!("Could not synchronize vCPU TSC offset: {e}");
}
}
}
#[cfg(target_arch = "aarch64")]
pub fn init_pmu(&self, irq: u32) -> Result<bool> {
for cpu in self.vcpus.iter() {