use super::{CacheTopology, Level};
use raw_cpuid::{Associativity, CacheType, CpuId, CpuIdReader};
pub fn detect() -> Option<CacheTopology> {
let cpuid = CpuId::new();
detect_topology_leaf(&cpuid).or_else(|| detect_amd_legacy(&cpuid))
}
fn assoc_num(a: Associativity) -> usize {
match a {
Associativity::DirectMapped => 1,
Associativity::NWay(n) => n as usize,
Associativity::FullyAssociative => 64,
_ => 8,
}
}
fn detect_amd_legacy<R: CpuIdReader>(cpuid: &CpuId<R>) -> Option<CacheTopology> {
let l1 = cpuid.get_l1_cache_and_tlb_info()?;
let l23 = cpuid.get_l2_l3_cache_and_tlb_info()?;
let l1d = Level {
bytes: l1.dcache_size() as usize * 1024,
assoc: assoc_num(l1.dcache_associativity()),
line: l1.dcache_line_size() as usize,
shared_by: 1,
};
let l2 = Level {
bytes: l23.l2cache_size() as usize * 1024,
assoc: assoc_num(l23.l2cache_associativity()),
line: l23.l2cache_line_size() as usize,
shared_by: 1,
};
let l3_bytes = l23.l3cache_size() as usize * 512 * 1024;
let l3 = (l3_bytes > 0).then(|| Level {
bytes: l3_bytes,
assoc: assoc_num(l23.l3cache_associativity()),
line: l23.l3cache_line_size() as usize,
shared_by: 1,
});
Some(CacheTopology { l1d, l2, l3 })
}
fn detect_topology_leaf<R: CpuIdReader>(cpuid: &CpuId<R>) -> Option<CacheTopology> {
let params = cpuid.get_cache_parameters()?;
let mut l1d = None;
let mut l2 = None;
let mut l3 = None;
for c in params {
if !matches!(c.cache_type(), CacheType::Data | CacheType::Unified) {
continue;
}
let bytes =
c.associativity() * c.coherency_line_size() * c.sets() * c.physical_line_partitions();
let level = Level {
bytes,
assoc: c.associativity(),
line: c.coherency_line_size(),
shared_by: 1,
};
match c.level() {
1 => l1d = Some(level),
2 => l2 = Some(level),
3 => l3 = Some(level),
_ => {}
}
}
Some(CacheTopology {
l1d: l1d?,
l2: l2?,
l3,
})
}
#[cfg(test)]
mod tests {
use raw_cpuid::{CpuId, CpuIdResult};
fn leaf04(ctype: u32, level: u32, line: u32, parts: u32, ways: u32, sets: u32) -> CpuIdResult {
CpuIdResult {
eax: ctype | (level << 5),
ebx: (line - 1) | ((parts - 1) << 12) | ((ways - 1) << 22),
ecx: sets - 1,
edx: 0,
}
}
#[test]
fn detect_topology_leaf_from_canned_intel_leaf04() {
let reader = |eax: u32, ecx: u32| -> CpuIdResult {
match (eax, ecx) {
(0x0, _) => CpuIdResult {
eax: 0x16,
ebx: 0x756e_6547, ecx: 0x6c65_746e, edx: 0x4965_6e69, },
(0x4, 0) => leaf04(1, 1, 64, 1, 12, 64), (0x4, 1) => leaf04(2, 1, 64, 1, 8, 64), (0x4, 2) => leaf04(3, 2, 64, 1, 8, 2048), (0x4, 3) => leaf04(3, 3, 64, 1, 16, 32768), (0x4, _) => CpuIdResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
},
_ => CpuIdResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
},
}
};
let cpuid = CpuId::with_cpuid_fn(reader);
let t = super::detect_topology_leaf(&cpuid).expect("canned Intel leaf-04h must detect");
assert_eq!(t.l1d.bytes, 48 * 1024, "L1d size");
assert_eq!(t.l1d.assoc, 12, "L1d ways");
assert_eq!(t.l1d.line, 64, "L1d line");
assert_eq!(t.l1d.shared_by, 1, "L1d shared_by is fixed at 1");
assert_eq!(t.l2.bytes, 1024 * 1024, "L2 size");
assert_eq!(t.l2.assoc, 8, "L2 ways");
let l3 = t.l3.expect("L3 present");
assert_eq!(l3.bytes, 32 * 1024 * 1024, "L3 size");
assert_eq!(l3.assoc, 16, "L3 ways");
}
#[test]
fn detect_amd_legacy_exotic_associativities() {
let reader = |eax: u32, _ecx: u32| -> CpuIdResult {
match eax {
0x0 => CpuIdResult {
eax: 0x10,
ebx: 0x6874_7541, ecx: 0x444d_4163, edx: 0x6974_6e65, },
0x8000_0000 => CpuIdResult {
eax: 0x8000_0008,
ebx: 0,
ecx: 0,
edx: 0,
},
0x8000_0005 => CpuIdResult {
eax: 0,
ebx: 0,
ecx: (64 << 24) | (0x01 << 16) | 64,
edx: 0,
},
0x8000_0006 => CpuIdResult {
eax: 0,
ebx: 0,
ecx: (512 << 16) | (0xF << 12) | 64,
edx: (16 << 18) | (0xF << 12) | 64,
},
_ => CpuIdResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
},
}
};
let cpuid = CpuId::with_cpuid_fn(reader);
let t = super::detect_amd_legacy(&cpuid).expect("canned AMD leaves must detect");
assert_eq!(t.l1d.bytes, 64 * 1024, "L1d size");
assert_eq!(t.l1d.assoc, 1, "DirectMapped L1d folds to assoc 1");
assert_eq!(t.l2.bytes, 512 * 1024, "L2 size");
assert_eq!(t.l2.assoc, 64, "FullyAssociative L2 folds to assoc 64");
let l3 = t.l3.expect("L3 present");
assert_eq!(l3.bytes, 16 * 512 * 1024, "L3 size (units of 512 KiB)");
assert_eq!(l3.assoc, 64, "FullyAssociative L3 folds to assoc 64");
}
#[test]
fn detect_topology_leaf_from_canned_amd_leaf1d() {
let reader = |eax: u32, ecx: u32| -> CpuIdResult {
match (eax, ecx) {
(0x0, _) => CpuIdResult {
eax: 0x10,
ebx: 0x6874_7541, ecx: 0x444d_4163, edx: 0x6974_6e65, },
(0x8000_0000, _) => CpuIdResult {
eax: 0x8000_0020,
ebx: 0,
ecx: 0,
edx: 0,
},
(0x8000_0006, _) => CpuIdResult {
eax: 0,
ebx: 0,
ecx: (1024 << 16) | (0x6 << 12) | 64,
edx: (128 << 18) | (0x9 << 12) | 64,
},
(0x8000_001D, 0) => leaf04(1, 1, 64, 1, 12, 64), (0x8000_001D, 1) => leaf04(2, 1, 64, 1, 8, 64), (0x8000_001D, 2) => leaf04(3, 2, 64, 1, 16, 1024), (0x8000_001D, 3) => leaf04(3, 3, 64, 1, 16, 32768), _ => CpuIdResult {
eax: 0,
ebx: 0,
ecx: 0,
edx: 0,
},
}
};
let cpuid = CpuId::with_cpuid_fn(reader);
let t = super::detect_topology_leaf(&cpuid).expect("canned AMD leaf-1Dh must detect");
assert_eq!(t.l1d.bytes, 48 * 1024, "L1d size");
assert_eq!(t.l1d.assoc, 12, "L1d ways");
assert_eq!(t.l2.bytes, 1024 * 1024, "L2 size");
assert_eq!(t.l2.assoc, 16, "L2 ways");
let l3 = t.l3.expect("L3 present");
assert_eq!(
l3.bytes,
32 * 1024 * 1024,
"L3 is the per-CCD slice, not the total"
);
assert_eq!(l3.assoc, 16, "L3 ways read exactly, not guessed");
assert_eq!(l3.shared_by, 1, "L3 shared_by is fixed at 1");
}
}