From 39a81c596f3c49df3590c0e89a22e023960d6c31 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 8 Feb 2023 19:06:15 +0000 Subject: [PATCH] arch, hypervisor: Populate CPUID leaf 0x4000_0010 (TSC frequency) This hypervisor leaf includes details of the TSC frequency if that is available from KVM. This can be used to efficiently calculate time passed when there is an invariant TSC. TEST=Run `cpuid` in the guest and observe the frequency populated. Fixes: #5178 Signed-off-by: Rob Bradford --- arch/src/x86_64/mod.rs | 34 +++++++++++++++++++++++++++++++- hypervisor/src/cpu.rs | 13 ++++++++++++ hypervisor/src/kvm/mod.rs | 17 ++++++++++++++++ hypervisor/src/kvm/x86_64/mod.rs | 3 +++ vmm/src/seccomp_filters.rs | 2 ++ 5 files changed, 68 insertions(+), 1 deletion(-) 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)?],