blob: b20a247b2c777db9133d74884be82e2cf790fb12 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::arch::x86_64::CpuidResult;
use std::arch::x86_64::__cpuid;
use std::arch::x86_64::__cpuid_count;
use std::cmp;
use std::result;
use devices::Apic;
use devices::IrqChipCap;
use devices::IrqChipX86_64;
use hypervisor::CpuConfigX86_64;
use hypervisor::CpuIdEntry;
use hypervisor::HypervisorCap;
use hypervisor::HypervisorX86_64;
use hypervisor::VcpuX86_64;
use remain::sorted;
use thiserror::Error;
use crate::CpuManufacturer;
#[sorted]
#[derive(Error, Debug, PartialEq)]
pub enum Error {
#[error("GetSupportedCpus ioctl failed: {0}")]
GetSupportedCpusFailed(base::Error),
#[error("SetSupportedCpus ioctl failed: {0}")]
SetSupportedCpusFailed(base::Error),
}
pub type Result<T> = result::Result<T, Error>;
// CPUID bits in ebx, ecx, and edx.
const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH.
const EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU.
const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU.
const ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit.
const ECX_X2APIC_SHIFT: u32 = 21; // APIC supports extended xAPIC (x2APIC) standard.
const ECX_TSC_DEADLINE_TIMER_SHIFT: u32 = 24; // TSC deadline mode of APIC timer.
const ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor.
const EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled.
const ECX_TOPO_TYPE_SHIFT: u32 = 8; // Topology Level type.
const ECX_TOPO_SMT_TYPE: u32 = 1; // SMT type.
const ECX_TOPO_CORE_TYPE: u32 = 2; // CORE type.
const ECX_HCFC_PERF_SHIFT: u32 = 0; // Presence of IA32_MPERF and IA32_APERF.
const EAX_CPU_CORES_SHIFT: u32 = 26; // Index of cpu cores in the same physical package.
const EDX_HYBRID_CPU_SHIFT: u32 = 15; // Hybrid. The processor is identified as a hybrid part.
const EAX_HWP_SHIFT: u32 = 7; // Intel Hardware P-states.
const EAX_HWP_NOTIFICATION_SHIFT: u32 = 8; // IA32_HWP_INTERRUPT MSR is supported
const EAX_HWP_EPP_SHIFT: u32 = 10; // HWP Energy Perf. Preference.
const EAX_ITMT_SHIFT: u32 = 14; // Intel Turbo Boost Max Technology 3.0 available.
const EAX_CORE_TEMP: u32 = 0; // Core Temperature
const EAX_PKG_TEMP: u32 = 6; // Package Temperature
/// All of the context required to emulate the CPUID instruction.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CpuIdContext {
/// Id of the Vcpu associated with this context.
vcpu_id: usize,
/// The total number of vcpus on this VM.
cpu_count: usize,
/// Whether or not the IrqChip's APICs support X2APIC.
x2apic: bool,
/// Whether or not the IrqChip's APICs support a TSC deadline timer.
tsc_deadline_timer: bool,
/// The frequency at which the IrqChip's APICs run.
apic_frequency: u32,
/// The TSC frequency in Hz, if it could be determined.
tsc_frequency: Option<u64>,
/// Whether the hypervisor requires a calibrated TSC cpuid leaf (0x15).
calibrated_tsc_leaf_required: bool,
/// CPU feature configurations.
cpu_config: CpuConfigX86_64,
/// __cpuid_count or a fake function for test.
cpuid_count: unsafe fn(u32, u32) -> CpuidResult,
/// __cpuid or a fake function for test.
cpuid: unsafe fn(u32) -> CpuidResult,
}
impl CpuIdContext {
pub fn new(
vcpu_id: usize,
cpu_count: usize,
irq_chip: Option<&dyn IrqChipX86_64>,
cpu_config: CpuConfigX86_64,
calibrated_tsc_leaf_required: bool,
cpuid_count: unsafe fn(u32, u32) -> CpuidResult,
cpuid: unsafe fn(u32) -> CpuidResult,
) -> CpuIdContext {
CpuIdContext {
vcpu_id,
cpu_count,
x2apic: irq_chip.map_or(false, |chip| chip.check_capability(IrqChipCap::X2Apic)),
tsc_deadline_timer: irq_chip.map_or(false, |chip| {
chip.check_capability(IrqChipCap::TscDeadlineTimer)
}),
apic_frequency: irq_chip.map_or(Apic::frequency(), |chip| chip.lapic_frequency()),
tsc_frequency: devices::tsc::tsc_frequency().ok(),
calibrated_tsc_leaf_required,
cpu_config,
cpuid_count,
cpuid,
}
}
}
/// Adjust a CPUID instruction result to return values that work with crosvm.
///
/// Given an input CpuIdEntry `entry`, which represents what the Hypervisor would normally return
/// for a given CPUID instruction result, adjust that result to reflect the capabilities of crosvm.
/// The `ctx` argument contains all of the Vm-specific and Vcpu-specific information required to
/// return the appropriate results.
pub fn adjust_cpuid(entry: &mut CpuIdEntry, ctx: &CpuIdContext) {
match entry.function {
0 => {
if ctx.tsc_frequency.is_some() {
// We add leaf 0x15 for the TSC frequency if it is available.
entry.cpuid.eax = cmp::max(0x15, entry.cpuid.eax);
}
}
1 => {
// X86 hypervisor feature
if entry.index == 0 {
entry.cpuid.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
}
if ctx.x2apic {
entry.cpuid.ecx |= 1 << ECX_X2APIC_SHIFT;
} else {
entry.cpuid.ecx &= !(1 << ECX_X2APIC_SHIFT);
}
if ctx.tsc_deadline_timer {
entry.cpuid.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT;
}
if ctx.cpu_config.host_cpu_topology {
entry.cpuid.ebx |= EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT;
// Expose HT flag to Guest.
let result = unsafe { (ctx.cpuid)(entry.function) };
entry.cpuid.edx |= result.edx & (1 << EDX_HTT_SHIFT);
return;
}
entry.cpuid.ebx = (ctx.vcpu_id << EBX_CPUID_SHIFT) as u32
| (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
if ctx.cpu_count > 1 {
// This field is only valid if CPUID.1.EDX.HTT[bit 28]= 1.
entry.cpuid.ebx |= (ctx.cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
// A value of 0 for HTT indicates there is only a single logical
// processor in the package and software should assume only a
// single APIC ID is reserved.
entry.cpuid.edx |= 1 << EDX_HTT_SHIFT;
}
}
2 | // Cache and TLB Descriptor information
0x80000002 | 0x80000003 | 0x80000004 | // Processor Brand String
0x80000005 | 0x80000006 // L1 and L2 cache information
=> entry.cpuid = unsafe { (ctx.cpuid)(entry.function) },
4 => {
entry.cpuid = unsafe { (ctx.cpuid_count)(entry.function, entry.index) };
if ctx.cpu_config.host_cpu_topology {
return;
}
entry.cpuid.eax &= !0xFC000000;
if ctx.cpu_count > 1 {
let cpu_cores = if ctx.cpu_config.no_smt {
ctx.cpu_count as u32
} else if ctx.cpu_count % 2 == 0 {
(ctx.cpu_count >> 1) as u32
} else {
1
};
entry.cpuid.eax |= (cpu_cores - 1) << EAX_CPU_CORES_SHIFT;
}
}
6 => {
// Safe because we pass 6 for this call and the host
// supports the `cpuid` instruction
let result = unsafe { (ctx.cpuid)(entry.function) };
if ctx.cpu_config.enable_hwp {
entry.cpuid.eax |= result.eax & (1 << EAX_HWP_SHIFT);
entry.cpuid.eax |= result.eax & (1 << EAX_HWP_NOTIFICATION_SHIFT);
entry.cpuid.eax |= result.eax & (1 << EAX_HWP_EPP_SHIFT);
entry.cpuid.ecx |= result.ecx & (1 << ECX_EPB_SHIFT);
if ctx.cpu_config.itmt {
entry.cpuid.eax |= result.eax & (1 << EAX_ITMT_SHIFT);
}
}
if ctx.cpu_config.enable_pnp_data {
// Expose core temperature, package temperature
// and APEF/MPERF to guest
entry.cpuid.eax |= result.eax & (1 << EAX_CORE_TEMP);
entry.cpuid.eax |= result.eax & (1 << EAX_PKG_TEMP);
entry.cpuid.ecx |= result.ecx & (1 << ECX_HCFC_PERF_SHIFT);
}
}
7 => {
if ctx.cpu_config.host_cpu_topology && entry.index == 0 {
// Safe because we pass 7 and 0 for this call and the host supports the
// `cpuid` instruction
let result = unsafe { (ctx.cpuid_count)(entry.function, entry.index) };
entry.cpuid.edx |= result.edx & (1 << EDX_HYBRID_CPU_SHIFT);
}
}
0x15 => {
if ctx.calibrated_tsc_leaf_required
|| ctx.cpu_config.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.cpu_config.enable_pnp_data {
// Expose TSC frequency to guest
// Safe because we pass 0x15 for this call and the host
// supports the `cpuid` instruction
entry.cpuid = unsafe { (ctx.cpuid)(entry.function) };
}
}
0x1A => {
// Hybrid information leaf.
if ctx.cpu_config.host_cpu_topology {
// Safe because we pass 0x1A for this call and the host supports the
// `cpuid` instruction
entry.cpuid = unsafe { (ctx.cpuid)(entry.function) };
}
}
0xB | 0x1F => {
if ctx.cpu_config.host_cpu_topology {
return;
}
// Extended topology enumeration / V2 Extended topology enumeration
// NOTE: these will need to be split if any of the fields that differ between
// the two versions are to be set.
// On AMD, these leaves are not used, so it is currently safe to leave in.
entry.cpuid.edx = ctx.vcpu_id as u32; // x2APIC ID
if entry.index == 0 {
if ctx.cpu_config.no_smt || (ctx.cpu_count == 1) {
// Make it so that all VCPUs appear as different,
// non-hyperthreaded cores on the same package.
entry.cpuid.eax = 0; // Shift to get id of next level
entry.cpuid.ebx = 1; // Number of logical cpus at this level
} else if ctx.cpu_count % 2 == 0 {
// Each core has 2 hyperthreads
entry.cpuid.eax = 1; // Shift to get id of next level
entry.cpuid.ebx = 2; // Number of logical cpus at this level
} else {
// One core contain all the cpu_count hyperthreads
let cpu_bits: u32 = 32 - ((ctx.cpu_count - 1) as u32).leading_zeros();
entry.cpuid.eax = cpu_bits; // Shift to get id of next level
entry.cpuid.ebx = ctx.cpu_count as u32; // Number of logical cpus at this level
}
entry.cpuid.ecx = (ECX_TOPO_SMT_TYPE << ECX_TOPO_TYPE_SHIFT) | entry.index;
} else if entry.index == 1 {
let cpu_bits: u32 = 32 - ((ctx.cpu_count - 1) as u32).leading_zeros();
entry.cpuid.eax = cpu_bits;
// Number of logical cpus at this level
entry.cpuid.ebx = (ctx.cpu_count as u32) & 0xffff;
entry.cpuid.ecx = (ECX_TOPO_CORE_TYPE << ECX_TOPO_TYPE_SHIFT) | entry.index;
} else {
entry.cpuid.eax = 0;
entry.cpuid.ebx = 0;
entry.cpuid.ecx = 0;
}
}
_ => (),
}
}
/// Adjust all the entries in `cpuid` based on crosvm's cpuid logic and `ctx`. Calls `adjust_cpuid`
/// on each entry in `cpuid`, and adds any entries that should exist and are missing from `cpuid`.
fn filter_cpuid(cpuid: &mut hypervisor::CpuId, ctx: &CpuIdContext) {
// Add an empty leaf 0x15 if we have a tsc_frequency and it's not in the current set of leaves.
// It will be filled with the appropriate frequency information by `adjust_cpuid`.
if ctx.tsc_frequency.is_some()
&& !cpuid
.cpu_id_entries
.iter()
.any(|entry| entry.function == 0x15)
{
cpuid.cpu_id_entries.push(CpuIdEntry {
function: 0x15,
index: 0,
flags: 0,
cpuid: CpuidResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
},
})
}
let entries = &mut cpuid.cpu_id_entries;
for entry in entries.iter_mut() {
adjust_cpuid(entry, ctx);
}
}
/// Sets up the cpuid entries for the given vcpu. Can fail if there are too many CPUs specified or
/// if an ioctl returns an error.
///
/// # Arguments
///
/// * `hypervisor` - `HypervisorX86_64` impl for getting supported CPU IDs.
/// * `irq_chip` - `IrqChipX86_64` for adjusting appropriate IrqChip CPUID bits.
/// * `vcpu` - `VcpuX86_64` for setting CPU ID.
/// * `vcpu_id` - The vcpu index of `vcpu`.
/// * `cpu_config` - CPU feature configurations.
pub fn setup_cpuid(
hypervisor: &dyn HypervisorX86_64,
irq_chip: &dyn IrqChipX86_64,
vcpu: &dyn VcpuX86_64,
vcpu_id: usize,
nrcpus: usize,
cpu_config: CpuConfigX86_64,
) -> Result<()> {
let mut cpuid = hypervisor
.get_supported_cpuid()
.map_err(Error::GetSupportedCpusFailed)?;
filter_cpuid(
&mut cpuid,
&CpuIdContext::new(
vcpu_id,
nrcpus,
Some(irq_chip),
cpu_config,
hypervisor.check_capability(HypervisorCap::CalibratedTscLeafRequired),
__cpuid_count,
__cpuid,
),
);
vcpu.set_cpuid(&cpuid)
.map_err(Error::SetSupportedCpusFailed)
}
const MANUFACTURER_ID_FUNCTION: u32 = 0x00000000;
const AMD_EBX: u32 = u32::from_le_bytes([b'A', b'u', b't', b'h']);
const AMD_EDX: u32 = u32::from_le_bytes([b'e', b'n', b't', b'i']);
const AMD_ECX: u32 = u32::from_le_bytes([b'c', b'A', b'M', b'D']);
const INTEL_EBX: u32 = u32::from_le_bytes([b'G', b'e', b'n', b'u']);
const INTEL_EDX: u32 = u32::from_le_bytes([b'i', b'n', b'e', b'I']);
const INTEL_ECX: u32 = u32::from_le_bytes([b'n', b't', b'e', b'l']);
pub fn cpu_manufacturer() -> CpuManufacturer {
// safe because MANUFACTURER_ID_FUNCTION is a well known cpuid function,
// and we own the result value afterwards.
let result = unsafe { __cpuid(MANUFACTURER_ID_FUNCTION) };
if result.ebx == AMD_EBX && result.edx == AMD_EDX && result.ecx == AMD_ECX {
return CpuManufacturer::Amd;
} else if result.ebx == INTEL_EBX && result.edx == INTEL_EDX && result.ecx == INTEL_ECX {
return CpuManufacturer::Intel;
}
return CpuManufacturer::Unknown;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpu_manufacturer_test() {
// this should be amd or intel. We don't support other processors for virtualization.
let manufacturer = cpu_manufacturer();
assert_ne!(manufacturer, CpuManufacturer::Unknown);
}
#[test]
#[cfg(unix)]
fn feature_and_vendor_name() {
let mut cpuid = hypervisor::CpuId::new(2);
let guest_mem =
vm_memory::GuestMemory::new(&[(vm_memory::GuestAddress(0), 0x10000)]).unwrap();
let kvm = hypervisor::kvm::Kvm::new().unwrap();
let vm = hypervisor::kvm::KvmVm::new(&kvm, guest_mem, Default::default()).unwrap();
let irq_chip = devices::KvmKernelIrqChip::new(vm, 1).unwrap();
let entries = &mut cpuid.cpu_id_entries;
entries.push(hypervisor::CpuIdEntry {
function: 0,
index: 0,
flags: 0,
cpuid: CpuidResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
},
});
entries.push(hypervisor::CpuIdEntry {
function: 1,
index: 0,
flags: 0,
cpuid: CpuidResult {
eax: 0,
ebx: 0,
ecx: 0x10,
edx: 0,
},
});
let cpu_config = CpuConfigX86_64::new(false, false, false, false, false, false);
filter_cpuid(
&mut cpuid,
&CpuIdContext::new(
1,
2,
Some(&irq_chip),
cpu_config,
false,
__cpuid_count,
__cpuid,
),
);
let entries = &mut cpuid.cpu_id_entries;
assert_eq!(entries[0].function, 0);
assert_eq!(1, (entries[1].cpuid.ebx >> EBX_CPUID_SHIFT) & 0x000000ff);
assert_eq!(
2,
(entries[1].cpuid.ebx >> EBX_CPU_COUNT_SHIFT) & 0x000000ff
);
assert_eq!(
EBX_CLFLUSH_CACHELINE,
(entries[1].cpuid.ebx >> EBX_CLFLUSH_SIZE_SHIFT) & 0x000000ff
);
assert_ne!(0, entries[1].cpuid.ecx & (1 << ECX_HYPERVISOR_SHIFT));
assert_ne!(0, entries[1].cpuid.edx & (1 << EDX_HTT_SHIFT));
}
#[test]
fn cpuid_copies_register() {
let fake_cpuid_count = |_function: u32, _index: u32| CpuidResult {
eax: 27,
ebx: 18,
ecx: 28,
edx: 18,
};
let fake_cpuid = |_function: u32| CpuidResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
};
let cpu_config = CpuConfigX86_64 {
force_calibrated_tsc_leaf: false,
host_cpu_topology: true,
enable_hwp: false,
enable_pnp_data: false,
no_smt: false,
itmt: false,
};
let ctx = CpuIdContext {
vcpu_id: 0,
cpu_count: 0,
x2apic: false,
tsc_deadline_timer: false,
apic_frequency: 0,
tsc_frequency: None,
cpu_config,
calibrated_tsc_leaf_required: false,
cpuid_count: fake_cpuid_count,
cpuid: fake_cpuid,
};
let mut cpu_id_entry = CpuIdEntry {
function: 0x4,
index: 0,
flags: 0,
cpuid: CpuidResult {
eax: 31,
ebx: 41,
ecx: 59,
edx: 26,
},
};
adjust_cpuid(&mut cpu_id_entry, &ctx);
assert_eq!(cpu_id_entry.cpuid.eax, 27)
}
}