Skip to main content

vm_topology/processor/
x86.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! X86-specific topology definitions.
5
6use 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/// X86-specific topology information.
25#[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
48/// X86-specific [`TopologyBuilder`] state.
49pub 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/// X2APIC configuration.
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum X2ApicState {
66    /// Support the X2APIC, and automatically enable it if needed to address all
67    /// processors.
68    Supported,
69    /// Do not support the X2APIC.
70    Unsupported,
71    /// Support and enable the X2APIC.
72    Enabled,
73}
74
75impl TopologyBuilder<X86Topology> {
76    /// Returns a builder for creating an x86 processor topology.
77    pub fn new_x86() -> Self {
78        Self {
79            vps_per_socket: 1,
80            smt_enabled: false,
81            arch: Default::default(),
82        }
83    }
84
85    /// Returns a builder initialized from host information (via CPUID).
86    ///
87    /// Note that this only queries SMT state and the socket size, it does not
88    /// otherwise affect APIC configuration.
89    #[cfg(target_arch = "x86_64")] // xtask-fmt allow-target-arch cpu-intrinsic
90    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    /// Returns a builder initialized from cpuid information.
100    ///
101    /// Note that this only queries SMT state and the socket size, it does not
102    /// otherwise affect APIC configuration.
103    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        // Try to get topology from leaf 0Bh.
115        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        // For AMD, try leaf 0x80000008 and 0x8000001e.
130        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        // Try to get topology from leaf 04h.
155        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    /// Sets the APIC ID offset. Each APIC ID will be offset by this value,
189    /// rounded up to the socket size.
190    pub fn apic_id_offset(&mut self, offset: u32) -> &mut Self {
191        self.arch.apic_id_offset = offset;
192        self
193    }
194
195    /// Sets the X2APIC configuration.
196    pub fn x2apic(&mut self, x2apic: X2ApicState) -> &mut Self {
197        self.arch.x2apic = x2apic;
198        self
199    }
200
201    /// Builds a processor topology with `proc_count` processors.
202    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            // Default vnode assignment by socket. The caller can override this
211            // with `ProcessorTopology::set_vnodes()` for NUMA configurations.
212            let vnode = n / self.vps_per_socket;
213            let socket = socket_offset + n / self.vps_per_socket;
214            let proc = n % self.vps_per_socket;
215            let apic_id = socket * vps_per_socket + proc;
216            X86VpInfo {
217                base: VpInfo { vp_index, vnode },
218                apic_id,
219            }
220        });
221
222        self.build_with_vp_info(vps)
223    }
224
225    /// Builds a processor topology with processors with the specified information.
226    pub fn build_with_vp_info(
227        &self,
228        vps: impl IntoIterator<Item = X86VpInfo>,
229    ) -> Result<ProcessorTopology<X86Topology>, InvalidTopology> {
230        let vps = Vec::from_iter(vps);
231
232        if vps
233            .iter()
234            .enumerate()
235            .any(|(i, vp)| i != vp.base.vp_index.index() as usize)
236        {
237            return Err(InvalidTopology::InvalidVpIndices);
238        }
239
240        let max_apic_id = vps
241            .iter()
242            .map(|x| x.apic_id)
243            .max()
244            .ok_or(InvalidTopology::NoVps)?;
245        let apic_mode = match self.arch.x2apic {
246            X2ApicState::Supported => {
247                if max_apic_id >= APIC_LEGACY_ID_COUNT {
248                    ApicMode::X2ApicEnabled
249                } else {
250                    ApicMode::X2ApicSupported
251                }
252            }
253            X2ApicState::Unsupported => {
254                if max_apic_id >= APIC_LEGACY_ID_COUNT {
255                    return Err(InvalidTopology::ApicIdLimitExceeded(max_apic_id));
256                }
257                ApicMode::XApic
258            }
259            X2ApicState::Enabled => ApicMode::X2ApicEnabled,
260        };
261        Ok(ProcessorTopology {
262            vps,
263            smt_enabled: self.smt_enabled && self.vps_per_socket > 1,
264            vps_per_socket: self.vps_per_socket,
265            arch: X86Topology { apic_mode },
266        })
267    }
268}
269
270impl ProcessorTopology<X86Topology> {
271    /// Returns the largest APIC ID in use.
272    pub fn max_apic_id(&self) -> u32 {
273        self.vps.iter().map(|x| x.apic_id).max().unwrap_or(0)
274    }
275
276    /// Returns the APIC mode configured for the processors.
277    pub fn apic_mode(&self) -> ApicMode {
278        self.arch.apic_mode
279    }
280}
281
282/// x86-specific VP info.
283#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
284#[derive(Debug, Copy, Clone)]
285pub struct X86VpInfo {
286    /// The base info.
287    #[cfg_attr(feature = "inspect", inspect(flatten))]
288    pub base: VpInfo,
289    /// The APIC ID of the processor.
290    pub apic_id: u32,
291}
292
293impl AsRef<VpInfo> for X86VpInfo {
294    fn as_ref(&self) -> &VpInfo {
295        &self.base
296    }
297}
298
299impl AsMut<VpInfo> for X86VpInfo {
300    fn as_mut(&mut self) -> &mut VpInfo {
301        &mut self.base
302    }
303}
304
305/// The APIC mode for the virtual processors.
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
308pub enum ApicMode {
309    /// xAPIC mode.
310    #[cfg_attr(feature = "inspect", inspect(rename = "xapic"))]
311    XApic,
312    /// x2APIC mode supported but disabled at boot.
313    #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_supported"))]
314    X2ApicSupported,
315    /// x2APIC mode supported and enabled at boot.
316    #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_enabled"))]
317    X2ApicEnabled,
318}