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 /// Failed to query the topology information from Device Tree.
98 #[error("failed to query memory topology from device tree")]
99 StdIoError(#[source] std::io::Error),
100}
101
102impl<T: ArchTopology> TopologyBuilder<T> {
103 /// Sets the number of VPs per socket.
104 ///
105 /// This does not need to be a power of 2, but it should be a multiple of 2
106 /// if SMT is enabled.
107 ///
108 /// The number of VPs per socket will be rounded up to a power of 2 for
109 /// purposes of defining the x2APIC ID.
110 pub fn vps_per_socket(&mut self, count: u32) -> &mut Self {
111 self.vps_per_socket = count.clamp(1, 32768);
112 self
113 }
114
115 /// Sets whether SMT (hyperthreading) is enabled.
116 ///
117 /// This is ignored if `vps_per_socket` is 1.
118 pub fn smt_enabled(&mut self, enabled: bool) -> &mut Self {
119 self.smt_enabled = enabled;
120 self
121 }
122}
123
124impl<
125 #[cfg(feature = "inspect")] T: ArchTopology + inspect::Inspect,
126 #[cfg(not(feature = "inspect"))] T: ArchTopology,
127> ProcessorTopology<T>
128{
129 /// Returns the number of VPs.
130 pub fn vp_count(&self) -> u32 {
131 self.vps.len() as u32
132 }
133
134 /// Returns information for the given processor by VP index.
135 ///
136 /// Panics if the VP index is out of range.
137 pub fn vp(&self, vp_index: VpIndex) -> VpInfo {
138 *self.vps[vp_index.index() as usize].as_ref()
139 }
140
141 /// Returns information for the given processor by VP index, including
142 /// architecture-specific information.
143 ///
144 /// Panics if the VP index is out of range.
145 pub fn vp_arch(&self, vp_index: VpIndex) -> T::ArchVpInfo {
146 self.vps[vp_index.index() as usize]
147 }
148
149 /// Returns an iterator over all VPs.
150 pub fn vps(&self) -> impl '_ + ExactSizeIterator<Item = VpInfo> + Clone {
151 self.vps.iter().map(|vp| *vp.as_ref())
152 }
153
154 /// Returns an iterator over all VPs, including architecture-specific information.
155 pub fn vps_arch(&self) -> impl '_ + ExactSizeIterator<Item = T::ArchVpInfo> + Clone {
156 self.vps.iter().copied()
157 }
158
159 /// Returns whether SMT (hyperthreading) is enabled.
160 pub fn smt_enabled(&self) -> bool {
161 self.smt_enabled
162 }
163
164 /// Returns the number of VPs per socket.
165 ///
166 /// This will always be a power of 2. The number of VPs actually populated
167 /// in a socket may be smaller than this.
168 pub fn reserved_vps_per_socket(&self) -> u32 {
169 self.vps_per_socket.next_power_of_two()
170 }
171
172 /// Computes the processor topology information for a VP.
173 pub fn vp_topology(&self, vp_index: VpIndex) -> VpTopologyInfo {
174 T::vp_topology(self, &self.vp_arch(vp_index))
175 }
176}
177
178/// Per-processor topology information.
179#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
180#[derive(Debug, Copy, Clone)]
181pub struct VpInfo {
182 /// The VP index of the processor.
183 pub vp_index: VpIndex,
184 /// The virtual NUMA node of the processor.
185 pub vnode: u32,
186}
187
188impl AsRef<VpInfo> for VpInfo {
189 fn as_ref(&self) -> &VpInfo {
190 self
191 }
192}
193
194impl VpInfo {
195 /// Returns true if this is the BSP.
196 pub fn is_bsp(&self) -> bool {
197 self.vp_index.is_bsp()
198 }
199}
200
201/// Topology information about a virtual processor.
202pub struct VpTopologyInfo {
203 /// The socket index.
204 pub socket: u32,
205 /// The core index within the socket.
206 pub core: u32,
207 /// The thread index within the core.
208 pub thread: u32,
209}
210
211/// The virtual processor index.
212///
213/// This value is used inside the VMM to identify the processor. It is expected
214/// to be used as an index into processor arrays, so it starts at zero and has
215/// no gaps.
216///
217/// VP index zero is special in that it is always present and is always the BSP.
218///
219/// The same value is exposed to the guest operating system as the HV VP index,
220/// via the Microsoft hypervisor guest interface. This constrains the HV VP
221/// index to start at zero and have no gaps, which is not required by the
222/// hypervisor interface, but it matches the behavior of Hyper-V and is not a
223/// practical limitation.
224///
225/// This value is distinct from the APIC ID, although they are often the same
226/// for all processors in small VMs and some in large VMs. Be careful not to use
227/// them interchangeably.
228#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
229#[cfg_attr(feature = "inspect", derive(inspect::Inspect), inspect(transparent))]
230pub struct VpIndex(u32);
231
232impl VpIndex {
233 /// Returns `index` as a VP index.
234 pub const fn new(index: u32) -> Self {
235 Self(index)
236 }
237
238 /// VP index zero, corresponding to the boot processor (BSP).
239 ///
240 /// Note that this being a constant means that the BSP's HV VP index
241 /// observed by the guest will always be zero. This is consistent with
242 /// Hyper-V and is not a practical limitation.
243 ///
244 /// Note that the APIC ID of the BSP might not be zero.
245 pub const BSP: Self = Self::new(0);
246
247 /// Returns the VP index value.
248 pub fn index(&self) -> u32 {
249 self.0
250 }
251
252 /// Returns true if this is the index of the BSP (0).
253 pub fn is_bsp(&self) -> bool {
254 *self == Self::BSP
255 }
256}