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            // FUTURE: support multiple NUMA nodes per socket.
211            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    /// Builds a processor topology with processors with the specified information.
225    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    /// Returns the largest APIC ID in use.
271    pub fn max_apic_id(&self) -> u32 {
272        self.vps.iter().map(|x| x.apic_id).max().unwrap_or(0)
273    }
274
275    /// Returns the APIC mode configured for the processors.
276    pub fn apic_mode(&self) -> ApicMode {
277        self.arch.apic_mode
278    }
279}
280
281/// x86-specific VP info.
282#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
283#[derive(Debug, Copy, Clone)]
284pub struct X86VpInfo {
285    /// The base info.
286    #[cfg_attr(feature = "inspect", inspect(flatten))]
287    pub base: VpInfo,
288    /// The APIC ID of the processor.
289    pub apic_id: u32,
290}
291
292impl AsRef<VpInfo> for X86VpInfo {
293    fn as_ref(&self) -> &VpInfo {
294        &self.base
295    }
296}
297
298/// The APIC mode for the virtual processors.
299#[derive(Debug, Copy, Clone, PartialEq, Eq)]
300#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
301pub enum ApicMode {
302    /// xAPIC mode.
303    #[cfg_attr(feature = "inspect", inspect(rename = "xapic"))]
304    XApic,
305    /// x2APIC mode supported but disabled at boot.
306    #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_supported"))]
307    X2ApicSupported,
308    /// x2APIC mode supported and enabled at boot.
309    #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_enabled"))]
310    X2ApicEnabled,
311}