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}