Skip to main content

libarch/x86/
cache.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// Represents a single cache.
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct CpuCacheLevelInfo {
16    pub level: usize,
17    pub cache_type: CacheType,
18
19    /// The size, in KiB, of the cache available to each processor. In the case of
20    /// the last-level cache, however, this field might report the aggregate size
21    /// of all such caches on the package.
22    pub size_kb: usize,
23
24    /// The number of sets in the cache available to each processor. In the case
25    /// of the last-level cache, however, this field might report the aggregate
26    /// number of sets across all such caches in the package.
27    /// Indeterminate if zero.
28    pub number_of_sets: usize,
29
30    /// Indeterminate if zero.
31    pub ways_of_associativity: usize,
32
33    /// Indeterminate if None.
34    pub fully_associative: Option<bool>,
35
36    /// The number of bits to shift an APIC ID to get the associated "share ID":
37    /// processors with coinciding share IDs share this cache. If None, then it
38    /// is indeterminate what the cache's shift is.
39    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/// Gives information on the set of caches in a package.
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct CpuCacheInfo {
67    caches: [CpuCacheLevelInfo; Self::MAX_NUM_CACHES],
68    size: usize,
69}
70
71impl CpuCacheInfo {
72    /// A split L1 and unified L2, L3, L4 caches makes five.
73    pub const MAX_NUM_CACHES: usize = 5;
74
75    pub fn from_cpuid(cpuid: impl Cpuid) -> Option<Self> {
76        // We first try the Intel v2 leaves - and then the AMD v2 leaves.
77        // Hypervisors on AMD hosts might lay CPUID values in the Intel style - and
78        // there is no harm in doing this in general as AMD hardware will tend to
79        // reserve these Intel leaves as zero.
80        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    /// Returns information on the last-level cache.
95    pub fn last_level(&self) -> &CpuCacheLevelInfo {
96        // Construction guarantees size >= 3.
97        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        // We expect at least split L1 caches and an L2 cache.
137        if size >= 3 { Some(Self { caches, size }) } else { None }
138    }
139
140    fn from_v1_amd_topology(cpuid: &impl Cpuid) -> Option<Self> {
141        // The extended leaves explicitly enumerate information about L1d, L1i, L2,
142        // and L3, which was the original means of figuring out cache topology on
143        // AMD.
144        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            // [amd/vol3]: E.4.5  Function 8000_0006h—L2 Cache and TLB and L3 Cache Information.
185            //
186            // `l3.size()` actually provides bounds for the total size of L3
187            // cache across the package, in terms of 512 KiB blocks:
188            // l3.size() * 512 ≤ total size KiB < (l3.size() + 1) * 512
189            // In practice, the total size is a multiple of 512 and this
190            // reports the actual total size.
191            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        // Subleaf 0: L1 Data cache.
223        // ways = 7 (8-way), physical_line_partitions = 0 (1), system_coherency_line_size = 63 (64 bytes), sets = 63 (64 sets) -> 32 KiB
224        let l1d_a = *CacheTopologyA::new()
225            .set_cache_level(1)
226            .set_cache_type(CacheType::Data)
227            .set_max_sharing_logical_processors(1) // 2 processors sharing -> share_id_shift = 1
228            .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        // Subleaf 1: L1 Instruction cache.
236        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        // Subleaf 2: L2 Unified cache.
248        // ways = 7 (8-way), physical_line_partitions = 0 (1), system_coherency_line_size = 63 (64 bytes), sets = 511 (512 sets) -> 256 KiB
249        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        // Subleaf 3: Null cache (ends enumeration).
261        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        // Extended max leaf >= 0x8000_0006.
315        cpuid.set(CpuidValue::<0x8000_0000, 0, EAX, u32>::new(), 0x8000_0006);
316
317        // L1 Data: 32 KiB, 8-way.
318        let l1d = *AmdL1CacheInformation::new().set_size_kb(32).set_assoc(8);
319        // L1 Inst: 64 KiB, 2-way.
320        let l1i = *AmdL1CacheInformation::new().set_size_kb(64).set_assoc(2);
321        // L2: 512 KiB, 8-way.
322        let l2 =
323            *AmdL2CacheInformation::new().set_size_kb(512).set_assoc(AmdL2L3Associativity::Ways8);
324        // L3: 8 * 512 = 4096 KiB, 16-way.
325        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}