1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Processor topology types.

pub mod aarch64;
pub mod x86;

cfg_if::cfg_if! {
    if #[cfg(guest_arch = "aarch64")] {
        pub use aarch64 as arch;
        pub use aarch64::Aarch64Topology as TargetTopology;
        pub use aarch64::Aarch64VpInfo as TargetVpInfo;
    } else if #[cfg(guest_arch = "x86_64")] {
        pub use x86 as arch;
        pub use x86::X86Topology as TargetTopology;
        pub use x86::X86VpInfo as TargetVpInfo;
    } else {
        compile_error!("Unsupported architecture");
    }
}
use thiserror::Error;

/// A description of the VM's processor topology.
///
/// Currently this just tracks the APIC IDs for the processors.
///
/// Build one with [`TopologyBuilder`].
#[cfg_attr(
    feature = "inspect",
    derive(inspect::Inspect),
    inspect(bound = "T: inspect::Inspect, T::ArchVpInfo: inspect::Inspect")
)]
#[derive(Debug, Clone)]
pub struct ProcessorTopology<T: ArchTopology = TargetTopology> {
    #[cfg_attr(feature = "inspect", inspect(iter_by_index))]
    vps: Vec<T::ArchVpInfo>,
    smt_enabled: bool,
    vps_per_socket: u32,
    arch: T,
}

/// Architecture-specific topology types.
pub trait ArchTopology: Sized {
    /// The architecture-specific VP info type.
    type ArchVpInfo: Copy + AsRef<VpInfo>;

    /// The architecture-specific [`TopologyBuilder`] generic.
    type BuilderState;

    /// Compute VP topology from a VP.
    fn vp_topology(topology: &ProcessorTopology<Self>, info: &Self::ArchVpInfo) -> VpTopologyInfo;
}

/// A builder for [`ProcessorTopology`].
#[derive(Debug)]
pub struct TopologyBuilder<T: ArchTopology> {
    vps_per_socket: u32,
    smt_enabled: bool,
    arch: T::BuilderState,
}

/// Error returned by [`TopologyBuilder::from_host_topology`].
#[derive(Debug, Error)]
pub enum HostTopologyError {
    /// Could not find the host topology.
    #[error("could not compute host topology via cpuid")]
    NotFound,
    /// The host topology has more than 2 threads per core.
    #[error("unsupported thread-per-core count {0}")]
    UnsupportedThreadsPerCore(u32),
}

/// Error when building a [`ProcessorTopology`].
#[derive(Debug, Error)]
pub enum InvalidTopology {
    /// Failed to configure at least one VP.
    #[error("must have at least one processor")]
    NoVps,
    /// Too many virtual processors.
    #[error("too many processors requested: {requested}, max {max}")]
    TooManyVps {
        /// The number of processors requested.
        requested: u32,
        /// The maximum number of processors.
        max: u32,
    },
    /// Not all processors will be addressable in XAPIC mode.
    #[error("too many processors or too high an APIC ID {0} for xapic mode")]
    ApicIdLimitExceeded(u32),
    /// VpInfo indices must be linear and start at 0
    #[error("vp indices don't start at 0 or don't count up")]
    InvalidVpIndices,
    /// Failed to query the topology information from Device Tree.
    #[error("failed to query memory topology from device tree")]
    StdIoError(#[source] std::io::Error),
}

impl<T: ArchTopology> TopologyBuilder<T> {
    /// Sets the number of VPs per socket.
    ///
    /// This does not need to be a power of 2, but it should be a multiple of 2
    /// if SMT is enabled.
    ///
    /// The number of VPs per socket will be rounded up to a power of 2 for
    /// purposes of defining the x2APIC ID.
    pub fn vps_per_socket(&mut self, count: u32) -> &mut Self {
        self.vps_per_socket = count.clamp(1, 32768);
        self
    }

