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 #[cfg(target_arch = "x86_64")] pub fn from_host_topology() -> Result<Self, HostTopologyError> {
91 fn cpuid(leaf: u32, sub_leaf: u32) -> [u32; 4] {
92 let result = safe_intrinsics::cpuid(leaf, sub_leaf);
93 [result.eax, result.ebx, result.ecx, result.edx]
94 }
95
96 Self::from_cpuid(&mut cpuid)
97 }
98
99 pub fn from_cpuid(
104 cpuid: &mut dyn FnMut(u32, u32) -> [u32; 4],
105 ) -> Result<Self, HostTopologyError> {
106 let mut threads_per_core = 1u32;
107 let mut vps_per_socket = 1u32;
108 let mut found_topology = false;
109
110 let [max_function, vendor_ebx, vendor_ecx, vendor_edx] =
111 cpuid(CpuidFunction::VendorAndMaxFunction.0, 0);
112 let vendor = Vendor::from_ebx_ecx_edx(vendor_ebx, vendor_ecx, vendor_edx);
113
114 if max_function >= CpuidFunction::ExtendedTopologyEnumeration.0 {
116 for i in 0..=255 {
117 let [eax, _ebx, ecx, _edx] = cpuid(CpuidFunction::ExtendedTopologyEnumeration.0, i);
118 let shift = ExtendedTopologyEax::from(eax).x2_apic_shift();
119 match TopologyLevelType(ExtendedTopologyEcx::from(ecx).level_type()) {
120 TopologyLevelType::INVALID => break,
121 TopologyLevelType::SMT => threads_per_core = 1 << shift,
122 TopologyLevelType::CORE => vps_per_socket = 1 << shift,
123 _ => {}
124 }
125 found_topology = true;
126 }
127 }
128
129 if !found_topology && vendor.is_amd_compatible() {
131 let extended_max_function = cpuid(CpuidFunction::ExtendedMaxFunction.0, 0)[0];
132 if extended_max_function >= CpuidFunction::ExtendedAddressSpaceSizes.0 {
133 let [_eax, _ebx, ecx, _edx] = cpuid(CpuidFunction::ExtendedAddressSpaceSizes.0, 0);
134 let ecx = ExtendedAddressSpaceSizesEcx::from(ecx);
135
136 vps_per_socket = if ecx.apic_core_id_size() != 0 {
137 1 << ecx.apic_core_id_size()
138 } else {
139 ecx.nc() as u32 + 1
140 };
141
142 if extended_max_function >= CpuidFunction::ProcessorTopologyDefinition.0 {
143 let [_eax, ebx, _ecx, _edx] =
144 cpuid(CpuidFunction::ProcessorTopologyDefinition.0, 0);
145 let ebx = ProcessorTopologyDefinitionEbx::from(ebx);
146 threads_per_core = ebx.threads_per_compute_unit().max(1).into();
147 vps_per_socket /= threads_per_core;
148 }
149
150 found_topology = true;
151 }
152 }
153
154 if !found_topology {
156 for i in 0..=255 {
157 let [eax, _ebx, _ecx, _edx] = cpuid(CpuidFunction::CacheParameters.0, i);
158 if eax == 0 {
159 break;
160 }
161 let eax = CacheParametersEax::from(eax);
162 if eax.cache_level() == 1 {
163 found_topology = true;
164 threads_per_core = eax.threads_sharing_cache_minus_one() + 1;
165 vps_per_socket = (eax.cores_per_socket_minus_one() + 1) * threads_per_core;
166 break;
167 }
168 }
169 }
170
171 if !found_topology {
172 return Err(HostTopologyError::NotFound);
173 }
174
175 if threads_per_core > 2 {
176 return Err(HostTopologyError::UnsupportedThreadsPerCore(
177 threads_per_core,
178 ));
179 }
180
181 Ok(Self {
182 smt_enabled: threads_per_core > 1 && vps_per_socket > 1,
183 vps_per_socket,
184 arch: Default::default(),
185 })
186 }
187
188 pub fn apic_id_offset(&mut self, offset: u32) -> &mut Self {
191 self.arch.apic_id_offset = offset;
192 self
193 }
194
195 pub fn x2apic(&mut self, x2apic: X2ApicState) -> &mut Self {
197 self.arch.x2apic = x2apic;
198 self
199 }
200
201 pub fn build(
203 &self,
204 proc_count: u32,
205 ) -> Result<ProcessorTopology<X86Topology>, InvalidTopology> {
206 let vps_per_socket = self.vps_per_socket.next_power_of_two();
207 let socket_offset = self.arch.apic_id_offset / vps_per_socket;
208 let vps = (0..proc_count).map(|n| {
209 let vp_index = VpIndex::new(n);
210 let vnode = n / vps_per_socket;
212 let socket = socket_offset + n / self.vps_per_socket;
213 let proc = n % self.vps_per_socket;
214 let apic_id = socket * vps_per_socket + proc;
215 X86VpInfo {
216 base: VpInfo { vp_index, vnode },
217 apic_id,
218 }
219 });
220
221 self.build_with_vp_info(vps)
222 }
223
224 pub fn build_with_vp_info(
226 &self,
227 vps: impl IntoIterator<Item = X86VpInfo>,
228 ) -> Result<ProcessorTopology<X86Topology>, InvalidTopology> {
229 let vps = Vec::from_iter(vps);
230
231 if vps
232 .iter()
233 .enumerate()
234 .any(|(i, vp)| i != vp.base.vp_index.index() as usize)
235 {
236 return Err(InvalidTopology::InvalidVpIndices);
237 }
238
239 let max_apic_id = vps
240 .iter()
241 .map(|x| x.apic_id)
242 .max()
243 .ok_or(InvalidTopology::NoVps)?;
244 let apic_mode = match self.arch.x2apic {
245 X2ApicState::Supported => {
246 if max_apic_id >= APIC_LEGACY_ID_COUNT {
247 ApicMode::X2ApicEnabled
248 } else {
249 ApicMode::X2ApicSupported
250 }
251 }
252 X2ApicState::Unsupported => {
253 if max_apic_id >= APIC_LEGACY_ID_COUNT {
254 return Err(InvalidTopology::ApicIdLimitExceeded(max_apic_id));
255 }
256 ApicMode::XApic
257 }
258 X2ApicState::Enabled => ApicMode::X2ApicEnabled,
259 };
260 Ok(ProcessorTopology {
261 vps,
262 smt_enabled: self.smt_enabled && self.vps_per_socket > 1,
263 vps_per_socket: self.vps_per_socket,
264 arch: X86Topology { apic_mode },
265 })
266 }
267}
268
269impl ProcessorTopology<X86Topology> {
270 pub fn max_apic_id(&self) -> u32 {
272 self.vps.iter().map(|x| x.apic_id).max().unwrap_or(0)
273 }
274
275 pub fn apic_mode(&self) -> ApicMode {
277 self.arch.apic_mode
278 }
279}
280
281#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
283#[derive(Debug, Copy, Clone)]
284pub struct X86VpInfo {
285 #[cfg_attr(feature = "inspect", inspect(flatten))]
287 pub base: VpInfo,
288 pub apic_id: u32,
290}
291
292impl AsRef<VpInfo> for X86VpInfo {
293 fn as_ref(&self) -> &VpInfo {
294 &self.base
295 }
296}
297
298#[derive(Debug, Copy, Clone, PartialEq, Eq)]
300#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
301pub enum ApicMode {
302 #[cfg_attr(feature = "inspect", inspect(rename = "xapic"))]
304 XApic,
305 #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_supported"))]
307 X2ApicSupported,
308 #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_enabled"))]
310 X2ApicEnabled,
311}