Skip to main content

libarch/x86/
apic_id.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    COMPUTE_UNIT_INFO, CacheType, EXTENDED_APIC_ID, EXTENDED_SIZE_INFO, FEATURE_FLAGS_D,
9    INTEL_CACHE_TOPOLOGY_A, NODE_INFO, PROCESSOR_INFO, TopologyEnumerationA, TopologyEnumerationC,
10    TopologyLevelType, V1_TOPOLOGY_A, V1_TOPOLOGY_C, V1_TOPOLOGY_D, V2_TOPOLOGY_A, V2_TOPOLOGY_C,
11    V2_TOPOLOGY_D,
12};
13
14/// Returns the APIC ID - x2APIC if supported - associated with the logical
15/// processor in turn associated with the provided [`Cpuid`].
16pub fn get_apic_id(cpuid: impl Cpuid) -> u32 {
17    // [intel/vol3]: 8.9.2  Hierarchical Mapping of CPUID Extended Topology Leaf.
18    //
19    // For extended topology enumeration, if the first level does not encode the
20    // "SMT" level (a spec'ed expectation), then we assume the associated leaves
21    // to be invalid.
22    if cpuid.supports(V2_TOPOLOGY_A)
23        && cpuid.read(V2_TOPOLOGY_C).level_type() == TopologyLevelType::Smt
24    {
25        return cpuid.read(V2_TOPOLOGY_D).x2apic_id();
26    }
27    if cpuid.supports(V1_TOPOLOGY_A)
28        && cpuid.read(V1_TOPOLOGY_C).level_type() == TopologyLevelType::Smt
29    {
30        return cpuid.read(V1_TOPOLOGY_D).x2apic_id();
31    }
32
33    if cpuid.supports(EXTENDED_APIC_ID) {
34        return cpuid.read(EXTENDED_APIC_ID).x2apic_id();
35    }
36
37    cpuid.read(PROCESSOR_INFO).initial_apic_id() as u32
38}
39
40/// [`ApicIdDecoder`] is a utility for extracting particular topological level
41/// IDs from an (x2)APIC ID.
42///
43/// In full generality, an APIC ID might decompose as follows:
44///
45/// [intel/vol3]: Figure 8-5.  Generalized Seven Level Interpretation of the APIC ID.
46/// -----------------------------------------------------------------------------
47/// | CLUSTER ID | PACKAGE ID | DIE ID | TILE ID | MODULE ID | CORE ID | SMT ID |
48/// -----------------------------------------------------------------------------
49///
50/// where the full ID width is 32-bit (if x2APIC) or 8-bit.
51///
52/// This, however, is higher fidelity than we are able to make use of. Since
53/// CLUSTER ID and PACKAGE_ID are not directly enumerable from CPUID, we elide
54/// the two IDs into a single PACKAGE ID, defined as the rest of the ID above
55/// DIE. Moreover, the system currently has no use for enumerating tiles and
56/// modules directly (which is also a practice that AMD does not do): we elide
57/// the TILE and MODULE IDs into DIE ID alone. Accordingly, [`ApicIdDecoder`]
58/// partitions up the APIC address space as
59/// ------------------------------------------
60/// | PACKAGE ID | DIE ID | CORE ID | SMT ID |
61/// ------------------------------------------
62#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
63pub struct ApicIdDecoder {
64    smt_id_width: usize,
65    // CORE ID width + SMT ID width.
66    core_id_cumulative_width: usize,
67    // DIE ID width + CORE ID width + SMT ID width.
68    die_id_cumulative_width: usize,
69}
70
71impl ApicIdDecoder {
72    const MAX_TOPOLOGY_LEVEL: usize = 5; // TopologyLevelType::Die as usize
73
74    /// Constructs a new [`ApicIdDecoder`] by querying the given [`Cpuid`].
75    pub fn from_cpuid(cpuid: impl Cpuid) -> Self {
76        let mut decoder = Self::default();
77
78        // [intel/vol3]: Example 8-21.  Support Routines for Identifying Package,
79        // Core and Logical Processors from 8-bit Initial APIC ID.
80        // [amd/vol3]: E.5.1  Legacy Method.
81        //
82        // When HTT ("Hyper-Threading Technology") is not advertised, the package
83        // contains a single logical processor. This is counter-intuitive, but
84        // Intel cores that do not actually have SMT available may still present
85        // HTT == 1; moreover, in the case of AMD, HTT means "either that there is
86        // more than one thread per core or more than one core per compute unit".
87        if !cpuid.read(FEATURE_FLAGS_D).htt() {
88            return decoder;
89        }
90
91        // First try the extended topology leaves, which may work with older AMD
92        // models. The "V2" leaf 0x1f is preferred - if available - to the "V1"
93        // leaf 0xb.
94        if decoder.try_extended_topology(&cpuid, V2_TOPOLOGY_A)
95            || decoder.try_extended_topology(&cpuid, V1_TOPOLOGY_A)
96        {
97            // The DIE level might not have been explicitly enumerated. If it does
98            // not seem so, redefine the cumulative die-and-below ID width to be the
99            // rounded binary order of the maximum number of addressable logical
100            // processors per package, which should always coincide in general.
101            if decoder.die_id_cumulative_width == decoder.core_id_cumulative_width {
102                decoder.die_id_cumulative_width =
103                    Self::ceil_log2(Self::max_num_logical_processors(&cpuid));
104            }
105            return decoder;
106        }
107
108        // Maximum per package, that is.
109        let max_logical_processors = Self::max_num_logical_processors(&cpuid);
110        let mut max_cores = 1;
111        let mut max_dies = 1;
112
113        // [intel/vol3]: Example 8-21.  Support Routines for Identifying
114        // Package, Core and Logical Processors from 8-bit Initial APIC ID.
115        if cpuid.supports(INTEL_CACHE_TOPOLOGY_A) {
116            let zeroth_cache_topology = cpuid.read(INTEL_CACHE_TOPOLOGY_A);
117            if zeroth_cache_topology.cache_type() != CacheType::Null {
118                // The field encodes one less than the real count.
119                max_cores = zeroth_cache_topology.max_cores() as usize + 1;
120                decoder.finalize(max_logical_processors, max_cores, max_dies);
121                return decoder;
122            }
123        }
124
125        // Unfortunately, the AMD spec does not give a general way of
126        // determining the maximum number of addressable cores and dies per
127        // package, respectively. If leaf 0x8000'001e is supported (which
128        // requires the topology extension feature to be advertised), then we
129        // can give best-effort guesses of these quantities based on the actual
130        // counts of dies per package and logical processors per core.
131        if cpuid.supports(COMPUTE_UNIT_INFO) {
132            // We translate "compute unit" and "node" here as core and die,
133            // respectively.
134            max_dies = cpuid.read(NODE_INFO).nodes_per_package() as usize + 1;
135            let threads_per_core =
136                cpuid.read(COMPUTE_UNIT_INFO).threads_per_compute_unit() as usize + 1;
137            max_cores = max_logical_processors / threads_per_core;
138        }
139        decoder.finalize(max_logical_processors, max_cores, max_dies);
140        decoder
141    }
142
143    /// Extracts the SMT ID from the provided APIC ID.
144    pub const fn smt_id(&self, apic_id: u32) -> u32 {
145        apic_id & Self::to_mask(self.smt_id_width)
146    }
147
148    /// Extracts the Core ID from the provided APIC ID.
149    pub const fn core_id(&self, apic_id: u32) -> u32 {
150        (apic_id & Self::to_mask(self.core_id_cumulative_width)) >> self.smt_id_width
151    }
152
153    /// Extracts the Die ID from the provided APIC ID.
154    pub const fn die_id(&self, apic_id: u32) -> u32 {
155        (apic_id & Self::to_mask(self.die_id_cumulative_width)) >> self.core_id_cumulative_width
156    }
157
158    /// Extracts the Package ID from the provided APIC ID.
159    pub const fn package_id(&self, apic_id: u32) -> u32 {
160        if self.die_id_cumulative_width >= 32 { 0 } else { apic_id >> self.die_id_cumulative_width }
161    }
162
163    /// Returns the bit width allocated to the SMT ID.
164    pub const fn smt_id_width(&self) -> usize {
165        self.smt_id_width
166    }
167
168    /// Returns the cumulative bit width allocated to Core and SMT IDs.
169    pub const fn core_id_cumulative_width(&self) -> usize {
170        self.core_id_cumulative_width
171    }
172
173    /// Returns the cumulative bit width allocated to Die, Core, and SMT IDs.
174    pub const fn die_id_cumulative_width(&self) -> usize {
175        self.die_id_cumulative_width
176    }
177
178    // [intel/vol3]: Example 8-18.  Support Routines for Identifying Package,
179    // Die, Core and Logical Processors from 32-bit x2APIC ID.
180    //
181    // Attempts to perform Intel's extended topology enumeration routine and
182    // returns whether the attempt was successful.
183    fn try_extended_topology<const LEAF: u32>(
184        &mut self,
185        cpuid: &impl Cpuid,
186        topology_0th: CpuidValue<LEAF, 0, EAX, TopologyEnumerationA>,
187    ) -> bool {
188        if !cpuid.supports(topology_0th) {
189            return false;
190        }
191
192        for i in 0..Self::MAX_TOPOLOGY_LEVEL {
193            let raw = cpuid.read_raw(LEAF, i as u32);
194            let eax = TopologyEnumerationA::from(raw.eax);
195            let ecx = TopologyEnumerationC::from(raw.ecx);
196
197            // The above reference explains that SMT is expected to be the first level.
198            let level_type = ecx.level_type();
199            if i == 0 && level_type != TopologyLevelType::Smt {
200                return false;
201            }
202            let shift = eax.next_level_apic_id_shift() as usize;
203            match level_type {
204                TopologyLevelType::Invalid => return true, // Signals the end of iteration.
205                TopologyLevelType::Smt => {
206                    self.smt_id_width = shift;
207                    self.core_id_cumulative_width = shift;
208                    self.die_id_cumulative_width = shift;
209                }
210                TopologyLevelType::Core => {
211                    self.core_id_cumulative_width = shift;
212                    self.die_id_cumulative_width = shift;
213                }
214                // See class documentation regarding the elision of MODULE and TILE.
215                TopologyLevelType::Module | TopologyLevelType::Tile | TopologyLevelType::Die => {
216                    self.die_id_cumulative_width = shift;
217                }
218            }
219        }
220
221        // Something went wrong; iteration should have finished in hitting on a
222        // TopologyLevelType::Invalid level.
223        false
224    }
225
226    fn finalize(&mut self, max_logical_processors: usize, max_cores: usize, max_dies: usize) {
227        if !(max_logical_processors >= max_cores && max_cores >= max_dies && max_dies > 0) {
228            return;
229        }
230
231        self.smt_id_width = Self::ceil_log2(max_logical_processors / max_cores);
232        self.core_id_cumulative_width = Self::ceil_log2(max_cores / max_dies) + self.smt_id_width;
233        self.die_id_cumulative_width = Self::ceil_log2(max_logical_processors);
234    }
235
236    // Returns the maximum addressable number of logical processors per package.
237    // Both Intel and AMD spec ways to determine this quantity.
238    fn max_num_logical_processors(cpuid: &impl Cpuid) -> usize {
239        // The Intel max.
240        let mut max = cpuid.read(PROCESSOR_INFO).max_logical_processors() as usize;
241
242        // The AMD max. For AMD hardware, the quantity above gives the actual count
243        // of logical processors instead of the maximum number of addressable ones.
244        if cpuid.supports(EXTENDED_SIZE_INFO) {
245            // [amd/vol3]: E.5.2  Extended Method.
246            let size_ids = cpuid.read(EXTENDED_SIZE_INFO);
247            let amd_max = if size_ids.apic_id_size() != 0 {
248                1usize << size_ids.apic_id_size()
249            } else {
250                size_ids.nc() as usize + 1
251            };
252            max = max.max(amd_max);
253        }
254        max
255    }
256
257    const fn ceil_log2(n: usize) -> usize {
258        n.next_power_of_two().trailing_zeros() as usize
259    }
260
261    const fn to_mask(width: usize) -> u32 {
262        if width >= 32 { !0 } else { !(!0u32 << width) }
263    }
264}