use super::*;
#[test]
fn bits_needed_values() {
assert_eq!(bits_needed(1), 0);
assert_eq!(bits_needed(2), 1);
assert_eq!(bits_needed(3), 2);
assert_eq!(bits_needed(4), 2);
assert_eq!(bits_needed(5), 3);
assert_eq!(bits_needed(8), 3);
assert_eq!(bits_needed(9), 4);
assert_eq!(bits_needed(16), 4);
}
fn make_cache_entry(level: u32) -> kvm_cpuid_entry2 {
kvm_cpuid_entry2 {
function: 0x4, index: 0,
flags: 0,
eax: (level << 5) | 2 | (0xfff << 14) | (0x3f << 26),
ebx: 0,
ecx: 0,
edx: 0,
..Default::default()
}
}
#[test]
fn patch_cache_l1_smt_scoped() {
let mut entry = make_cache_entry(1);
patch_cache_topology_eax(&mut entry, 1, 3, 4);
let sharing = (entry.eax >> 14) & 0xfff;
assert_eq!(sharing, 1, "L1 sharing should be SMT-scoped");
}
#[test]
fn patch_cache_l2_smt_scoped() {
let mut entry = make_cache_entry(2);
patch_cache_topology_eax(&mut entry, 1, 3, 4);
let sharing = (entry.eax >> 14) & 0xfff;
assert_eq!(sharing, 1, "L2 sharing should be SMT-scoped");
}
#[test]
fn patch_cache_l3_llc_scoped() {
let mut entry = make_cache_entry(3);
patch_cache_topology_eax(&mut entry, 1, 3, 4);
let sharing = (entry.eax >> 14) & 0xfff;
assert_eq!(sharing, 7, "L3 sharing should be LLC-scoped");
}
#[test]
fn patch_cache_unknown_level_zero() {
let mut entry0 = make_cache_entry(0);
patch_cache_topology_eax(&mut entry0, 1, 3, 4);
assert_eq!((entry0.eax >> 14) & 0xfff, 0, "level 0 sharing should be 0");
let mut entry4 = make_cache_entry(4);
patch_cache_topology_eax(&mut entry4, 1, 3, 4);
assert_eq!((entry4.eax >> 14) & 0xfff, 0, "level 4 sharing should be 0");
let mut entry7 = make_cache_entry(7);
patch_cache_topology_eax(&mut entry7, 1, 3, 4);
assert_eq!((entry7.eax >> 14) & 0xfff, 0, "level 7 sharing should be 0");
}
#[test]
fn patch_cache_single_core_single_thread() {
let mut entry_l1 = make_cache_entry(1);
patch_cache_topology_eax(&mut entry_l1, 0, 0, 1);
assert_eq!(
(entry_l1.eax >> 14) & 0xfff,
0,
"1c/1t L1 sharing should be 0"
);
assert_eq!((entry_l1.eax >> 26) & 0x3f, 0, "1c/1t core IDs should be 0");
let mut entry_l3 = make_cache_entry(3);
patch_cache_topology_eax(&mut entry_l3, 0, 0, 1);
assert_eq!(
(entry_l3.eax >> 14) & 0xfff,
0,
"1c/1t L3 sharing should be 0"
);
}
#[test]
fn patch_cache_large_topology() {
let smt = bits_needed(2); let core = smt + bits_needed(16); assert_eq!(smt, 1);
assert_eq!(core, 5);
let mut entry_l3 = make_cache_entry(3);
patch_cache_topology_eax(&mut entry_l3, smt, core, 16);
let sharing = (entry_l3.eax >> 14) & 0xfff;
assert_eq!(sharing, 31, "16c/2t L3 sharing");
let core_ids = (entry_l3.eax >> 26) & 0x3f;
assert_eq!(core_ids, 15, "16c/2t core IDs");
assert!(sharing < (1 << 12));
assert!(core_ids < (1 << 6));
let mut entry_l1 = make_cache_entry(1);
patch_cache_topology_eax(&mut entry_l1, smt, core, 16);
assert_eq!(
(entry_l1.eax >> 14) & 0xfff,
1,
"16c/2t L1 sharing (SMT-scoped)"
);
}
#[test]
fn patch_cache_preserves_lower_bits() {
let mut entry = kvm_cpuid_entry2 {
eax: (3 << 5) | 0b10101 | (0x2a << 8), ..Default::default()
};
patch_cache_topology_eax(&mut entry, 1, 3, 4);
assert_eq!(entry.eax & 0x1f, 0b10101, "type bits [4:0] preserved");
assert_eq!((entry.eax >> 5) & 0x7, 3, "level bits [7:5] preserved");
assert_eq!((entry.eax >> 8) & 0x3f, 0x2a, "bits [13:8] preserved");
}
#[test]
fn patch_cache_leaf4_and_8000001d_identical() {
let topos = [(1, 1, 1), (2, 4, 1), (2, 4, 2), (4, 8, 2), (8, 16, 2)];
for (llcs, cores, threads) in topos {
let smt = bits_needed(threads);
let core = smt + bits_needed(cores);
for level in 1..=3 {
let mut leaf4 = make_cache_entry(level);
leaf4.function = 0x4;
let mut leaf_amd = make_cache_entry(level);
leaf_amd.function = 0x8000_001d;
patch_cache_topology_eax(&mut leaf4, smt, core, cores);
patch_cache_topology_eax(&mut leaf_amd, smt, core, cores);
assert_eq!(
leaf4.eax, leaf_amd.eax,
"{llcs}l/{cores}c/{threads}t L{level}: leaf 0x4 and 0x8000001D \
EAX should be identical"
);
}
}
}
#[test]
fn apic_ids_unique() {
let t = Topology {
llcs: 2,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let ids: Vec<u32> = (0..t.total_cpus()).map(|i| apic_id(&t, i)).collect();
let unique: std::collections::HashSet<u32> = ids.iter().copied().collect();
assert_eq!(ids.len(), unique.len(), "APIC IDs must be unique: {ids:?}");
}
#[test]
fn apic_ids_smt_siblings_adjacent() {
let t = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let smt_mask = (1u32 << smt_shift(&t)) - 1;
for core_start in (0..t.total_cpus()).step_by(t.threads_per_core as usize) {
let base = apic_id(&t, core_start) & !smt_mask;
for thread in 0..t.threads_per_core {
let apic = apic_id(&t, core_start + thread);
assert_eq!(
apic & !smt_mask,
base,
"SMT siblings should share upper bits: cpu {}, apic {apic:#x}",
core_start + thread
);
}
}
}
#[test]
fn apic_ids_same_llc_share_upper_bits() {
let t = Topology {
llcs: 2,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let pkg_mask = !((1u32 << core_shift(&t)) - 1);
let cpus_per_llc = t.cores_per_llc * t.threads_per_core;
for llc in 0..t.llcs {
let start = llc * cpus_per_llc;
let llc_bits = apic_id(&t, start) & pkg_mask;
for cpu in start..start + cpus_per_llc {
assert_eq!(
apic_id(&t, cpu) & pkg_mask,
llc_bits,
"CPU {cpu} should be in LLC {llc}"
);
}
}
let s0 = apic_id(&t, 0) & pkg_mask;
let s1 = apic_id(&t, cpus_per_llc) & pkg_mask;
assert_ne!(s0, s1, "different LLCs should have different package IDs");
}
#[test]
fn smt_shift_values() {
assert_eq!(
smt_shift(&Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
}),
0
);
assert_eq!(
smt_shift(&Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
}),
1
);
assert_eq!(
smt_shift(&Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 4,
numa_nodes: 1,
nodes: None,
distances: None,
}),
2
);
}
#[test]
fn core_shift_values() {
assert_eq!(
core_shift(&Topology {
llcs: 1,
cores_per_llc: 4,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
}),
2
);
assert_eq!(
core_shift(&Topology {
llcs: 1,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
}),
3
);
}
#[test]
fn generate_cpuid_produces_entries() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return, };
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
assert!(!cpuid.is_empty());
let leaf1 = cpuid.iter().find(|e| e.function == 1);
if let Some(entry) = leaf1 {
let apic_from_cpuid = (entry.ebx >> 24) & 0xff;
assert_eq!(apic_from_cpuid, apic_id(&topo, 0));
}
}
#[test]
fn generate_cpuid_different_per_vcpu() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid0 = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let cpuid1 = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
1,
false,
);
let leaf_b_0 = cpuid0.iter().find(|e| e.function == 0xb && e.index == 0);
let leaf_b_1 = cpuid1.iter().find(|e| e.function == 0xb && e.index == 0);
if let (Some(e0), Some(e1)) = (leaf_b_0, leaf_b_1) {
assert_ne!(
e0.edx, e1.edx,
"different vCPUs should have different x2APIC IDs"
);
}
}
#[test]
fn topology_odd_counts() {
let t = Topology {
llcs: 3,
cores_per_llc: 3,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(t.total_cpus(), 9);
let ids: Vec<u32> = (0..9).map(|i| apic_id(&t, i)).collect();
let unique: std::collections::HashSet<u32> = ids.iter().copied().collect();
assert_eq!(
unique.len(),
9,
"odd topology APIC IDs must be unique: {ids:?}"
);
}
#[test]
fn leaf1_lpc_is_package_total() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 4,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(topo.total_cpus(), 32);
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf1 = cpuid.iter().find(|e| e.function == 1);
if let Some(entry) = leaf1 {
let lpc = (entry.ebx >> 16) & 0xff;
assert_eq!(
lpc,
32, "EBX[23:16] is the package logical-processor count (all 32 CPUs), \
not one LLC's worth"
);
}
}
#[test]
fn apic_ids_unique_representative_topologies() {
let topos = [
(1, 1, 1), (2, 1, 1), (3, 3, 1), (1, 1, 2), (2, 4, 2), (7, 5, 3), (15, 16, 1), (14, 9, 2), (2, 128, 1), ];
for (llcs, cores, threads) in topos {
let t = Topology {
llcs,
cores_per_llc: cores,
threads_per_core: threads,
numa_nodes: 1,
nodes: None,
distances: None,
};
let ids: Vec<u32> = (0..t.total_cpus()).map(|i| apic_id(&t, i)).collect();
let unique: std::collections::HashSet<u32> = ids.iter().copied().collect();
assert_eq!(
ids.len(),
unique.len(),
"topology {llcs}l/{cores}c/{threads}t: APIC IDs not unique"
);
}
}
#[test]
fn leaf0b_subleaf0_ebx_is_threads_per_core() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 2,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf_b_0 = cpuid.iter().find(|e| e.function == 0xb && e.index == 0);
if let Some(entry) = leaf_b_0 {
assert_eq!(
entry.ebx & 0xffff,
2, "leaf 0xB subleaf 0 EBX should be threads per core"
);
assert_eq!(
entry.eax,
smt_shift(&topo),
"leaf 0xB subleaf 0 EAX should be smt_shift"
);
}
}
#[test]
fn leaf0b_subleaf1_core_spans_package() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 2,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf_b_1 = cpuid.iter().find(|e| e.function == 0xb && e.index == 1);
if let Some(entry) = leaf_b_1 {
assert_eq!(
entry.ebx & 0xffff,
topo.total_cpus(),
"leaf 0xB Core subleaf EBX is the whole package's logical CPUs"
);
assert_eq!(
entry.eax,
bits_needed(max_apic_id(&topo) + 1),
"leaf 0xB Core subleaf EAX is the package shift (apic >> it == 0)"
);
}
}
#[test]
fn leaf0b_ecx_includes_subleaf_index() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
if let Some(entry) = cpuid.iter().find(|e| e.function == 0xb && e.index == 0) {
assert_eq!(entry.ecx & 0xff, 0, "subleaf 0 ECX[7:0] should be 0");
assert_eq!(
(entry.ecx >> 8) & 0xff,
1,
"subleaf 0 ECX[15:8] should be 1 (SMT)"
);
}
if let Some(entry) = cpuid.iter().find(|e| e.function == 0xb && e.index == 1) {
assert_eq!(entry.ecx & 0xff, 1, "subleaf 1 ECX[7:0] should be 1");
assert_eq!(
(entry.ecx >> 8) & 0xff,
2,
"subleaf 1 ECX[15:8] should be 2 (Core)"
);
}
}
#[test]
fn leaf4_l3_shared_within_llc() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 2,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let l3 = cpuid
.iter()
.find(|e| e.function == 0x4 && ((e.eax >> 5) & 0x7) == 3);
if let Some(entry) = l3 {
let max_sharing = ((entry.eax >> 14) & 0xfff) + 1;
let expected = 1u32 << core_shift(&topo); assert_eq!(max_sharing, expected, "L3 max sharing: APIC-ID-space value");
}
}
#[test]
fn leaf4_core_ids_apic_space() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 1,
cores_per_llc: 3,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf4 = cpuid
.iter()
.find(|e| e.function == 0x4 && ((e.eax >> 5) & 0x7) > 0);
if let Some(entry) = leaf4 {
let max_core_ids = ((entry.eax >> 26) & 0x3f) + 1;
let core_bits = bits_needed(topo.cores_per_llc);
assert_eq!(
max_core_ids,
1 << core_bits,
"leaf 0x4 EAX[31:26]+1 should be power-of-2 from APIC ID space"
);
}
}
#[test]
fn leaf1_hypervisor_bit_set() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf1 = cpuid.iter().find(|e| e.function == 1);
if let Some(entry) = leaf1 {
assert_ne!(
entry.ecx & (1 << 31),
0,
"hypervisor bit (ECX.31) should be set"
);
}
}
#[test]
fn leaf1_clflush_set() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf1 = cpuid.iter().find(|e| e.function == 1);
if let Some(entry) = leaf1 {
let clflush = (entry.ebx >> 8) & 0xff;
assert_eq!(clflush, 8, "CLFLUSH should be 8 (64-byte cache lines)");
}
}
#[test]
fn leaf_0xa_pmu_v2_synthesized() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf_a = cpuid.iter().find(|e| e.function == 0xa);
if let Some(entry) = leaf_a {
let version = entry.eax & 0xff;
if version == 0 {
return;
}
let num_gp = (entry.eax >> 8) & 0xff;
let gp_width = (entry.eax >> 16) & 0xff;
let mask_len = (entry.eax >> 24) & 0xff;
assert_eq!(version, PMU_ARCH_PERFMON_VERSION, "PMU v2 version");
assert_eq!(num_gp, PMU_NUM_GP_COUNTERS, "PMU v2 GP counter count");
assert_eq!(gp_width, PMU_GP_COUNTER_WIDTH, "PMU v2 GP counter width");
assert_eq!(
mask_len, PMU_EVENT_MASK_LENGTH,
"mask_length must equal ARCH_PERFMON_EVENTS_COUNT"
);
assert_eq!(entry.ebx, 0, "no architectural events disabled");
assert_eq!(entry.ecx, 0, "ECX reserved for PMU v2");
let num_fixed = entry.edx & 0x1f;
let fixed_width = (entry.edx >> 5) & 0xff;
assert_eq!(
num_fixed, PMU_NUM_FIXED_COUNTERS,
"PMU v2 fixed counter count"
);
assert_eq!(
fixed_width, PMU_FIXED_COUNTER_WIDTH,
"PMU v2 fixed counter width"
);
assert_eq!(
entry.edx & !0x1fff,
0,
"EDX bits[31:13] must be zero for PMU v2"
);
}
}
#[test]
fn leaf_0xa_absent_from_base_stays_absent() {
let base = vec![kvm_cpuid_entry2 {
function: 0,
index: 0,
flags: 0,
eax: 0,
ebx: 0x756e_6547,
edx: 0x4965_6e69,
ecx: 0x6c65_746e,
..Default::default()
}];
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(&base, &topo, 0, false);
assert!(
cpuid.iter().all(|e| e.function != 0xa),
"leaf 0xA must not be fabricated when absent from base"
);
}
#[test]
fn leaf_0xa_zero_version_preserved() {
let base = vec![
kvm_cpuid_entry2 {
function: 0,
index: 0,
flags: 0,
eax: 0,
ebx: 0x756e_6547,
edx: 0x4965_6e69,
ecx: 0x6c65_746e,
..Default::default()
},
kvm_cpuid_entry2 {
function: 0xa,
index: 0,
flags: 0,
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
..Default::default()
},
];
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(&base, &topo, 0, false);
let leaf_a = cpuid
.iter()
.find(|e| e.function == 0xa)
.expect("leaf 0xA preserved from base");
assert_eq!(leaf_a.eax, 0, "EAX must stay zero (host PMU disabled)");
assert_eq!(leaf_a.ebx, 0, "EBX must stay zero");
assert_eq!(leaf_a.ecx, 0, "ECX must stay zero");
assert_eq!(leaf_a.edx, 0, "EDX must stay zero");
}
#[test]
fn leaf_0xa_nonzero_version_synthesized_to_v2() {
let base = vec![
kvm_cpuid_entry2 {
function: 0,
index: 0,
flags: 0,
eax: 0,
ebx: 0x756e_6547,
edx: 0x4965_6e69,
ecx: 0x6c65_746e,
..Default::default()
},
kvm_cpuid_entry2 {
function: 0xa,
index: 0,
flags: 0,
eax: 5 | (8 << 8) | (56 << 16) | (10 << 24),
ebx: 0xdead_beef,
ecx: 0xcafe_f00d,
edx: 0xface_d00d,
..Default::default()
},
];
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(&base, &topo, 0, false);
let leaf_a = cpuid
.iter()
.find(|e| e.function == 0xa)
.expect("leaf 0xA present from base");
let expected_eax = PMU_ARCH_PERFMON_VERSION
| (PMU_NUM_GP_COUNTERS << 8)
| (PMU_GP_COUNTER_WIDTH << 16)
| (PMU_EVENT_MASK_LENGTH << 24);
let expected_edx = PMU_NUM_FIXED_COUNTERS | (PMU_FIXED_COUNTER_WIDTH << 5);
assert_eq!(leaf_a.eax, expected_eax, "EAX synthesized to PMU v2");
assert_eq!(leaf_a.ebx, 0, "EBX cleared (no events disabled)");
assert_eq!(leaf_a.ecx, 0, "ECX cleared (reserved)");
assert_eq!(leaf_a.edx, expected_edx, "EDX synthesized to PMU v2");
}
#[test]
fn leaf_0xa_amd_vendor_same_synthesis() {
let base = vec![
kvm_cpuid_entry2 {
function: 0,
index: 0,
flags: 0,
eax: 0,
ebx: 0x6874_7541, edx: 0x6974_6e65, ecx: 0x444d_4163, ..Default::default()
},
kvm_cpuid_entry2 {
function: 0xa,
index: 0,
flags: 0,
eax: 1 | (2 << 8) | (32 << 16) | (4 << 24),
ebx: 0x1111_1111,
ecx: 0x2222_2222,
edx: 0x3333_3333,
..Default::default()
},
];
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(&base, &topo, 0, false);
let leaf_a = cpuid
.iter()
.find(|e| e.function == 0xa)
.expect("leaf 0xA present from base");
let expected_eax = PMU_ARCH_PERFMON_VERSION
| (PMU_NUM_GP_COUNTERS << 8)
| (PMU_GP_COUNTER_WIDTH << 16)
| (PMU_EVENT_MASK_LENGTH << 24);
let expected_edx = PMU_NUM_FIXED_COUNTERS | (PMU_FIXED_COUNTER_WIDTH << 5);
assert_eq!(
leaf_a.eax, expected_eax,
"AMD synthesized identically to Intel"
);
assert_eq!(leaf_a.ebx, 0, "EBX cleared on AMD");
assert_eq!(leaf_a.ecx, 0, "ECX cleared on AMD");
assert_eq!(
leaf_a.edx, expected_edx,
"AMD synthesized identically to Intel"
);
}
#[test]
fn max_hypervisor_leaf_not_lowered_in_performance_mode() {
let base = vec![
kvm_cpuid_entry2 {
function: 0,
index: 0,
flags: 0,
eax: 0,
ebx: 0x756e_6547,
edx: 0x4965_6e69,
ecx: 0x6c65_746e,
..Default::default()
},
kvm_cpuid_entry2 {
function: 0x4000_0000,
index: 0,
flags: 0,
eax: 0x4000_0010, ebx: 0,
ecx: 0,
edx: 0,
..Default::default()
},
kvm_cpuid_entry2 {
function: 0x4000_0001,
index: 0,
flags: 0,
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
..Default::default()
},
];
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(&base, &topo, 0, true);
let leaf40 = cpuid
.iter()
.find(|e| e.function == 0x4000_0000)
.expect("leaf 0x40000000 present");
assert_eq!(
leaf40.eax, 0x4000_0010,
"max hypervisor leaf must not regress when already > 0x40000001"
);
}
#[test]
fn hypervisor_leaf_present() {
let kvm = match kvm_ioctls::Kvm::new() {
Ok(k) => k,
Err(_) => return,
};
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let cpuid = generate_cpuid(
kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
.unwrap()
.as_slice(),
&topo,
0,
false,
);
let leaf_40 = cpuid.iter().find(|e| e.function == 0x4000_0000);
assert!(leaf_40.is_some(), "hypervisor leaf 0x40000000 should exist");
}
#[test]
fn decompose_roundtrip_representative_topologies() {
let topos = [
(1, 1, 1), (2, 1, 1), (3, 3, 1), (1, 1, 2), (2, 4, 2), (7, 5, 3), (15, 16, 1), (14, 9, 2), ];
for (llcs, cores, threads) in topos {
let t = Topology {
llcs,
cores_per_llc: cores,
threads_per_core: threads,
numa_nodes: 1,
nodes: None,
distances: None,
};
for cpu in 0..t.total_cpus() {
let (s, c, th) = t.decompose(cpu);
assert!(s < llcs, "cpu {cpu}: llc {s} >= {llcs}");
assert!(c < cores, "cpu {cpu}: core {c} >= {cores}");
assert!(th < threads, "cpu {cpu}: thread {th} >= {threads}");
let recomposed = s * cores * threads + c * threads + th;
assert_eq!(
recomposed, cpu,
"decompose roundtrip failed for {llcs}l/{cores}c/{threads}t cpu {cpu}"
);
}
}
}