x86_64: add TSC leaf synthesis.

There have been two evolutions of providing the TSC cpuid leaf
(aka 0x15) to the guest.

a) For CrosVM on Windows, we have been providing the leaf
   unconditionally.  Furthermore, we've not been using the
   exact host leaf; instead, we calibrate the TSC frequency
   and provide that value in the leaf. This was done because
   the actual cpuid leaf values are not as accurate as
   we needed them to be to drive a guest clocksource.

b) In CrosVM mainline, 4080aaf9b3
   introduced the flag enable_pnp / enable_pnp_data, and
   provides the exact host 0x15 leaf to the guest if the
   flag is enabled.

This CL adds a new hypervisor capability (CalibratedTscLeafRequired) to control
whether or not the calibrated TSC leaf should be used, in addition to a new CLI
option to force it on hypervisors where it isn't enabled by default. The new
option is `--force_calibrated_tsc_leaf`.

BUG=b:213152505
TEST=builds upstream, battletested downstream on WHPX.

Change-Id: I611422808a9e10578c0ddcbd211ae902f937685f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3698993
Commit-Queue: Noah Gold <nkgold@google.com>
Reviewed-by: Junichi Uekawa <uekawa@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Noah Gold 2022-06-10 13:46:21 -07:00 committed by Chromeos LUCI
parent efde16f4d2
commit 4ea25d1e33
12 changed files with 79 additions and 3 deletions

View file