    /// Sets whether SMT (hyperthreading) is enabled.
    ///
    /// This is ignored if `vps_per_socket` is 1.
    pub fn smt_enabled(&mut self, enabled: bool) -> &mut Self {
        self.smt_enabled = enabled;
        self
    }
}

impl<
        #[cfg(feature = "inspect")] T: ArchTopology + inspect::Inspect,
        #[cfg(not(feature = "inspect"))] T: ArchTopology,
    > ProcessorTopology<T>
{
    /// Returns the number of VPs.
    pub fn vp_count(&self) -> u32 {
        self.vps.len() as u32
    }

    /// Returns information for the given processor by VP index.
    ///
    /// Panics if the VP index is out of range.
    pub fn vp(&self, vp_index: VpIndex) -> VpInfo {
        *self.vps[vp_index.index() as usize].as_ref()
    }

    /// Returns information for the given processor by VP index, including
    /// architecture-specific information.
    ///
    /// Panics if the VP index is out of range.
    pub fn vp_arch(&self, vp_index: VpIndex) -> T::ArchVpInfo {
        self.vps[vp_index.index() as usize]
    }

    /// Returns an iterator over all VPs.
    pub fn vps(&self) -> impl '_ + ExactSizeIterator<Item = VpInfo> + Clone {
        self.vps.iter().map(|vp| *vp.as_ref())
    }

    /// Returns an iterator over all VPs, including architecture-specific information.
    pub fn vps_arch(&self) -> impl '_ + ExactSizeIterator<Item = T::ArchVpInfo> + Clone {
        self.vps.iter().copied()
    }

    /// Returns whether SMT (hyperthreading) is enabled.
    pub fn smt_enabled(&self) -> bool {
        self.smt_enabled
    }

    /// Returns the number of VPs per socket.
    ///
    /// This will always be a power of 2. The number of VPs actually populated
    /// in a socket may be smaller than this.
    pub fn reserved_vps_per_socket(&self) -> u32 {
        self.vps_per_socket.next_power_of_two()
    }

    /// Computes the processor topology information for a VP.
    pub fn vp_topology(&self, vp_index: VpIndex) -> VpTopologyInfo {
        T::vp_topology(self, &self.vp_arch(vp_index))
    }
}

/// Per-processor topology information.
#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
#[derive(Debug, Copy, Clone)]
pub struct VpInfo {
    /// The VP index of the processor.
    pub vp_index: VpIndex,
    /// The virtual NUMA node of the processor.
    pub vnode: u32,
}

impl AsRef<VpInfo> for VpInfo {
    fn as_ref(&self) -> &VpInfo {
        self
    }
}

impl VpInfo {
    /// Returns true if this is the BSP.
    pub fn is_bsp(&self) -> bool {
        self.vp_index.is_bsp()
    }
}

/// Topology information about a virtual processor.
pub struct VpTopologyInfo {
    /// The socket index.
    pub socket: u32,
    /// The core index within the socket.
    pub core: u32,
    /// The thread index within the core.
    pub thread: u32,
}

/// The virtual processor index.
///
/// This value is used inside the VMM to identify the processor. It is expected
/// to be used as an index into processor arrays, so it starts at zero and has
/// no gaps.
///
/// VP index zero is special in that it is always present and is always the BSP.
///
/// The same value is exposed to the guest operating system as the HV VP index,
/// via the Microsoft hypervisor guest interface. This constrains the HV VP
/// index to start at zero and have no gaps, which is not required by the
/// hypervisor interface, but it matches the behavior of Hyper-V and is not a
/// practical limitation.
///
/// This value is distinct from the APIC ID, although they are often the same
/// for all processors in small VMs and some in large VMs. Be careful not to use
/// them interchangeably.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "inspect", derive(inspect::Inspect), inspect(transparent))]
pub struct VpIndex(u32);

impl VpIndex {
    /// Returns `index` as a VP index.
    pub const fn new(index: u32) -> Self {
        Self(index)
    }

    /// VP index zero, corresponding to the boot processor (BSP).
    ///
    /// Note that this being a constant means that the BSP's HV VP index
    /// observed by the guest will always be zero. This is consistent with
    /// Hyper-V and is not a practical limitation.
    ///
    /// Note that the APIC ID of the BSP might not be zero.
    pub const BSP: Self = Self::new(0);

    /// Returns the VP index value.
    pub fn index(&self) -> u32 {
        self.0
    }

    /// Returns true if this is the index of the BSP (0).
    pub fn is_bsp(&self) -> bool {
        *self == Self::BSP
    }
}