diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index dd84081c7..f56489177 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -16,7 +16,7 @@ use crate::GuestMemoryMmap; use crate::InitramfsConfig; use crate::RegionType; use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX}; -use hypervisor::HypervisorError; +use hypervisor::{HypervisorCpuError, HypervisorError}; use linux_loader::loader::bootparam::boot_params; use linux_loader::loader::elf::start_info::{ hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info, @@ -36,6 +36,7 @@ pub mod tdx; const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit. const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit. const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit. +const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX // KVM feature bits const KVM_FEATURE_ASYNC_PF_INT_BIT: u8 = 14; @@ -191,6 +192,9 @@ pub enum Error { // Error writing EBDA address EbdaSetup(vm_memory::GuestMemoryError), + // Error getting CPU TSC frequency + GetTscFrequency(HypervisorCpuError), + /// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API #[cfg(feature = "tdx")] TdxCapabilities(HypervisorError), @@ -749,6 +753,34 @@ pub fn configure_vcpu( CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id)); CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, u32::from(id)); + // The TSC frequency CPUID leaf should not be included when running with HyperV emulation + if !kvm_hyperv { + if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? { + // Need to check that the TSC doesn't vary with dynamic frequency + // SAFETY: cpuid called with valid leaves + if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx + & (1u32 << INVARIANT_TSC_EDX_BIT) + > 0 + { + CpuidPatch::set_cpuid_reg( + &mut cpuid, + 0x4000_0000, + None, + CpuidReg::EAX, + 0x4000_0010, + ); + cpuid.retain(|c| c.function != 0x4000_0010); + cpuid.push(CpuIdEntry { + function: 0x4000_0010, + eax: tsc_khz, + ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's + * APIC_BUS_CYCLE_NS */ + ..Default::default() + }); + }; + } + } + vcpu.set_cpuid2(&cpuid) .map_err(|e| Error::SetSupportedCpusFailed(e.into()))?; diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 4768d0b37..c2fc77057 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -237,6 +237,12 @@ pub enum HypervisorCpuError { /// #[error("Failed to initialize PMU")] InitializePmu, + #[cfg(target_arch = "x86_64")] + /// + /// Error getting TSC frequency + /// + #[error("Failed to get TSC frequency: {0}")] + GetTscKhz(#[source] anyhow::Error), } #[derive(Debug)] @@ -432,4 +438,11 @@ pub trait Vcpu: Send + Sync { /// Return the list of initial MSR entries for a VCPU /// fn boot_msr_entries(&self) -> Vec; + #[cfg(target_arch = "x86_64")] + /// + /// Get the frequency of the TSC if available + /// + fn tsc_khz(&self) -> Result> { + Ok(None) + } } diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 3433a44e2..248605dcb 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -2152,6 +2152,23 @@ impl cpu::Vcpu for KvmVcpu { .set_device_attr(&cpu_attr) .map_err(|_| cpu::HypervisorCpuError::InitializePmu) } + + #[cfg(target_arch = "x86_64")] + /// + /// Get the frequency of the TSC if available + /// + fn tsc_khz(&self) -> cpu::Result> { + match self.fd.get_tsc_khz() { + Err(e) => { + if e.errno() == libc::EIO { + Ok(None) + } else { + Err(cpu::HypervisorCpuError::GetTscKhz(e.into())) + } + } + Ok(v) => Ok(Some(v)), + } + } } impl KvmVcpu { diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index 8106340d3..2a0ed6a6d 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -50,6 +50,9 @@ pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> { if !kvm.check_extension(Cap::ImmediateExit) { return Err(KvmError::CapabilityMissing(Cap::ImmediateExit)); } + if !kvm.check_extension(Cap::GetTscKhz) { + return Err(KvmError::CapabilityMissing(Cap::GetTscKhz)); + } Ok(()) } #[derive(Clone, Serialize, Deserialize)] diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index e59a227a4..a7a2796fb 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -341,6 +341,7 @@ fn create_vmm_ioctl_seccomp_rule_kvm() -> Result, BackendError> const KVM_GET_MSR_INDEX_LIST: u64 = 0xc004_ae02; const KVM_GET_MSRS: u64 = 0xc008_ae88; const KVM_GET_SREGS: u64 = 0x8138_ae83; + const KVM_GET_TSC_KHZ: u64 = 0xaea3; const KVM_GET_XCRS: u64 = 0x8188_aea6; const KVM_GET_XSAVE: u64 = 0x9000_aea4; const KVM_KVMCLOCK_CTRL: u64 = 0xaead; @@ -367,6 +368,7 @@ fn create_vmm_ioctl_seccomp_rule_kvm() -> Result, BackendError> and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_MSR_INDEX_LIST)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_MSRS)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_SREGS)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_TSC_KHZ)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_XCRS,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_XSAVE,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_KVMCLOCK_CTRL)?],