@ -561,6 +561,7 @@ impl arch::LinuxArch for AArch64 {
_host_cpu_topology: bool,
_enable_pnp_data: bool,
_itmt: bool,
_force_calibrated_tsc_leaf: bool,
) -> std::result::Result<(), Self::Error> {
// AArch64 doesn't configure vcpus on the vcpu thread, so nothing to do here.
Ok(())

View file

@ -238,6 +238,7 @@ pub trait LinuxArch {
/// * `host_cpu_topology` - whether enabling host cpu topology.
/// * `enable_pnp_data` - whether enabling PnP statistics data.
/// * `itmt` - whether enabling ITMT scheduler
/// * `force_calibrated_tsc_leaf` - whether to force using a calibrated TSC leaf (0x15).
fn configure_vcpu<V: Vm>(
vm: &V,
hypervisor: &dyn HypervisorArch,
@ -251,6 +252,7 @@ pub trait LinuxArch {
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<(), Self::Error>;
/// Configures and add a pci device into vm

View file

@ -12,6 +12,18 @@ pub enum HypervisorCap {
UserMemory,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Xcrs,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
/// CPUID leaf 0x15 is available on some Intel chips and contains the TSC
/// frequency, which can be used to calibrate the guest's TSC clocksource;
/// however, it is not typically accurate enough (being off by 1-2% is a
/// big problem for a clocksource), and inside the guest, calibration by
/// other means is not always reliable.
///
/// Hypervisors which do not provide the TSC frequency (e.g. via the kvm
/// pvclock) or have another suitable calibration source can declare this
/// capability, which causes CrosVM to substitute a calibrated value in leaf
/// 0x15 that will be accurate enough for use in a clocksource.
CalibratedTscLeafRequired,
}
/// A capability the `Vm` can possibly expose.

View file

@ -1152,6 +1152,8 @@ impl TryFrom<HypervisorCap> for KvmCap {
HypervisorCap::UserMemory => Ok(KvmCap::UserMemory),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
HypervisorCap::Xcrs => Ok(KvmCap::Xcrs),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
HypervisorCap::CalibratedTscLeafRequired => Err(Error::new(libc::EINVAL)),
}
}
}

View file

@ -375,6 +375,8 @@ impl Vm for WhpxVm {
VmCap::Protected => false,
// whpx initializes cpuid early during VM creation.
VmCap::EarlyInitCpuid => true,
// under whpx, guests rely on this leaf to calibrate their clocksource.
VmCap::CalibratedTscLeafRequired => true,
}
}

View file

@ -559,6 +559,11 @@ pub struct RunCommand {
/// align - whether to adjust addr and size to page
/// boundaries implicitly
pub file_backed_mappings: Vec<FileBackedMappingParameters>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[argh(switch)]
/// force use of a calibrated TSC cpuid leaf (0x15) even if the hypervisor
/// doesn't require one.
pub force_calibrated_tsc_leaf: bool,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
#[argh(option, arg_name = "PORT")]
/// (EXPERIMENTAL) gdb on the given port
@ -1594,8 +1599,21 @@ impl TryFrom<RunCommand> for super::config::Config {
cfg.itmt = cmd.itmt;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if cmd.enable_pnp_data && cmd.force_calibrated_tsc_leaf {
return Err(
"Only one of [enable_pnp_data,force_calibrated_tsc_leaf] can be specified"
.to_string(),
);
}
cfg.enable_pnp_data = cmd.enable_pnp_data;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
cfg.force_calibrated_tsc_leaf = cmd.force_calibrated_tsc_leaf;
}
cfg.privileged_vm = cmd.privileged_vm;
cfg.stub_pci_devices = cmd.stub_pci_devices;

View file

@ -1704,6 +1704,7 @@ pub struct Config {
pub enable_pnp_data: bool,
pub executable_path: Option<Executable>,
pub file_backed_mappings: Vec<FileBackedMappingParameters>,
pub force_calibrated_tsc_leaf: bool,
pub force_s2idle: bool,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub gdb: Option<u32>,
@ -1835,6 +1836,7 @@ impl Default for Config {
enable_pnp_data: false,
executable_path: None,
file_backed_mappings: Vec::new(),
force_calibrated_tsc_leaf: false,
force_s2idle: false,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: None,

View file

@ -1986,6 +1986,7 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
cfg.host_cpu_topology,
cfg.enable_pnp_data,
cfg.itmt,
cfg.force_calibrated_tsc_leaf,
cfg.privileged_vm,
match vcpu_cgroup_tasks_file {
None => None,

View file

@ -143,6 +143,7 @@ pub fn runnable_vcpu<V>(
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<(V, VcpuRunHandle)>
where
V: VcpuArch,
@ -180,6 +181,7 @@ where
host_cpu_topology,
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
)
.context("failed to configure vcpu")?;
@ -575,6 +577,7 @@ pub fn run_vcpu<V>(
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
privileged_vm: bool,
vcpu_cgroup_tasks_file: Option<File>,
userspace_msr: BTreeMap<u32, MsrConfig>,
@ -616,6 +619,7 @@ where
host_cpu_topology,
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
);
// Add MSR handlers after CPU affinity setting.

View file

@ -7,7 +7,7 @@ use std::cmp;
use std::result;
use devices::{Apic, IrqChipCap, IrqChipX86_64};
use hypervisor::{CpuIdEntry, HypervisorX86_64, VcpuX86_64};
use hypervisor::{CpuIdEntry, HypervisorCap, HypervisorX86_64, VcpuX86_64};
use crate::CpuManufacturer;
use remain::sorted;
@ -63,6 +63,11 @@ pub struct CpuIdContext {
apic_frequency: u32,
/// The TSC frequency in Hz, if it could be determined.
tsc_frequency: Option<u64>,
/// Whether to force the use of a calibrated TSC cpuid leaf (0x15) even if
/// the hypervisor doesn't require it.
force_calibrated_tsc_leaf: bool,
/// Whether the hypervisor requires a calibrated TSC cpuid leaf (0x15).
calibrated_tsc_leaf_required: bool,
/// Whether or not VCPU IDs and APIC IDs should match host cpu IDs.
host_cpu_topology: bool,
enable_pnp_data: bool,
@ -83,6 +88,8 @@ impl CpuIdContext {
irq_chip: Option<&dyn IrqChipX86_64>,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
calibrated_tsc_leaf_required: bool,
cpuid_count: unsafe fn(u32, u32) -> CpuidResult,
cpuid: unsafe fn(u32) -> CpuidResult,
) -> CpuIdContext {
@ -96,6 +103,8 @@ impl CpuIdContext {
}),
apic_frequency: irq_chip.map_or(Apic::frequency(), |chip| chip.lapic_frequency()),
tsc_frequency: devices::tsc::tsc_frequency().ok(),
force_calibrated_tsc_leaf,
calibrated_tsc_leaf_required,
host_cpu_topology,
enable_pnp_data,
itmt,
@ -210,7 +219,18 @@ pub fn adjust_cpuid(entry: &mut CpuIdEntry, ctx: &CpuIdContext) {
}
}
0x15 => {
if ctx.enable_pnp_data {
if ctx.calibrated_tsc_leaf_required
|| ctx.force_calibrated_tsc_leaf {
let cpuid_15 = ctx
.tsc_frequency
.map(|tsc_freq| devices::tsc::fake_tsc_frequency_cpuid(
tsc_freq, ctx.apic_frequency));
if let Some(new_entry) = cpuid_15 {
entry.cpuid = new_entry.cpuid;
}
} else if ctx.enable_pnp_data {
// Expose TSC frequency to guest
// Safe because we pass 0x15 for this call and the host
// supports the `cpuid` instruction
@ -321,6 +341,7 @@ pub fn setup_cpuid(
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<()> {
let mut cpuid = hypervisor
.get_supported_cpuid()
@ -336,6 +357,8 @@ pub fn setup_cpuid(
Some(irq_chip),
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
hypervisor.check_capability(HypervisorCap::CalibratedTscLeafRequired),
__cpuid_count,
__cpuid,
),
@ -421,6 +444,8 @@ mod tests {
Some(&irq_chip),
false,
false,
false,
false,
__cpuid_count,
__cpuid,
),
@ -466,6 +491,8 @@ mod tests {
host_cpu_topology: true,
enable_pnp_data: false,
itmt: false,
force_calibrated_tsc_leaf: false,
calibrated_tsc_leaf_required: false,
cpuid_count: fake_cpuid_count,
cpuid: fake_cpuid,
};

View file

@ -770,6 +770,7 @@ impl arch::LinuxArch for X8664arch {
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<()> {
if !vm.check_capability(VmCap::EarlyInitCpuid) {
cpuid::setup_cpuid(
@ -782,6 +783,7 @@ impl arch::LinuxArch for X8664arch {
host_cpu_topology,
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
)
.map_err(Error::SetupCpuid)?;
}

View file

@ -240,7 +240,10 @@ where
.expect("failed to add vcpu to irqchip");
if !vm.check_capability(VmCap::EarlyInitCpuid) {
setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, false, false, false, false).unwrap();
setup_cpuid(
&hyp, &irq_chip, &vcpu, 0, 1, false, false, false, false, false,
)
.unwrap();
}
setup_msrs(&vm, &vcpu, read_pci_mmio_before_32bit().start).unwrap();