1use super::ArchTopology;
7use super::HostTopologyError;
8use super::InvalidTopology;
9use super::ProcessorTopology;
10use super::TopologyBuilder;
11use super::VpIndex;
12use super::VpInfo;
13use super::VpTopologyInfo;
14use x86defs::apic::APIC_LEGACY_ID_COUNT;
15use x86defs::cpuid::CacheParametersEax;
16use x86defs::cpuid::CpuidFunction;
17use x86defs::cpuid::ExtendedAddressSpaceSizesEcx;
18use x86defs::cpuid::ExtendedTopologyEax;
19use x86defs::cpuid::ExtendedTopologyEcx;
20use x86defs::cpuid::ProcessorTopologyDefinitionEbx;
21use x86defs::cpuid::TopologyLevelType;
22use x86defs::cpuid::Vendor;
23
24#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
26#[derive(Debug, Copy, Clone)]
27pub struct X86Topology {
28 apic_mode: ApicMode,
29}
30
31impl ArchTopology for X86Topology {
32 type ArchVpInfo = X86VpInfo;
33 type BuilderState = X86TopologyBuilderState;
34
35 fn vp_topology(topology: &ProcessorTopology<Self>, info: &Self::ArchVpInfo) -> VpTopologyInfo {
36 VpTopologyInfo {
37 socket: info.apic_id / topology.reserved_vps_per_socket(),
38 core: info.apic_id % topology.reserved_vps_per_socket(),
39 thread: if topology.smt_enabled() {
40 info.apic_id & 1
41 } else {
42 0
43 },
44 }
45 }
46}
47
48pub struct X86TopologyBuilderState {
50 apic_id_offset: u32,
51 x2apic: X2ApicState,
52}
53
54impl Default for X86TopologyBuilderState {
55 fn default() -> Self {
56 Self {
57 apic_id_offset: 0,
58 x2apic: X2ApicState::Supported,
59 }
60 }
61}
62
63#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum X2ApicState {
66 Supported,
69 Unsupported,
71 Enabled,
73}
74
75impl TopologyBuilder<X86Topology> {
76 pub fn new_x86() -> Self {
78 Self {
79 vps_per_socket: 1,
80 smt_enabled: false,
81 arch: Default::default(),
82 }
83 }
84
85 pub fn from_host_topology() -> Result<Self, HostTopologyError> {
90 fn cpuid(leaf: u32, sub_leaf: u32) -> [u32; 4] {
91 #[cfg(not(target_arch = "x86_64"))] {
93 let (_, _) = (leaf, sub_leaf);
94 unimplemented!("cannot invoke from_host_topology: host arch is not x86_64");
95 }
96 #[cfg(target_arch = "x86_64")] {
98 let result = safe_intrinsics::cpuid(leaf, sub_leaf);
99 [result.eax, result.ebx, result.ecx, result.edx]
100 }
101 }
102
103 Self::from_cpuid(&mut cpuid)
104 }
105
106 pub fn from_cpuid(
111 cpuid: &mut dyn FnMut(u32, u32) -> [u32; 4],
112 ) -> Result<Self, HostTopologyError> {
113 let mut threads_per_core = 1u32;
114 let mut vps_per_socket = 1u32;
115 let mut found_topology = false;
116
117 let [max_function, vendor_ebx, vendor_ecx, vendor_edx] =
118 cpuid(CpuidFunction::VendorAndMaxFunction.0, 0);
119 let vendor = Vendor::from_ebx_ecx_edx(vendor_ebx, vendor_ecx, vendor_edx);
120
121 if max_function >= CpuidFunction::ExtendedTopologyEnumeration.0 {
123 for i in 0..=255 {
124 let [eax, _ebx, ecx, _edx] = cpuid(CpuidFunction::ExtendedTopologyEnumeration.0, i);
125 let shift = ExtendedTopologyEax::from(eax).x2_apic_shift();
126 match TopologyLevelType(ExtendedTopologyEcx::from(ecx).level_type()) {
127 TopologyLevelType::INVALID => break,
128 TopologyLevelType::SMT => threads_per_core = 1 << shift,
129 TopologyLevelType::CORE => vps_per_socket = 1 << shift,
130 _ => {}
131 }
132 found_topology = true;
133 }
134 }
135
136 if !found_topology && vendor.is_amd_compatible() {
138 let extended_max_function = cpuid(CpuidFunction::ExtendedMaxFunction.0, 0)[0];
139 if extended_max_function >= CpuidFunction::ExtendedAddressSpaceSizes.0 {
140 let [_eax, _ebx, ecx, _edx] = cpuid(CpuidFunction::ExtendedAddressSpaceSizes.0, 0);
141 let ecx = ExtendedAddressSpaceSizesEcx::from(ecx);
142
143 vps_per_socket = if ecx.apic_core_id_size() != 0 {
144 1 << ecx.apic_core_id_size()
145 } else {
146 ecx.nc() as u32 + 1
147 };
148
149 if extended_max_function >= CpuidFunction::ProcessorTopologyDefinition.0 {
150 let [_eax, ebx, _ecx, _edx] =
151 cpuid(CpuidFunction::ProcessorTopologyDefinition.0, 0);
152 let ebx = ProcessorTopologyDefinitionEbx::from(ebx);
153 threads_per_core = ebx.threads_per_compute_unit().max(1).into();
154 vps_per_socket /= threads_per_core;
155 }
156
157 found_topology = true;
158 }
159 }
160
161 if !found_topology {
163 for i in 0..=255 {
164 let [eax, _ebx, _ecx, _edx] = cpuid(CpuidFunction::CacheParameters.0, i);
165 if eax == 0 {
166 break;
167 }
168 let eax = CacheParametersEax::from(eax);
169 if eax.cache_level() == 1 {
170 found_topology = true;
171 threads_per_core = eax.threads_sharing_cache_minus_one() + 1;
172 vps_per_socket = (eax.cores_per_socket_minus_one() + 1) * threads_per_core;
173 break;
174 }
175 }
176 }
177
178 if !found_topology {
179 return Err(HostTopologyError::NotFound);
180 }
181
182 if threads_per_core > 2 {
183 return Err(HostTopologyError::UnsupportedThreadsPerCore(
184 threads_per_core,
185 ));
186 }
187
188 Ok(Self {
189 smt_enabled: threads_per_core > 1 && vps_per_socket > 1,
190 vps_per_socket,
191 arch: Default::default(),
192 })
193 }
194
195 pub fn apic_id_offset(&mut self, offset: u32) -> &mut Self {
198 self.arch.apic_id_offset = offset;
199 self
200 }
201
202 pub fn x2apic(&mut self, x2apic: X2ApicState) -> &mut Self {
204 self.arch.x2apic = x2apic;
205 self
206 }
207
208 pub fn build(
210 &self,
211 proc_count: u32,
212 ) -> Result<ProcessorTopology<X86Topology>, InvalidTopology> {
213 let vps_per_socket = self.vps_per_socket.next_power_of_two();
214 let socket_offset = self.arch.apic_id_offset / vps_per_socket;
215 let vps = (0..proc_count).map(|n| {
216 let vp_index = VpIndex::new(n);
217 let vnode = n / vps_per_socket;
219 let socket = socket_offset + n / self.vps_per_socket;
220 let proc = n % self.vps_per_socket;
221 let apic_id = socket * vps_per_socket + proc;
222 X86VpInfo {
223 base: VpInfo { vp_index, vnode },
224 apic_id,
225 }
226 });
227
228 self.build_with_vp_info(vps)
229 }
230
231 pub fn build_with_vp_info(
233 &self,
234 vps: impl IntoIterator<Item = X86VpInfo>,
235 ) -> Result<ProcessorTopology<X86Topology>, InvalidTopology> {
236 let vps = Vec::from_iter(vps);
237
238 if vps
239 .iter()
240 .enumerate()
241 .any(|(i, vp)| i != vp.base.vp_index.index() as usize)
242 {
243 return Err(InvalidTopology::InvalidVpIndices);
244 }
245
246 let max_apic_id = vps
247 .iter()
248 .map(|x| x.apic_id)
249 .max()
250 .ok_or(InvalidTopology::NoVps)?;
251 let apic_mode = match self.arch.x2apic {
252 X2ApicState::Supported => {
253 if max_apic_id >= APIC_LEGACY_ID_COUNT {
254 ApicMode::X2ApicEnabled
255 } else {
256 ApicMode::X2ApicSupported
257 }
258 }
259 X2ApicState::Unsupported => {
260 if max_apic_id >= APIC_LEGACY_ID_COUNT {
261 return Err(InvalidTopology::ApicIdLimitExceeded(max_apic_id));
262 }
263 ApicMode::XApic
264 }
265 X2ApicState::Enabled => ApicMode::X2ApicEnabled,
266 };
267 Ok(ProcessorTopology {
268 vps,
269 smt_enabled: self.smt_enabled && self.vps_per_socket > 1,
270 vps_per_socket: self.vps_per_socket,
271 arch: X86Topology { apic_mode },
272 })
273 }
274}
275
276impl ProcessorTopology<X86Topology> {
277 pub fn max_apic_id(&self) -> u32 {
279 self.vps.iter().map(|x| x.apic_id).max().unwrap_or(0)
280 }
281
282 pub fn apic_mode(&self) -> ApicMode {
284 self.arch.apic_mode
285 }
286}
287
288#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
290#[derive(Debug, Copy, Clone)]
291pub struct X86VpInfo {
292 #[cfg_attr(feature = "inspect", inspect(flatten))]
294 pub base: VpInfo,
295 pub apic_id: u32,
297}
298
299impl AsRef<VpInfo> for X86VpInfo {
300 fn as_ref(&self) -> &VpInfo {
301 &self.base
302 }
303}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
308pub enum ApicMode {
309 #[cfg_attr(feature = "inspect", inspect(rename = "xapic"))]
311 XApic,
312 #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_supported"))]
314 X2ApicSupported,
315 #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_enabled"))]
317 X2ApicEnabled,
318}