hyperlight_host/hypervisor/virtual_machine/
mod.rs1use std::fmt::Debug;
18use std::sync::OnceLock;
19
20use tracing::{Span, instrument};
21
22#[cfg(gdb)]
23use crate::hypervisor::gdb::DebugError;
24use crate::hypervisor::regs::{
25 CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
26};
27use crate::mem::memory_region::MemoryRegion;
28#[cfg(feature = "trace_guest")]
29use crate::sandbox::trace::TraceContext as SandboxTraceContext;
30
31#[cfg(kvm)]
33pub(crate) mod kvm;
34#[cfg(mshv3)]
36pub(crate) mod mshv;
37#[cfg(target_os = "windows")]
39pub(crate) mod whp;
40
41static AVAILABLE_HYPERVISOR: OnceLock<Option<HypervisorType>> = OnceLock::new();
42
43pub fn get_available_hypervisor() -> &'static Option<HypervisorType> {
45 AVAILABLE_HYPERVISOR.get_or_init(|| {
46 cfg_if::cfg_if! {
47 if #[cfg(all(kvm, mshv3))] {
48 if mshv::is_hypervisor_present() {
52 Some(HypervisorType::Mshv)
53 } else if kvm::is_hypervisor_present() {
54 Some(HypervisorType::Kvm)
55 } else {
56 None
57 }
58 } else if #[cfg(kvm)] {
59 if kvm::is_hypervisor_present() {
60 Some(HypervisorType::Kvm)
61 } else {
62 None
63 }
64 } else if #[cfg(mshv3)] {
65 if mshv::is_hypervisor_present() {
66 Some(HypervisorType::Mshv)
67 } else {
68 None
69 }
70 } else if #[cfg(target_os = "windows")] {
71 if whp::is_hypervisor_present() {
72 Some(HypervisorType::Whp)
73 } else {
74 None
75 }
76 } else {
77 None
78 }
79 }
80 })
81}
82
83#[instrument(skip_all, parent = Span::current())]
86pub fn is_hypervisor_present() -> bool {
87 get_available_hypervisor().is_some()
88}
89
90#[derive(PartialEq, Eq, Debug, Copy, Clone)]
92pub(crate) enum HypervisorType {
93 #[cfg(kvm)]
94 Kvm,
95
96 #[cfg(mshv3)]
97 Mshv,
98
99 #[cfg(target_os = "windows")]
100 Whp,
101}
102
103#[cfg(any(mshv3, target_os = "windows"))]
107pub(crate) const XSAVE_MIN_SIZE: usize = 576;
108
109#[cfg(all(any(kvm, mshv3), test, feature = "init-paging"))]
112pub(crate) const XSAVE_BUFFER_SIZE: usize = 4096;
113
114#[cfg(not(any(kvm, mshv3, target_os = "windows")))]
116compile_error!(
117 "No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature."
118);
119
120pub(crate) enum VmExit {
122 #[cfg(gdb)]
124 Debug { dr6: u64, exception: u32 },
125 Halt(),
127 IoOut(u16, Vec<u8>),
129 MmioRead(u64),
131 MmioWrite(u64),
133 Cancelled(),
135 Unknown(String),
137 #[cfg_attr(
139 target_os = "windows",
140 expect(
141 dead_code,
142 reason = "Retry() is never constructed on Windows, but it is still matched on (which dead_code lint ignores)"
143 )
144 )]
145 Retry(),
146}
147
148#[derive(Debug, Clone, thiserror::Error)]
150pub enum VmError {
151 #[error("Failed to create vm: {0}")]
152 CreateVm(#[from] CreateVmError),
153 #[cfg(gdb)]
154 #[error("Debug operation failed: {0}")]
155 Debug(#[from] DebugError),
156 #[error("Map memory operation failed: {0}")]
157 MapMemory(#[from] MapMemoryError),
158 #[error("Register operation failed: {0}")]
159 Register(#[from] RegisterError),
160 #[error("Failed to run vcpu: {0}")]
161 RunVcpu(#[from] RunVcpuError),
162 #[error("Unmap memory operation failed: {0}")]
163 UnmapMemory(#[from] UnmapMemoryError),
164}
165
166#[derive(Debug, Clone, thiserror::Error)]
168pub enum CreateVmError {
169 #[error("VCPU creation failed: {0}")]
170 CreateVcpuFd(HypervisorError),
171 #[error("VM creation failed: {0}")]
172 CreateVmFd(HypervisorError),
173 #[error("Hypervisor is not available: {0}")]
174 HypervisorNotAvailable(HypervisorError),
175 #[error("Initialize VM failed: {0}")]
176 InitializeVm(HypervisorError),
177 #[error("Set Partition Property failed: {0}")]
178 SetPartitionProperty(HypervisorError),
179 #[cfg(target_os = "windows")]
180 #[error("Surrogate process creation failed: {0}")]
181 SurrogateProcess(String),
182}
183
184#[derive(Debug, Clone, thiserror::Error)]
186pub enum RunVcpuError {
187 #[error("Failed to decode message type: {0}")]
188 DecodeIOMessage(u32),
189 #[cfg(gdb)]
190 #[error("Failed to get DR6 debug register: {0}")]
191 GetDr6(HypervisorError),
192 #[error("Increment RIP failed: {0}")]
193 IncrementRip(HypervisorError),
194 #[error("Parse GPA access info failed")]
195 ParseGpaAccessInfo,
196 #[error("Unknown error: {0}")]
197 Unknown(HypervisorError),
198}
199
200#[derive(Debug, Clone, thiserror::Error)]
202pub enum RegisterError {
203 #[error("Failed to get registers: {0}")]
204 GetRegs(HypervisorError),
205 #[error("Failed to set registers: {0}")]
206 SetRegs(HypervisorError),
207 #[error("Failed to get FPU registers: {0}")]
208 GetFpu(HypervisorError),
209 #[error("Failed to set FPU registers: {0}")]
210 SetFpu(HypervisorError),
211 #[error("Failed to get special registers: {0}")]
212 GetSregs(HypervisorError),
213 #[error("Failed to set special registers: {0}")]
214 SetSregs(HypervisorError),
215 #[error("Failed to get debug registers: {0}")]
216 GetDebugRegs(HypervisorError),
217 #[error("Failed to set debug registers: {0}")]
218 SetDebugRegs(HypervisorError),
219 #[error("Failed to get xsave: {0}")]
220 GetXsave(HypervisorError),
221 #[error("Failed to set xsave: {0}")]
222 SetXsave(HypervisorError),
223 #[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
224 XsaveSizeMismatch {
225 expected: u32,
227 actual: u32,
229 },
230 #[error("Invalid xsave alignment")]
231 InvalidXsaveAlignment,
232 #[cfg(target_os = "windows")]
233 #[error("Failed to get xsave size: {0}")]
234 GetXsaveSize(#[from] HypervisorError),
235 #[cfg(target_os = "windows")]
236 #[error("Failed to convert WHP registers: {0}")]
237 ConversionFailed(String),
238}
239
240#[derive(Debug, Clone, thiserror::Error)]
242pub enum MapMemoryError {
243 #[cfg(target_os = "windows")]
244 #[error("Address conversion failed: {0}")]
245 AddressConversion(std::num::TryFromIntError),
246 #[error("Hypervisor error: {0}")]
247 Hypervisor(HypervisorError),
248 #[cfg(target_os = "windows")]
249 #[error("Invalid memory region flags: {0}")]
250 InvalidFlags(String),
251 #[cfg(target_os = "windows")]
252 #[error("Failed to load API '{api_name}': {source}")]
253 LoadApi {
254 api_name: &'static str,
255 source: windows_result::Error,
256 },
257 #[cfg(target_os = "windows")]
258 #[error("Operation not supported: {0}")]
259 NotSupported(String),
260 #[cfg(target_os = "windows")]
261 #[error("Surrogate process creation failed: {0}")]
262 SurrogateProcess(String),
263}
264
265#[derive(Debug, Clone, thiserror::Error)]
267pub enum UnmapMemoryError {
268 #[error("Hypervisor error: {0}")]
269 Hypervisor(HypervisorError),
270}
271
272#[derive(Debug, Clone, thiserror::Error)]
274pub enum HypervisorError {
275 #[cfg(kvm)]
276 #[error("KVM error: {0}")]
277 KvmError(#[from] kvm_ioctls::Error),
278 #[cfg(mshv3)]
279 #[error("MSHV error: {0}")]
280 MshvError(#[from] mshv_ioctls::MshvError),
281 #[cfg(target_os = "windows")]
282 #[error("Windows error: {0}")]
283 WindowsError(#[from] windows_result::Error),
284}
285
286pub(crate) trait VirtualMachine: Debug + Send {
289 unsafe fn map_memory(
298 &mut self,
299 region: (u32, &MemoryRegion),
300 ) -> std::result::Result<(), MapMemoryError>;
301
302 fn unmap_memory(
304 &mut self,
305 region: (u32, &MemoryRegion),
306 ) -> std::result::Result<(), UnmapMemoryError>;
307
308 fn run_vcpu(
312 &mut self,
313 #[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
314 ) -> std::result::Result<VmExit, RunVcpuError>;
315
316 #[allow(dead_code)]
318 fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
319 fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
321 #[allow(dead_code)]
323 fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
324 fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
326 #[allow(dead_code)]
328 fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
329 fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
331 #[allow(dead_code)]
333 fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
334 fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
336
337 #[allow(dead_code)]
339 fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
340 fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
342 #[cfg(test)]
344 #[cfg(feature = "init-paging")]
345 fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
346
347 #[cfg(target_os = "windows")]
349 fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
350}
351
352#[cfg(test)]
353mod tests {
354
355 #[test]
356 #[cfg(target_os = "linux")]
358 fn is_hypervisor_present() {
359 use std::path::Path;
360
361 cfg_if::cfg_if! {
362 if #[cfg(all(kvm, mshv3))] {
363 assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
364 } else if #[cfg(kvm)] {
365 assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
366 } else if #[cfg(mshv3)] {
367 assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
368 } else {
369 assert!(!super::is_hypervisor_present());
370 }
371 }
372 }
373}