xref: /aosp_15_r20/external/crosvm/hypervisor/src/whpx.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Implementation for WHPX hypervisor aka Windows Hyper-V platform.
6 
7 use core::ffi::c_void;
8 use std::arch::x86_64::__cpuid_count;
9 
10 use base::error;
11 use base::warn;
12 use base::Error;
13 use base::Result;
14 use once_cell::sync::Lazy;
15 use thiserror::Error as ThisError;
16 use winapi::shared::winerror::S_OK;
17 
18 use crate::CpuId;
19 use crate::CpuIdEntry;
20 use crate::Hypervisor;
21 use crate::HypervisorCap;
22 use crate::HypervisorX86_64;
23 
24 #[macro_export]
25 macro_rules! check_whpx {
26     ($x: expr) => {{
27         match $x {
28             S_OK => Ok(()),
29             _ => Err(Error::new($x)),
30         }
31     }};
32 }
33 
34 mod types;
35 mod vcpu;
36 pub use vcpu::*;
37 mod vm;
38 pub use vm::*;
39 pub mod whpx_sys;
40 pub use whpx_sys::*;
41 
42 // used by both the vm and vcpu
43 struct SafePartition {
44     partition: WHV_PARTITION_HANDLE,
45 }
46 
47 // we can send the partition over safely even though it is void*, it can be sent
48 // in another thread safely.
49 unsafe impl Send for SafePartition {}
50 unsafe impl Sync for SafePartition {}
51 
52 impl SafePartition {
new() -> WhpxResult<SafePartition>53     fn new() -> WhpxResult<SafePartition> {
54         let mut partition_handle: WHV_PARTITION_HANDLE = std::ptr::null_mut();
55         // safe because we pass in the partition handle for the system to fill in.
56         check_whpx!(unsafe { WHvCreatePartition(&mut partition_handle) })
57             .map_err(WhpxError::CreatePartitionError)?;
58 
59         Ok(SafePartition {
60             partition: partition_handle,
61         })
62     }
63 }
64 
65 impl Drop for SafePartition {
drop(&mut self)66     fn drop(&mut self) {
67         // safe because we own this partition
68         check_whpx!(unsafe { WHvDeletePartition(self.partition) }).unwrap();
69     }
70 }
71 
72 #[derive(Clone)]
73 pub struct Whpx {
74     // there is no general whpx object, the vm contains the partition.
75 }
76 
77 /// Enum of WHPX Features. Similar to WHV_CAPABILITY_FEATURES but allows us to avoid making the
78 /// whpx_sys crate pub.
79 #[derive(Debug)]
80 pub enum WhpxFeature {
81     PartialUnmap = 0x0,
82     LocalApicEmulation = 0x1,
83     Xsave = 0x2,
84     DirtyPageTracking = 0x4,
85     SpeculationControl = 0x8,
86     ApicRemoteRead = 0x10,
87     IdleSuspend = 0x20,
88 }
89 
90 #[derive(ThisError, Debug, Clone, Copy)]
91 pub enum WhpxError {
92     #[error("failed to create WHPX partition: {0}")]
93     CreatePartitionError(base::Error),
94     #[error("failed to get WHPX capability {0}: {1}")]
95     GetCapability(WHV_CAPABILITY_CODE, base::Error),
96     #[error("WHPX local apic emulation is not supported on this host")]
97     LocalApicEmulationNotSupported,
98     #[error("failed to map guest physical address range: {0}")]
99     MapGpaRange(base::Error),
100     #[error("failed to set WHPX partition processor count: {0}")]
101     SetProcessorCount(base::Error),
102     #[error("failed to set WHPX partition cpuid result list: {0}")]
103     SetCpuidResultList(base::Error),
104     #[error("failed to set WHPX partition cpuid exit list: {0}")]
105     SetCpuidExitList(base::Error),
106     #[error("failed to set WHPX partition extended vm exits: {0}")]
107     SetExtendedVmExits(base::Error),
108     #[error("failed to set WHPX partition local apic emulation mode: {0}")]
109     SetLocalApicEmulationMode(base::Error),
110     #[error("failed to setup WHPX partition: {0}")]
111     SetupPartition(base::Error),
112 }
113 
114 impl From<WhpxError> for Box<dyn std::error::Error + Send> {
from(e: WhpxError) -> Self115     fn from(e: WhpxError) -> Self {
116         Box::new(e)
117     }
118 }
119 
120 type WhpxResult<T> = std::result::Result<T, WhpxError>;
121 
122 impl Whpx {
new() -> Result<Whpx>123     pub fn new() -> Result<Whpx> {
124         Ok(Whpx {})
125     }
126 
is_enabled() -> bool127     pub fn is_enabled() -> bool {
128         let res = Whpx::get_capability(WHV_CAPABILITY_CODE_WHvCapabilityCodeHypervisorPresent);
129         match res {
130             Ok(cap_code) => {
131                 // safe because we trust the kernel to fill in hypervisor present in the union
132                 unsafe { cap_code.HypervisorPresent > 0 }
133             }
134             _ => {
135                 warn!("error checking if whpx was enabled. Assuming whpx is disabled");
136                 false
137             }
138         }
139     }
140 
get_capability(cap: WHV_CAPABILITY_CODE) -> WhpxResult<WHV_CAPABILITY>141     fn get_capability(cap: WHV_CAPABILITY_CODE) -> WhpxResult<WHV_CAPABILITY> {
142         let mut capability: WHV_CAPABILITY = Default::default();
143         let mut written_size = 0;
144         check_whpx!(unsafe {
145             WHvGetCapability(
146                 cap,
147                 &mut capability as *mut _ as *mut c_void,
148                 std::mem::size_of::<WHV_CAPABILITY>() as u32,
149                 &mut written_size,
150             )
151         })
152         .map_err(|e| WhpxError::GetCapability(cap, e))?;
153         Ok(capability)
154     }
155 
check_whpx_feature(feature: WhpxFeature) -> WhpxResult<bool>156     pub fn check_whpx_feature(feature: WhpxFeature) -> WhpxResult<bool> {
157         // use Lazy to cache the results of the get_capability call
158         static FEATURES: Lazy<WhpxResult<WHV_CAPABILITY>> =
159             Lazy::new(|| Whpx::get_capability(WHV_CAPABILITY_CODE_WHvCapabilityCodeFeatures));
160 
161         Ok((unsafe { (*FEATURES)?.Features.AsUINT64 } & feature as u64) != 0)
162     }
163 }
164 
165 impl Hypervisor for Whpx {
166     /// Makes a shallow clone of this `Hypervisor`.
try_clone(&self) -> Result<Self>167     fn try_clone(&self) -> Result<Self> {
168         Ok(self.clone())
169     }
170 
171     /// Checks if a particular `HypervisorCap` is available.
check_capability(&self, cap: HypervisorCap) -> bool172     fn check_capability(&self, cap: HypervisorCap) -> bool {
173         // whpx supports immediate exit, user memory, and the xcr0 state
174         match cap {
175             HypervisorCap::ImmediateExit => true,
176             HypervisorCap::UserMemory => true,
177             HypervisorCap::Xcrs => {
178                 Whpx::check_whpx_feature(WhpxFeature::Xsave).unwrap_or_else(|e| {
179                     error!(
180                         "failed to check whpx feature {:?}: {}",
181                         WhpxFeature::Xsave,
182                         e
183                     );
184                     false
185                 })
186             }
187             // under whpx, guests rely on this leaf to calibrate their clocksource.
188             HypervisorCap::CalibratedTscLeafRequired => true,
189             _ => false,
190         }
191     }
192 }
193 
194 /// Build a CpuIdEntry for a given `function`/`index` from the host results for that
195 /// `function`/`index`.
cpuid_entry_from_host(function: u32, index: u32) -> CpuIdEntry196 fn cpuid_entry_from_host(function: u32, index: u32) -> CpuIdEntry {
197     // Safe because arguments are passed by value
198     let result = unsafe { __cpuid_count(function, index) };
199     CpuIdEntry {
200         function,
201         index,
202         flags: 0,
203         cpuid: result,
204     }
205 }
206 
207 impl HypervisorX86_64 for Whpx {
208     /// Get the system supported CPUID values.
209     ///
210     /// WHPX does not have an API for getting this information, so we just return the host values
211     /// instead. This is not technically accurate, since WHPX does modify the contents of various
212     /// leaves, but in practice this is fine because this function is only used for pre-setting
213     /// the contents of certain leaves that we can safely base off of the host value.
get_supported_cpuid(&self) -> Result<CpuId>214     fn get_supported_cpuid(&self) -> Result<CpuId> {
215         Ok(CpuId {
216             cpu_id_entries: vec![
217                 // Leaf 0 just contains information about the max leaf. WHPX seems to set this to
218                 // a value lower than the host value but we want it to be at least 0x15. We set it
219                 // to the host value here assuming that the leaves above 0x15 probably won't hurt
220                 // the guest.
221                 cpuid_entry_from_host(0, 0),
222                 // crosvm overrides the entirety of leaves 2, 0x80000005, and 0x80000006 to the
223                 // host value, so we just return the host value here.
224                 cpuid_entry_from_host(2, 0),
225                 cpuid_entry_from_host(0x80000005, 0),
226                 cpuid_entry_from_host(0x80000006, 0),
227             ],
228         })
229     }
230 
231     /// Gets the list of supported MSRs.
232     /// TODO: this is only used by the plugin
get_msr_index_list(&self) -> Result<Vec<u32>>233     fn get_msr_index_list(&self) -> Result<Vec<u32>> {
234         Ok(vec![])
235     }
236 }
237 
238 #[cfg(test)]
239 mod tests {
240     use super::*;
241 
242     #[test]
new_whpx()243     fn new_whpx() {
244         Whpx::new().expect("failed to instantiate whpx");
245     }
246 
247     #[test]
clone_whpx()248     fn clone_whpx() {
249         let whpx = Whpx::new().expect("failed to instantiate whpx");
250         let _whpx_clone = whpx.try_clone().unwrap();
251     }
252 
253     #[test]
check_capability()254     fn check_capability() {
255         let whpx = Whpx::new().expect("failed to instantiate whpx");
256         assert!(whpx.check_capability(HypervisorCap::UserMemory));
257         assert!(whpx.check_capability(HypervisorCap::Xcrs));
258         assert!(whpx.check_capability(HypervisorCap::ImmediateExit));
259         assert!(!whpx.check_capability(HypervisorCap::S390UserSigp));
260     }
261 }
262