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