Skip to main content

vm_topology/
processor.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Processor topology types.
5
6pub mod aarch64;
7pub mod x86;
8
9cfg_select! {
10    guest_arch = "aarch64" => {
11        pub use aarch64 as arch;
12        pub use aarch64::Aarch64Topology as TargetTopology;
13        pub use aarch64::Aarch64VpInfo as TargetVpInfo;
14    }
15    guest_arch = "x86_64" => {
16        pub use x86 as arch;
17        pub use x86::X86Topology as TargetTopology;
18        pub use x86::X86VpInfo as TargetVpInfo;
19    }
20    _ => {
21        compile_error!("Unsupported architecture");
22    }
23}
24use thiserror::Error;
25
26/// A description of the VM's processor topology.
27///
28/// Currently this just tracks the APIC IDs for the processors.
29///
30/// Build one with [`TopologyBuilder`].
31#[cfg_attr(
32    feature = "inspect",
33    derive(inspect::Inspect),
34    inspect(bound = "T: inspect::Inspect, T::ArchVpInfo: inspect::Inspect")
35)]
36#[derive(Debug, Clone)]
37pub struct ProcessorTopology<T: ArchTopology = TargetTopology> {
38    #[cfg_attr(feature = "inspect", inspect(iter_by_index))]
39    vps: Vec<T::ArchVpInfo>,
40    smt_enabled: bool,
41    vps_per_socket: u32,
42    arch: T,
43}
44
45/// Architecture-specific topology types.
46pub trait ArchTopology: Sized {
47    /// The architecture-specific VP info type.
48    type ArchVpInfo: Copy + AsRef<VpInfo>;
49
50    /// The architecture-specific [`TopologyBuilder`] generic.
51    type BuilderState;
52
53    /// Compute VP topology from a VP.
54    fn vp_topology(topology: &ProcessorTopology<Self>, info: &Self::ArchVpInfo) -> VpTopologyInfo;
55}
56
57/// A builder for [`ProcessorTopology`].
58#[derive(Debug)]
59pub struct TopologyBuilder<T: ArchTopology> {
60    vps_per_socket: u32,
61    smt_enabled: bool,
62    arch: T::BuilderState,
63}
64
65/// Error returned by [`TopologyBuilder::from_host_topology`].
66#[derive(Debug, Error)]
67pub enum HostTopologyError {
68    /// Could not find the host topology.
69    #[error("could not compute host topology via cpuid")]
70    NotFound,
71    /// The host topology has more than 2 threads per core.
72    #[error("unsupported thread-per-core count {0}")]
73    UnsupportedThreadsPerCore(u32),
74}
75
76/// Error when building a [`ProcessorTopology`].
77#[derive(Debug, Error)]
78pub enum InvalidTopology {
79    /// Failed to configure at least one VP.
80    #[error("must have at least one processor")]
81    NoVps,
82    /// Too many virtual processors.
83    #[error("too many processors requested: {requested}, max {max}")]
84    TooManyVps {
85        /// The number of processors requested.
86        requested: u32,
87        /// The maximum number of processors.
88        max: u32,
89    },
90    /// Not all processors will be addressable in XAPIC mode.
91    #[error("too many processors or too high an APIC ID {0} for xapic mode")]
92    ApicIdLimitExceeded(u32),
93    /// VpInfo indices must be linear and start at 0
94    #[error("vp indices don't start at 0 or don't count up")]
95    InvalidVpIndices,
96    /// A PPI INTID is not in the valid range (16..32).
97    #[error("PPI INTID {0} is not in the valid range 16..32")]
98    InvalidPpiIntid(u32),
99    /// The GIC interrupt count is invalid.
100    #[error("gic_nr_irqs {0} must be 64..=992 and a multiple of 32")]
101    InvalidGicNrIrqs(u32),
102    /// GICv2 supports at most 8 CPUs.
103    #[error("GICv2 supports at most 8 CPUs, but {0} were requested")]
104    TooManyCpusForGicV2(u32),
105    /// Failed to query the topology information from Device Tree.
106    #[error("failed to query memory topology from device tree")]
107    StdIoError(#[source] std::io::Error),
108}
109
110impl<T: ArchTopology> TopologyBuilder<T> {
111    /// Sets the number of VPs per socket.
112    ///
113    /// This does not need to be a power of 2, but it should be a multiple of 2
114    /// if SMT is enabled.
115    ///
116    /// The number of VPs per socket will be rounded up to a power of 2 for
117    /// purposes of defining the x2APIC ID.
118    pub fn vps_per_socket(&mut self, count: u32) -> &mut Self {
119        self.vps_per_socket = count.clamp(1, 32768);
120        self
121    }
122
123    /// Sets whether SMT (hyperthreading) is enabled.
124    ///
125    /// This is ignored if `vps_per_socket` is 1.
126    pub fn smt_enabled(&mut self, enabled: bool) -> &mut Self {
127        self.smt_enabled = enabled;
128        self
129    }
130}
131
132impl<T: ArchTopology> ProcessorTopology<T> {
133    /// Computes the socket, core, and thread coordinates of a VP from the
134    /// configured topology.
135    ///
136    /// This describes the regular topology the VM was asked for. It is
137    /// deliberately independent of any architectural CPU identity, since those
138    /// identities are free to encode an offset, reserved holes, or a packing
139    /// that carries no topology at all.
140    ///
141    /// The result is only meaningful if `vps_per_socket` and `smt_enabled`
142    /// actually describe the VPs in this topology. That holds by construction
143    /// for [`TopologyBuilder::build`], which generates VPs from those same
144    /// values, but [`TopologyBuilder::build_with_vp_info`] takes the VP list
145    /// from its caller and keeps the declared values unchecked. A caller that
146    /// supplies VPs laid out some other way gets coordinates describing the
147    /// layout it declared, not the one it passed in.
148    ///
149    /// Irregular topologies cannot be represented at all: `vps_per_socket` is a
150    /// single value, so sockets of differing sizes, or siblings that are not
151    /// adjacent in VP index order, have nowhere to live. Supporting those would
152    /// require storing per-VP coordinates rather than deriving them here.
153    pub(crate) fn logical_topology(&self, vp_index: VpIndex) -> VpTopologyInfo {
154        let index = vp_index.index();
155        let in_socket = index % self.vps_per_socket;
156        let (core, thread) = if self.smt_enabled {
157            (in_socket / THREADS_PER_CORE, in_socket % THREADS_PER_CORE)
158        } else {
159            (in_socket, 0)
160        };
161        VpTopologyInfo {
162            socket: index / self.vps_per_socket,
163            core,
164            thread,
165        }
166    }
167}
168
169/// The number of threads per core when SMT is enabled.
170///
171/// The topology API models SMT as a boolean, so two is the only possibility.
172pub(crate) const THREADS_PER_CORE: u32 = 2;
173
174impl<
175    #[cfg(feature = "inspect")] T: ArchTopology + inspect::Inspect,
176    #[cfg(not(feature = "inspect"))] T: ArchTopology,
177> ProcessorTopology<T>
178{
179    /// Returns the number of VPs.
180    pub fn vp_count(&self) -> u32 {
181        self.vps.len() as u32
182    }
183
184    /// Returns information for the given processor by VP index.
185    ///
186    /// Panics if the VP index is out of range.
187    pub fn vp(&self, vp_index: VpIndex) -> VpInfo {
188        *self.vps[vp_index.index() as usize].as_ref()
189    }
190
191    /// Returns information for the given processor by VP index, including
192    /// architecture-specific information.
193    ///
194    /// Panics if the VP index is out of range.
195    pub fn vp_arch(&self, vp_index: VpIndex) -> T::ArchVpInfo {
196        self.vps[vp_index.index() as usize]
197    }
198
199    /// Returns an iterator over all VPs.
200    pub fn vps(&self) -> impl '_ + ExactSizeIterator<Item = VpInfo> + Clone {
201        self.vps.iter().map(|vp| *vp.as_ref())
202    }
203
204    /// Returns an iterator over all VPs, including architecture-specific information.
205    pub fn vps_arch(&self) -> impl '_ + ExactSizeIterator<Item = T::ArchVpInfo> + Clone {
206        self.vps.iter().copied()
207    }
208
209    /// Returns whether SMT (hyperthreading) is enabled.
210    pub fn smt_enabled(&self) -> bool {
211        self.smt_enabled
212    }
213
214    /// Returns the configured number of VPs per socket.
215    ///
216    /// This is the logical socket size before power-of-two rounding. Use
217    /// this for NUMA VP assignment and other logical topology queries.
218    pub fn vps_per_socket(&self) -> u32 {
219        self.vps_per_socket
220    }
221
222    /// Returns the number of VPs per socket, rounded up to a power of 2.
223    ///
224    /// This is the APIC-ID-space reservation per socket. The number of VPs
225    /// actually populated in a socket may be smaller than this.
226    pub fn reserved_vps_per_socket(&self) -> u32 {
227        self.vps_per_socket.next_power_of_two()
228    }
229
230    /// Computes the processor topology information for a VP.
231    ///
232    /// This reports the topology the VM was configured with, which is only as
233    /// accurate as that configuration. It is not recovered from the VP's
234    /// architectural identity, and callers should not treat it as a
235    /// measurement of the underlying hardware.
236    pub fn vp_topology(&self, vp_index: VpIndex) -> VpTopologyInfo {
237        T::vp_topology(self, &self.vp_arch(vp_index))
238    }
239
240    /// Sets the virtual NUMA node for each VP.
241    ///
242    /// `vnodes` must have exactly `vp_count()` entries, where `vnodes[i]` is
243    /// the vnode for VP index `i`.
244    ///
245    /// # Panics
246    ///
247    /// Panics if `vnodes.len() != vp_count()`.
248    pub fn set_vnodes(&mut self, vnodes: &[u32])
249    where
250        T::ArchVpInfo: AsMut<VpInfo>,
251    {
252        assert_eq!(vnodes.len(), self.vps.len());
253        for (vp, &vnode) in self.vps.iter_mut().zip(vnodes) {
254            vp.as_mut().vnode = vnode;
255        }
256    }
257}
258
259/// Per-processor topology information.
260#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
261#[derive(Debug, Copy, Clone)]
262pub struct VpInfo {
263    /// The VP index of the processor.
264    pub vp_index: VpIndex,
265    /// The virtual NUMA node of the processor.
266    pub vnode: u32,
267}
268
269impl AsRef<VpInfo> for VpInfo {
270    fn as_ref(&self) -> &VpInfo {
271        self
272    }
273}
274
275impl VpInfo {
276    /// Returns true if this is the BSP.
277    pub fn is_bsp(&self) -> bool {
278        self.vp_index.is_bsp()
279    }
280}
281
282/// Topology information about a virtual processor.
283pub struct VpTopologyInfo {
284    /// The socket index.
285    pub socket: u32,
286    /// The core index within the socket.
287    pub core: u32,
288    /// The thread index within the core.
289    pub thread: u32,
290}
291
292/// The virtual processor index.
293///
294/// This value is used inside the VMM to identify the processor. It is expected
295/// to be used as an index into processor arrays, so it starts at zero and has
296/// no gaps.
297///
298/// VP index zero is special in that it is always present and is always the BSP.
299///
300/// The same value is exposed to the guest operating system as the HV VP index,
301/// via the Microsoft hypervisor guest interface. This constrains the HV VP
302/// index to start at zero and have no gaps, which is not required by the
303/// hypervisor interface, but it matches the behavior of Hyper-V and is not a
304/// practical limitation.
305///
306/// This value is distinct from the APIC ID, although they are often the same
307/// for all processors in small VMs and some in large VMs. Be careful not to use
308/// them interchangeably.
309#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
310#[cfg_attr(feature = "inspect", derive(inspect::Inspect), inspect(transparent))]
311pub struct VpIndex(u32);
312
313impl VpIndex {
314    /// Returns `index` as a VP index.
315    pub const fn new(index: u32) -> Self {
316        Self(index)
317    }
318
319    /// VP index zero, corresponding to the boot processor (BSP).
320    ///
321    /// Note that this being a constant means that the BSP's HV VP index
322    /// observed by the guest will always be zero. This is consistent with
323    /// Hyper-V and is not a practical limitation.
324    ///
325    /// Note that the APIC ID of the BSP might not be zero.
326    pub const BSP: Self = Self::new(0);
327
328    /// Returns the VP index value.
329    pub fn index(&self) -> u32 {
330        self.0
331    }
332
333    /// Returns true if this is the index of the BSP (0).
334    pub fn is_bsp(&self) -> bool {
335        *self == Self::BSP
336    }
337}