1use regio::x86::{Cpuid, CpuidValue, EAX};
6
7use super::cpuid::{
8 AMD_CACHE_TOPOLOGY_A, AMD_L1_DATA_CACHE_INFO, AMD_L1_INSTRUCTION_CACHE_INFO, AMD_L2_CACHE_INFO,
9 AMD_L3_CACHE_INFO, CacheTopologyA, CacheTopologyB, CacheTopologyC, CacheType,
10 INTEL_CACHE_TOPOLOGY_A,
11};
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct CpuCacheLevelInfo {
16 pub level: usize,
17 pub cache_type: CacheType,
18
19 pub size_kb: usize,
23
24 pub number_of_sets: usize,
29
30 pub ways_of_associativity: usize,
32
33 pub fully_associative: Option<bool>,
35
36 pub share_id_shift: Option<usize>,
40}
41
42impl CpuCacheLevelInfo {
43 const NULL: Self = Self {
44 level: 0,
45 cache_type: CacheType::Null,
46 size_kb: 0,
47 number_of_sets: 0,
48 ways_of_associativity: 0,
49 fully_associative: None,
50 share_id_shift: None,
51 };
52}
53
54impl Default for CpuCacheLevelInfo {
55 fn default() -> Self {
56 Self::NULL
57 }
58}
59
60const fn ceil_log2(n: usize) -> usize {
61 n.next_power_of_two().trailing_zeros() as usize
62}
63
64#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct CpuCacheInfo {
67 caches: [CpuCacheLevelInfo; Self::MAX_NUM_CACHES],
68 size: usize,
69}
70
71impl CpuCacheInfo {
72 pub const MAX_NUM_CACHES: usize = 5;
74
75 pub fn from_cpuid(cpuid: impl Cpuid) -> Option<Self> {
76 if let Some(info) = Self::from_v2_topology(&cpuid, INTEL_CACHE_TOPOLOGY_A) {
81 return Some(info);
82 }
83 if let Some(info) = Self::from_v2_topology(&cpuid, AMD_CACHE_TOPOLOGY_A) {
84 return Some(info);
85 }
86
87 Self::from_v1_amd_topology(&cpuid)
88 }
89
90 pub fn as_slice(&self) -> &[CpuCacheLevelInfo] {
91 &self.caches[..self.size]
92 }
93
94 pub fn last_level(&self) -> &CpuCacheLevelInfo {
96 self.as_slice().last().unwrap()
98 }
99
100 fn from_v2_topology<const LEAF: u32>(
101 cpuid: &impl Cpuid,
102 topology_0th: CpuidValue<LEAF, 0, EAX, CacheTopologyA>,
103 ) -> Option<Self> {
104 if !cpuid.supports(topology_0th) {
105 return None;
106 }
107
108 let mut caches = [CpuCacheLevelInfo::NULL; Self::MAX_NUM_CACHES];
109 let mut size = 0;
110 for subleaf in 0..Self::MAX_NUM_CACHES {
111 let raw = cpuid.read_raw(LEAF, subleaf as u32);
112 let eax = CacheTopologyA::from(raw.eax);
113 if eax.cache_type() == CacheType::Null {
114 break;
115 }
116 let ebx = CacheTopologyB::from(raw.ebx);
117 let ecx = CacheTopologyC::from(raw.ecx);
118
119 let size_bytes = (ebx.ways() as usize + 1)
120 * (ebx.physical_line_partitions() as usize + 1)
121 * (ebx.system_coherency_line_size() as usize + 1)
122 * (ecx.sets() as usize + 1);
123
124 caches[size] = CpuCacheLevelInfo {
125 level: eax.cache_level() as usize,
126 cache_type: eax.cache_type(),
127 size_kb: size_bytes / 1024,
128 number_of_sets: ecx.sets() as usize + 1,
129 ways_of_associativity: ebx.ways() as usize + 1,
130 fully_associative: Some(eax.fully_associative()),
131 share_id_shift: Some(ceil_log2(eax.max_sharing_logical_processors() as usize + 1)),
132 };
133 size += 1;
134 }
135
136 if size >= 3 { Some(Self { caches, size }) } else { None }
138 }
139
140 fn from_v1_amd_topology(cpuid: &impl Cpuid) -> Option<Self> {
141 if !cpuid.supports(AMD_L3_CACHE_INFO) {
145 return None;
146 }
147
148 let l1d = cpuid.read(AMD_L1_DATA_CACHE_INFO);
149 let l1i = cpuid.read(AMD_L1_INSTRUCTION_CACHE_INFO);
150 let l2 = cpuid.read(AMD_L2_CACHE_INFO);
151 let l3 = cpuid.read(AMD_L3_CACHE_INFO);
152
153 let mut caches = [CpuCacheLevelInfo::NULL; Self::MAX_NUM_CACHES];
154 caches[0] = CpuCacheLevelInfo {
155 level: 1,
156 cache_type: CacheType::Data,
157 size_kb: l1d.size_kb() as usize,
158 number_of_sets: 0,
159 ways_of_associativity: l1d.ways_of_associativity(),
160 fully_associative: l1d.fully_associative(),
161 share_id_shift: None,
162 };
163 caches[1] = CpuCacheLevelInfo {
164 level: 1,
165 cache_type: CacheType::Instruction,
166 size_kb: l1i.size_kb() as usize,
167 number_of_sets: 0,
168 ways_of_associativity: l1i.ways_of_associativity(),
169 fully_associative: l1i.fully_associative(),
170 share_id_shift: None,
171 };
172 caches[2] = CpuCacheLevelInfo {
173 level: 2,
174 cache_type: CacheType::Unified,
175 size_kb: l2.size_kb() as usize,
176 number_of_sets: 0,
177 ways_of_associativity: l2.ways_of_associativity(),
178 fully_associative: l2.fully_associative(),
179 share_id_shift: None,
180 };
181 let mut size = 3;
182
183 if l3.size() != 0 {
184 caches[3] = CpuCacheLevelInfo {
192 level: 3,
193 cache_type: CacheType::Unified,
194 size_kb: 512 * l3.size() as usize,
195 number_of_sets: 0,
196 ways_of_associativity: l3.ways_of_associativity(),
197 fully_associative: l3.fully_associative(),
198 share_id_shift: None,
199 };
200 size = 4;
201 }
202
203 Some(Self { caches, size })
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::x86::cpuid::{
211 AmdL1CacheInformation, AmdL2CacheInformation, AmdL2L3Associativity, AmdL3CacheInformation,
212 MAX_LEAF,
213 };
214 use regio::testing::x86::FakeCpuid;
215 use regio::x86::{EBX, ECX};
216
217 #[test]
218 fn intel_v2_topology() {
219 let mut cpuid = FakeCpuid::new();
220 cpuid.set(MAX_LEAF, 4);
221
222 let l1d_a = *CacheTopologyA::new()
225 .set_cache_level(1)
226 .set_cache_type(CacheType::Data)
227 .set_max_sharing_logical_processors(1) .set_fully_associative(false);
229 let l1d_b = *CacheTopologyB::new()
230 .set_ways(7)
231 .set_physical_line_partitions(0)
232 .set_system_coherency_line_size(63);
233 let l1d_c = *CacheTopologyC::new().set_sets(63);
234
235 let l1i_a = *CacheTopologyA::new()
237 .set_cache_level(1)
238 .set_cache_type(CacheType::Instruction)
239 .set_max_sharing_logical_processors(1)
240 .set_fully_associative(false);
241 let l1i_b = *CacheTopologyB::new()
242 .set_ways(7)
243 .set_physical_line_partitions(0)
244 .set_system_coherency_line_size(63);
245 let l1i_c = *CacheTopologyC::new().set_sets(63);
246
247 let l2_a = *CacheTopologyA::new()
250 .set_cache_level(2)
251 .set_cache_type(CacheType::Unified)
252 .set_max_sharing_logical_processors(1)
253 .set_fully_associative(false);
254 let l2_b = *CacheTopologyB::new()
255 .set_ways(7)
256 .set_physical_line_partitions(0)
257 .set_system_coherency_line_size(63);
258 let l2_c = *CacheTopologyC::new().set_sets(511);
259
260 let l3_a = *CacheTopologyA::new().set_cache_type(CacheType::Null);
262
263 cpuid
264 .set(CpuidValue::<4, 0, EAX, CacheTopologyA>::new(), l1d_a)
265 .set(CpuidValue::<4, 0, EBX, CacheTopologyB>::new(), l1d_b)
266 .set(CpuidValue::<4, 0, ECX, CacheTopologyC>::new(), l1d_c)
267 .set(CpuidValue::<4, 1, EAX, CacheTopologyA>::new(), l1i_a)
268 .set(CpuidValue::<4, 1, EBX, CacheTopologyB>::new(), l1i_b)
269 .set(CpuidValue::<4, 1, ECX, CacheTopologyC>::new(), l1i_c)
270 .set(CpuidValue::<4, 2, EAX, CacheTopologyA>::new(), l2_a)
271 .set(CpuidValue::<4, 2, EBX, CacheTopologyB>::new(), l2_b)
272 .set(CpuidValue::<4, 2, ECX, CacheTopologyC>::new(), l2_c)
273 .set(CpuidValue::<4, 3, EAX, CacheTopologyA>::new(), l3_a);
274
275 let info = CpuCacheInfo::from_cpuid(&cpuid).unwrap();
276 assert_eq!(
277 info.as_slice(),
278 &[
279 CpuCacheLevelInfo {
280 level: 1,
281 cache_type: CacheType::Data,
282 size_kb: 32,
283 number_of_sets: 64,
284 ways_of_associativity: 8,
285 fully_associative: Some(false),
286 share_id_shift: Some(1),
287 },
288 CpuCacheLevelInfo {
289 level: 1,
290 cache_type: CacheType::Instruction,
291 size_kb: 32,
292 number_of_sets: 64,
293 ways_of_associativity: 8,
294 fully_associative: Some(false),
295 share_id_shift: Some(1),
296 },
297 CpuCacheLevelInfo {
298 level: 2,
299 cache_type: CacheType::Unified,
300 size_kb: 256,
301 number_of_sets: 512,
302 ways_of_associativity: 8,
303 fully_associative: Some(false),
304 share_id_shift: Some(1),
305 },
306 ]
307 );
308 assert_eq!(info.last_level(), &info.as_slice()[2]);
309 }
310
311 #[test]
312 fn amd_v1_topology() {
313 let mut cpuid = FakeCpuid::new();
314 cpuid.set(CpuidValue::<0x8000_0000, 0, EAX, u32>::new(), 0x8000_0006);
316
317 let l1d = *AmdL1CacheInformation::new().set_size_kb(32).set_assoc(8);
319 let l1i = *AmdL1CacheInformation::new().set_size_kb(64).set_assoc(2);
321 let l2 =
323 *AmdL2CacheInformation::new().set_size_kb(512).set_assoc(AmdL2L3Associativity::Ways8);
324 let l3 = *AmdL3CacheInformation::new().set_size(8).set_assoc(AmdL2L3Associativity::Ways16);
326
327 cpuid
328 .set(AMD_L1_DATA_CACHE_INFO, l1d)
329 .set(AMD_L1_INSTRUCTION_CACHE_INFO, l1i)
330 .set(AMD_L2_CACHE_INFO, l2)
331 .set(AMD_L3_CACHE_INFO, l3);
332
333 let info = CpuCacheInfo::from_cpuid(&cpuid).unwrap();
334 assert_eq!(
335 info.as_slice(),
336 &[
337 CpuCacheLevelInfo {
338 level: 1,
339 cache_type: CacheType::Data,
340 size_kb: 32,
341 number_of_sets: 0,
342 ways_of_associativity: 8,
343 fully_associative: Some(false),
344 share_id_shift: None,
345 },
346 CpuCacheLevelInfo {
347 level: 1,
348 cache_type: CacheType::Instruction,
349 size_kb: 64,
350 number_of_sets: 0,
351 ways_of_associativity: 2,
352 fully_associative: Some(false),
353 share_id_shift: None,
354 },
355 CpuCacheLevelInfo {
356 level: 2,
357 cache_type: CacheType::Unified,
358 size_kb: 512,
359 number_of_sets: 0,
360 ways_of_associativity: 8,
361 fully_associative: Some(false),
362 share_id_shift: None,
363 },
364 CpuCacheLevelInfo {
365 level: 3,
366 cache_type: CacheType::Unified,
367 size_kb: 4096,
368 number_of_sets: 0,
369 ways_of_associativity: 16,
370 fully_associative: Some(false),
371 share_id_shift: None,
372 },
373 ]
374 );
375 assert_eq!(info.last_level(), &info.as_slice()[3]);
376 }
377}