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    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"))] // xtask-fmt allow-target-arch cpu-intrinsic
92            {
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")] // xtask-fmt allow-target-arch cpu-intrinsic
97            {
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    /// Returns a builder initialized from cpuid information.
107    ///
108    /// Note that this only queries SMT state and the socket size, it does not
109    /// otherwise affect APIC configuration.
110    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        // Try to get topology from leaf 0Bh.
122        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        // For AMD, try leaf 0x80000008 and 0x8000001e.
137        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        // Try to get topology from leaf 04h.
162        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    /// Sets the APIC ID offset. Each APIC ID will be offset by this value,
196    /// rounded up to the socket size.
197    pub fn apic_id_offset(&mut self, offset: u32) -> &mut Self {
198        self.arch.apic_id_offset = offset;
199        self
200    }
201
202    /// Sets the X2APIC configuration.
203    pub fn x2apic(&mut self, x2apic: X2ApicState) -> &mut Self {
204        self.arch.x2apic = x2apic;
205        self
206    }
207
208    /// Builds a processor topology with `proc_count` processors.
209    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            // FUTURE: support multiple NUMA nodes per socket.
218            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    /// Builds a processor topology with processors with the specified information.
232    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    /// Returns the largest APIC ID in use.
278    pub fn max_apic_id(&self) -> u32 {
279        self.vps.iter().map(|x| x.apic_id).max().unwrap_or(0)
280    }
281
282    /// Returns the APIC mode configured for the processors.
283    pub fn apic_mode(&self) -> ApicMode {
284        self.arch.apic_mode
285    }
286}
287
288/// x86-specific VP info.
289#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
290#[derive(Debug, Copy, Clone)]
291pub struct X86VpInfo {
292    /// The base info.
293    #[cfg_attr(feature = "inspect", inspect(flatten))]
294    pub base: VpInfo,
295    /// The APIC ID of the processor.
296    pub apic_id: u32,
297}
298
299impl AsRef<VpInfo> for X86VpInfo {
300    fn as_ref(&self) -> &VpInfo {
301        &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}