blob: 3e49d03252c7827a04e7bc002fe7aab7c311b965 [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::{__cpuid, __cpuid_count};
use std::result;
use devices::{IrqChipCap, IrqChipX86_64};
use hypervisor::{HypervisorX86_64, VcpuX86_64};
use remain::sorted;
use thiserror::Error;
#[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 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.
fn filter_cpuid(
vcpu_id: usize,
cpu_count: usize,
cpuid: &mut hypervisor::CpuId,
irq_chip: &dyn IrqChipX86_64,
no_smt: bool,
host_cpu_topology: bool,
) {
let entries = &mut cpuid.cpu_id_entries;
for entry in entries {
match entry.function {
1 => {
// X86 hypervisor feature
if entry.index == 0 {
entry.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
}
if irq_chip.check_capability(IrqChipCap::X2Apic) {
entry.ecx |= 1 << ECX_X2APIC_SHIFT;
} else {
entry.ecx &= !(1 << ECX_X2APIC_SHIFT);
}
if irq_chip.check_capability(IrqChipCap::TscDeadlineTimer) {
entry.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT;
}
if host_cpu_topology {
entry.ebx |= EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT;
// Expose HT flag to Guest.
let result = unsafe { __cpuid(entry.function) };
entry.edx |= result.edx & (1 << EDX_HTT_SHIFT);
continue;
}
entry.ebx = (vcpu_id << EBX_CPUID_SHIFT) as u32
| (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
if cpu_count > 1 && !no_smt {
// This field is only valid if CPUID.1.EDX.HTT[bit 28]= 1.
entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
entry.edx |= 1 << EDX_HTT_SHIFT;
}
}
2 | 0x80000005 | 0x80000006 => unsafe {
let result = __cpuid(entry.function);
entry.eax = result.eax;
entry.ebx = result.ebx;
entry.ecx = result.ecx;
entry.edx = result.edx;
},
4 => {
unsafe {
let result = __cpuid_count(entry.function, entry.index);
entry.eax = result.eax;
entry.ebx = result.ebx;
entry.ecx = result.ecx;
entry.edx = result.edx;
}
if host_cpu_topology {
continue;
}
entry.eax &= !0xFC000000;
if cpu_count > 1 {
let cpu_cores = if no_smt {
cpu_count as u32
} else if cpu_count % 2 == 0 {
(cpu_count >> 1) as u32
} else {
1
};
entry.eax |= (cpu_cores - 1) << EAX_CPU_CORES_SHIFT;
}
}
6 => {
// Clear X86 EPB feature. No frequency selection in the hypervisor.
entry.ecx &= !(1 << ECX_EPB_SHIFT);
}
7 => {
if 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 { __cpuid_count(entry.function, entry.index) };
entry.edx |= result.edx & (1 << EDX_HYBRID_CPU_SHIFT);
}
}
0x1A => {
// Hybrid information leaf.
if host_cpu_topology {
// Safe because we pass 0x1A for this call and the host supports the
// `cpuid` instruction
let result = unsafe { __cpuid(entry.function) };
entry.eax = result.eax;
entry.ebx = result.ebx;
entry.ecx = result.ecx;
entry.edx = result.edx;
}
}
0xB | 0x1F => {
if host_cpu_topology {
continue;
}
// 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.
entry.edx = vcpu_id as u32; // x2APIC ID
if entry.index == 0 {
if no_smt || (cpu_count == 1) {
// Make it so that all VCPUs appear as different,
// non-hyperthreaded cores on the same package.
entry.eax = 0; // Shift to get id of next level
entry.ebx = 1; // Number of logical cpus at this level
} else if cpu_count % 2 == 0 {
// Each core has 2 hyperthreads
entry.eax = 1; // Shift to get id of next level
entry.ebx = 2; // Number of logical cpus at this level
} else {
// One core contain all the cpu_count hyperthreads
let cpu_bits: u32 = 32 - ((cpu_count - 1) as u32).leading_zeros();
entry.eax = cpu_bits; // Shift to get id of next level
entry.ebx = cpu_count as u32; // Number of logical cpus at this level
}
entry.ecx = (ECX_TOPO_SMT_TYPE << ECX_TOPO_TYPE_SHIFT) | entry.index;
} else if entry.index == 1 {
let cpu_bits: u32 = 32 - ((cpu_count - 1) as u32).leading_zeros();
entry.eax = cpu_bits;
entry.ebx = (cpu_count as u32) & 0xffff; // Number of logical cpus at this level
entry.ecx = (ECX_TOPO_CORE_TYPE << ECX_TOPO_TYPE_SHIFT) | entry.index;
} else {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
}
}
_ => (),
}
}
}
/// 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.
/// * `vcpu` - `VcpuX86_64` for setting CPU ID.
/// * `vcpu_id` - The vcpu index of `vcpu`.
/// * `nrcpus` - The number of vcpus being used by this VM.
/// * `no_smt` - The flag indicates whether vCPUs supports SMT.
/// * `host_cpu_topology` - The flag indicates whether vCPUs use mirror CPU topology.
pub fn setup_cpuid(
hypervisor: &dyn HypervisorX86_64,
irq_chip: &dyn IrqChipX86_64,
vcpu: &dyn VcpuX86_64,
vcpu_id: usize,
nrcpus: usize,
no_smt: bool,
host_cpu_topology: bool,
) -> Result<()> {
let mut cpuid = hypervisor
.get_supported_cpuid()
.map_err(Error::GetSupportedCpusFailed)?;
filter_cpuid(
vcpu_id,
nrcpus,
&mut cpuid,
irq_chip,
no_smt,
host_cpu_topology,
);
vcpu.set_cpuid(&cpuid)
.map_err(Error::SetSupportedCpusFailed)
}
/// get host cpu max physical address bits
pub fn phy_max_address_bits() -> u32 {
let mut phys_bits: u32 = 36;
let highest_ext_function = unsafe { __cpuid(0x80000000) };
if highest_ext_function.eax >= 0x80000008 {
let addr_size = unsafe { __cpuid(0x80000008) };
phys_bits = addr_size.eax & 0xff;
}
phys_bits
}
#[cfg(test)]
mod tests {
use super::*;
use hypervisor::CpuIdEntry;
#[test]
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).unwrap();
let irq_chip = devices::KvmKernelIrqChip::new(vm, 1).unwrap();
let entries = &mut cpuid.cpu_id_entries;
entries.push(CpuIdEntry {
function: 0,
..Default::default()
});
entries.push(CpuIdEntry {
function: 1,
ecx: 0x10,
edx: 0,
..Default::default()
});
filter_cpuid(1, 2, &mut cpuid, &irq_chip, false, false);
let entries = &mut cpuid.cpu_id_entries;
assert_eq!(entries[0].function, 0);
assert_eq!(1, (entries[1].ebx >> EBX_CPUID_SHIFT) & 0x000000ff);
assert_eq!(2, (entries[1].ebx >> EBX_CPU_COUNT_SHIFT) & 0x000000ff);
assert_eq!(
EBX_CLFLUSH_CACHELINE,
(entries[1].ebx >> EBX_CLFLUSH_SIZE_SHIFT) & 0x000000ff
);
assert_ne!(0, entries[1].ecx & (1 << ECX_HYPERVISOR_SHIFT));
assert_ne!(0, entries[1].edx & (1 << EDX_HTT_SHIFT));
}
}