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 <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2023-02-08 19:06:15 +00:00 committed by Bo Chen
parent 3b0d2e796b
commit 39a81c596f
5 changed files with 68 additions and 1 deletions

View File

@ -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()))?;

View File

@ -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<MsrEntry>;
#[cfg(target_arch = "x86_64")]
///
/// Get the frequency of the TSC if available
///
fn tsc_khz(&self) -> Result<Option<u32>> {
Ok(None)
}
}

View File

@ -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<Option<u32>> {
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 {

View File

@ -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)]

View File

@ -341,6 +341,7 @@ fn create_vmm_ioctl_seccomp_rule_kvm() -> Result<Vec<SeccompRule>, 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<Vec<SeccompRule>, 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)?],