use kvm_bindings::{KVM_CPUID_FLAG_SIGNIFCANT_INDEX, kvm_cpuid_entry2};
use crate::vmm::topology::Topology;
const PMU_ARCH_PERFMON_VERSION: u32 = 2;
const PMU_NUM_GP_COUNTERS: u32 = 4;
const PMU_GP_COUNTER_WIDTH: u32 = 48;
const PMU_EVENT_MASK_LENGTH: u32 = 7;
const PMU_NUM_FIXED_COUNTERS: u32 = 3;
const PMU_FIXED_COUNTER_WIDTH: u32 = 48;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CpuVendor {
Intel,
Amd,
Unknown,
}
fn detect_vendor(entries: &[kvm_cpuid_entry2]) -> CpuVendor {
let leaf0 = entries.iter().find(|e| e.function == 0 && e.index == 0);
match leaf0 {
Some(e) => {
match (e.ebx, e.edx, e.ecx) {
(0x756e_6547, 0x4965_6e69, 0x6c65_746e) => CpuVendor::Intel,
(0x6874_7541, 0x6974_6e65, 0x444d_4163) => CpuVendor::Amd,
_ => CpuVendor::Unknown,
}
}
None => CpuVendor::Unknown,
}
}
fn bits_needed(n: u32) -> u32 {
if n <= 1 {
return 0;
}
32 - (n - 1).leading_zeros()
}
pub fn apic_id(topo: &Topology, cpu_id: u32) -> u32 {
let (llc_id, core_id, thread_id) = topo.decompose(cpu_id);
let thread_bits = bits_needed(topo.threads_per_core);
let core_bits = bits_needed(topo.cores_per_llc);
(llc_id << (core_bits + thread_bits)) | (core_id << thread_bits) | thread_id
}
pub fn max_apic_id(topo: &Topology) -> u32 {
let total = topo.total_cpus();
if total == 0 {
return 0;
}
apic_id(topo, total - 1)
}
pub fn smt_shift(topo: &Topology) -> u32 {
bits_needed(topo.threads_per_core)
}
pub fn core_shift(topo: &Topology) -> u32 {
bits_needed(topo.threads_per_core) + bits_needed(topo.cores_per_llc)
}
fn topo_subleaf(
function: u32,
index: u32,
shift_to_next: u32,
count: u32,
level_type: u32,
apic: u32,
) -> kvm_cpuid_entry2 {
kvm_cpuid_entry2 {
function,
index,
flags: KVM_CPUID_FLAG_SIGNIFCANT_INDEX,
eax: shift_to_next,
ebx: count & 0xffff,
ecx: (level_type << 8) | (index & 0xff),
edx: apic,
..Default::default()
}
}
fn patch_cache_topology_eax(entry: &mut kvm_cpuid_entry2, smt: u32, core: u32, cores_per_llc: u32) {
let cache_level = (entry.eax >> 5) & 0x7;
let max_sharing = match cache_level {
1 | 2 => (1u32 << smt).saturating_sub(1),
3 => (1u32 << core).saturating_sub(1),
_ => 0,
};
entry.eax = (entry.eax & 0xfc003fff) | ((max_sharing & 0xfff) << 14);
let core_bits = bits_needed(cores_per_llc);
let max_core_ids = (1u32 << core_bits).saturating_sub(1);
entry.eax = (entry.eax & 0x03ffffff) | ((max_core_ids & 0x3f) << 26);
}
const CACHE_LINE_SIZE: u32 = 64;
const L1_CACHE_SIZE_KIB: u32 = 64;
const L1_CACHE_WAYS: u32 = 2;
const L2_CACHE_SIZE_KIB: u32 = 512;
const L2_CACHE_WAYS: u32 = 16;
const L3_CACHE_SIZE_KIB: u32 = 16 * 1024;
const L3_CACHE_WAYS: u32 = 16;
const ASSOC_ENC_16WAY: u32 = 0x8;
const L80000006_ECX: u32 =
(L2_CACHE_SIZE_KIB << 16) | (ASSOC_ENC_16WAY << 12) | (1 << 8) | CACHE_LINE_SIZE;
const L80000006_EDX: u32 =
((L3_CACHE_SIZE_KIB / 512) << 18) | (ASSOC_ENC_16WAY << 12) | (1 << 8) | CACHE_LINE_SIZE;
#[allow(clippy::too_many_arguments)]
fn amd_cache_subleaf(
index: u32,
cache_type: u32,
level: u32,
self_init: bool,
size_kib: u32,
ways: u32,
num_threads_sharing: u32,
flags: u32,
) -> kvm_cpuid_entry2 {
let sets = (size_kib * 1024) / (CACHE_LINE_SIZE * ways);
let eax = cache_type
| (level << 5)
| (u32::from(self_init) << 8)
| ((num_threads_sharing & 0xfff) << 14);
let ebx = (CACHE_LINE_SIZE - 1) | ((ways - 1) << 22);
kvm_cpuid_entry2 {
function: 0x8000_001d,
index,
flags: KVM_CPUID_FLAG_SIGNIFCANT_INDEX,
eax,
ebx,
ecx: sets - 1,
edx: flags,
..Default::default()
}
}
pub fn generate_cpuid(
base_cpuid: &[kvm_cpuid_entry2],
topo: &Topology,
cpu_id: u32,
performance_mode: bool,
) -> Vec<kvm_cpuid_entry2> {
let mut entries: Vec<kvm_cpuid_entry2> = base_cpuid.to_vec();
let vendor = detect_vendor(&entries);
let apic = apic_id(topo, cpu_id);
let smt = smt_shift(topo);
let core = core_shift(topo);
let total_cpus = topo.total_cpus();
let pkg_shift = bits_needed(max_apic_id(topo) + 1);
for entry in entries.iter_mut() {
match entry.function {
0x1 => {
entry.ebx = (entry.ebx & 0x00ffffff) | ((apic & 0xff) << 24);
let lpc = total_cpus.next_power_of_two().min(255);
entry.ebx = (entry.ebx & 0xff00ffff) | (lpc << 16);
if total_cpus > 1 {
entry.edx |= 1 << 28;
}
}
0x4 if vendor == CpuVendor::Intel => {
patch_cache_topology_eax(entry, smt, core, topo.cores_per_llc);
}
0xa => {
if entry.eax & 0xff != 0 {
entry.eax = PMU_ARCH_PERFMON_VERSION
| (PMU_NUM_GP_COUNTERS << 8)
| (PMU_GP_COUNTER_WIDTH << 16)
| (PMU_EVENT_MASK_LENGTH << 24);
entry.ebx = 0;
entry.ecx = 0;
entry.edx = PMU_NUM_FIXED_COUNTERS | (PMU_FIXED_COUNTER_WIDTH << 5);
}
}
0x8000_0001 if vendor == CpuVendor::Amd && total_cpus > 1 => {
entry.ecx |= (1 << 1) | (1 << 22);
}
0x8000_0006 if vendor == CpuVendor::Amd => {
entry.ecx = L80000006_ECX; entry.edx = L80000006_EDX; }
0x8000_0008 => {
if total_cpus > 1 {
entry.ecx = (pkg_shift << 12) | ((total_cpus - 1).min(0xff));
} else {
entry.ecx = 0;
}
}
0x8000_001e if vendor == CpuVendor::Amd => {
entry.eax = apic;
let (llc_id, core_id, _) = topo.decompose(cpu_id);
entry.ebx = ((topo.threads_per_core - 1) << 8) | (core_id & 0xff);
let node_id = topo.numa_node_of(llc_id);
entry.ecx = node_id | ((topo.numa_nodes - 1) << 8);
entry.edx = 0;
}
_ => {}
}
}
let topo_leaves: &[u32] = if vendor == CpuVendor::Intel {
&[0xb, 0x1f]
} else {
&[0xb]
};
entries.retain(|e| e.function != 0xb && e.function != 0x1f);
for &func in topo_leaves {
entries.push(topo_subleaf(func, 0, smt, topo.threads_per_core, 1, apic));
entries.push(topo_subleaf(func, 1, pkg_shift, total_cpus, 2, apic));
entries.push(topo_subleaf(func, 2, 0, 0, 0, apic));
}
if vendor == CpuVendor::Amd {
let smt_sharing = (1u32 << smt).saturating_sub(1);
let llc_sharing = (1u32 << core).saturating_sub(1);
entries.retain(|e| e.function != 0x8000_001d);
entries.push(amd_cache_subleaf(
0,
1,
1,
true,
L1_CACHE_SIZE_KIB,
L1_CACHE_WAYS,
smt_sharing,
0x1,
)); entries.push(amd_cache_subleaf(
1,
2,
1,
true,
L1_CACHE_SIZE_KIB,
L1_CACHE_WAYS,
smt_sharing,
0x1,
)); entries.push(amd_cache_subleaf(
2,
3,
2,
false,
L2_CACHE_SIZE_KIB,
L2_CACHE_WAYS,
smt_sharing,
0x0,
)); entries.push(amd_cache_subleaf(
3,
3,
3,
true,
L3_CACHE_SIZE_KIB,
L3_CACHE_WAYS,
llc_sharing,
0x6,
)); entries.push(kvm_cpuid_entry2 {
function: 0x8000_001d,
index: 4,
flags: KVM_CPUID_FLAG_SIGNIFCANT_INDEX,
..Default::default()
});
if !entries.iter().any(|e| e.function == 0x8000_0006) {
entries.push(kvm_cpuid_entry2 {
function: 0x8000_0006,
ecx: L80000006_ECX,
edx: L80000006_EDX,
..Default::default()
});
}
}
if !entries.iter().any(|e| e.function == 0x4000_0000) {
entries.push(kvm_cpuid_entry2 {
function: 0x4000_0000,
index: 0,
flags: 0,
eax: 0x4000_0001, ebx: 0x4b56_4d4b, ecx: 0x564b_4d56, edx: 0x0000_004d, ..Default::default()
});
}
let wide_smp = max_apic_id(topo) > crate::vmm::x86_64::kvm::MAX_XAPIC_ID;
if wide_smp {
if let Some(entry) = entries.iter_mut().find(|e| e.function == 0x4000_0001) {
entry.eax |= 1 << 15; } else {
entries.push(kvm_cpuid_entry2 {
function: 0x4000_0001,
eax: 1 << 15,
..Default::default()
});
}
}
if performance_mode && let Some(entry) = entries.iter_mut().find(|e| e.function == 0x4000_0001)
{
entry.edx |= 1;
}
if (wide_smp || performance_mode)
&& let Some(entry) = entries.iter_mut().find(|e| e.function == 0x4000_0000)
{
entry.eax = entry.eax.max(0x4000_0001);
}
entries
}
#[cfg(test)]
mod tests_cpuid;
#[cfg(test)]
mod tests_math;