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
41#[cfg(feature = "hw-interrupts")]
43pub(crate) mod x86_64;
44
45static AVAILABLE_HYPERVISOR: OnceLock<Option<HypervisorType>> = OnceLock::new();
46
47pub fn get_available_hypervisor() -> &'static Option<HypervisorType> {
49 AVAILABLE_HYPERVISOR.get_or_init(|| {
50 cfg_if::cfg_if! {
51 if #[cfg(all(kvm, mshv3))] {
52 if mshv::is_hypervisor_present() {
56 Some(HypervisorType::Mshv)
57 } else if kvm::is_hypervisor_present() {
58 Some(HypervisorType::Kvm)
59 } else {
60 None
61 }
62 } else if #[cfg(kvm)] {
63 if kvm::is_hypervisor_present() {
64 Some(HypervisorType::Kvm)
65 } else {
66 None
67 }
68 } else if #[cfg(mshv3)] {
69 if mshv::is_hypervisor_present() {
70 Some(HypervisorType::Mshv)
71 } else {
72 None
73 }
74 } else if #[cfg(target_os = "windows")] {
75 if whp::is_hypervisor_present() {
76 Some(HypervisorType::Whp)
77 } else {
78 None
79 }
80 } else {
81 None
82 }
83 }
84 })
85}
86
87#[instrument(skip_all, parent = Span::current())]
90pub fn is_hypervisor_present() -> bool {
91 get_available_hypervisor().is_some()
92}
93
94#[derive(PartialEq, Eq, Debug, Copy, Clone)]
96pub(crate) enum HypervisorType {
97 #[cfg(kvm)]
98 Kvm,
99
100 #[cfg(mshv3)]
101 Mshv,
102
103 #[cfg(target_os = "windows")]
104 Whp,
105}
106
107#[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))]
111pub(crate) const XSAVE_MIN_SIZE: usize = 576;
112
113#[cfg(all(any(kvm, mshv3), test, not(target_arch = "aarch64")))]
116pub(crate) const XSAVE_BUFFER_SIZE: usize = 4096;
117
118#[cfg(not(any(kvm, mshv3, target_os = "windows", target_arch = "aarch64")))]
120compile_error!(
121 "No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature."
122);
123
124pub(crate) enum VmExit {
126 #[cfg(gdb)]
128 Debug {
129 #[cfg(target_arch = "x86_64")]
130 dr6: u64,
131 #[cfg(target_arch = "x86_64")]
132 exception: u32,
133 },
134 Halt(),
136 IoOut(u16, Vec<u8>),
138 MmioRead(u64),
140 MmioWrite(u64),
142 Cancelled(),
144 Unknown(String),
146 #[cfg_attr(
148 any(target_os = "windows", feature = "hw-interrupts"),
149 expect(
150 dead_code,
151 reason = "Retry() is never constructed on Windows or with hw-interrupts (EAGAIN causes continue instead)"
152 )
153 )]
154 Retry(),
155}
156
157#[derive(Debug, Clone, thiserror::Error)]
159pub enum VmError {
160 #[error("Failed to create vm: {0}")]
161 CreateVm(#[from] CreateVmError),
162 #[cfg(gdb)]
163 #[error("Debug operation failed: {0}")]
164 Debug(#[from] DebugError),
165 #[error("Map memory operation failed: {0}")]
166 MapMemory(#[from] MapMemoryError),
167 #[error("Register operation failed: {0}")]
168 Register(#[from] RegisterError),
169 #[error("Failed to run vcpu: {0}")]
170 RunVcpu(#[from] RunVcpuError),
171 #[error("Unmap memory operation failed: {0}")]
172 UnmapMemory(#[from] UnmapMemoryError),
173}
174
175#[derive(Debug, Clone, thiserror::Error)]
177pub enum CreateVmError {
178 #[error("VCPU creation failed: {0}")]
179 CreateVcpuFd(HypervisorError),
180 #[error("VM creation failed: {0}")]
181 CreateVmFd(HypervisorError),
182 #[error("Hypervisor is not available: {0}")]
183 HypervisorNotAvailable(HypervisorError),
184 #[error("Initialize VM failed: {0}")]
185 InitializeVm(HypervisorError),
186 #[error("Set Partition Property failed: {0}")]
187 SetPartitionProperty(HypervisorError),
188 #[cfg(target_os = "windows")]
189 #[error("Surrogate process creation failed: {0}")]
190 SurrogateProcess(String),
191}
192
193#[derive(Debug, Clone, thiserror::Error)]
195pub enum RunVcpuError {
196 #[error("Failed to decode message type: {0}")]
197 DecodeIOMessage(u32),
198 #[cfg(gdb)]
199 #[error("Failed to get DR6 debug register: {0}")]
200 GetDr6(HypervisorError),
201 #[error("Increment RIP failed: {0}")]
202 IncrementRip(HypervisorError),
203 #[error("Parse GPA access info failed")]
204 ParseGpaAccessInfo,
205 #[cfg(target_arch = "aarch64")]
206 #[error("Flush MMIO pending state failed: {0}")]
207 FlushMmioPending(String),
208 #[error("Unknown error: {0}")]
209 Unknown(HypervisorError),
210}
211
212#[derive(Debug, Clone, thiserror::Error)]
214pub enum RegisterError {
215 #[error("Failed to get registers: {0}")]
216 GetRegs(HypervisorError),
217 #[error("Failed to set registers: {0}")]
218 SetRegs(HypervisorError),
219 #[error("Failed to get FPU registers: {0}")]
220 GetFpu(HypervisorError),
221 #[error("Failed to set FPU registers: {0}")]
222 SetFpu(HypervisorError),
223 #[error("Failed to get special registers: {0}")]
224 GetSregs(HypervisorError),
225 #[error("Failed to set special registers: {0}")]
226 SetSregs(HypervisorError),
227 #[error("Failed to get debug registers: {0}")]
228 GetDebugRegs(HypervisorError),
229 #[error("Failed to set debug registers: {0}")]
230 SetDebugRegs(HypervisorError),
231 #[error("Failed to get xsave: {0}")]
232 GetXsave(HypervisorError),
233 #[error("Failed to set xsave: {0}")]
234 SetXsave(HypervisorError),
235 #[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
236 XsaveSizeMismatch {
237 expected: u32,
239 actual: u32,
241 },
242 #[error("Invalid xsave alignment")]
243 InvalidXsaveAlignment,
244 #[cfg(target_os = "windows")]
245 #[error("Failed to get xsave size: {0}")]
246 GetXsaveSize(#[from] HypervisorError),
247 #[cfg(target_os = "windows")]
248 #[error("Failed to convert WHP registers: {0}")]
249 ConversionFailed(String),
250}
251
252#[derive(Debug, Clone, thiserror::Error)]
253pub enum ResetVcpuError {
254 #[error("Single-operation vcpu reset not supported on this hypervisor")]
255 NotSupported,
256 #[error("Hypervisor operation failed: {0}")]
257 Hypervisor(HypervisorError),
258 #[error("Register operation failed: {0}")]
259 Register(#[from] RegisterError),
260 #[error("Operation failed: {0}")]
261 Unknown(String),
262}
263
264#[derive(Debug, Clone, thiserror::Error)]
266pub enum MapMemoryError {
267 #[cfg(target_os = "windows")]
268 #[error("Address conversion failed: {0}")]
269 AddressConversion(std::num::TryFromIntError),
270 #[error("Hypervisor error: {0}")]
271 Hypervisor(HypervisorError),
272 #[cfg(target_os = "windows")]
273 #[error("Invalid memory region flags: {0}")]
274 InvalidFlags(String),
275 #[cfg(target_os = "windows")]
276 #[error("Failed to load API '{api_name}': {source}")]
277 LoadApi {
278 api_name: &'static str,
279 source: windows_result::Error,
280 },
281 #[cfg(target_os = "windows")]
282 #[error("Operation not supported: {0}")]
283 NotSupported(String),
284 #[cfg(target_os = "windows")]
285 #[error("Surrogate process creation failed: {0}")]
286 SurrogateProcess(String),
287}
288
289#[derive(Debug, Clone, thiserror::Error)]
291pub enum UnmapMemoryError {
292 #[error("Hypervisor error: {0}")]
293 Hypervisor(HypervisorError),
294}
295
296#[derive(Debug, Clone, thiserror::Error)]
298pub enum HypervisorError {
299 #[cfg(kvm)]
300 #[error("KVM error: {0}")]
301 KvmError(#[from] kvm_ioctls::Error),
302 #[cfg(mshv3)]
303 #[error("MSHV error: {0}")]
304 MshvError(#[from] mshv_ioctls::MshvError),
305 #[cfg(target_os = "windows")]
306 #[error("Windows error: {0}")]
307 WindowsError(#[from] windows_result::Error),
308}
309
310pub(crate) trait VirtualMachine: Debug + Send {
313 unsafe fn map_memory(
322 &mut self,
323 region: (u32, &MemoryRegion),
324 ) -> std::result::Result<(), MapMemoryError>;
325
326 fn unmap_memory(
328 &mut self,
329 region: (u32, &MemoryRegion),
330 ) -> std::result::Result<(), UnmapMemoryError>;
331
332 fn run_vcpu(
336 &mut self,
337 #[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
338 ) -> std::result::Result<VmExit, RunVcpuError>;
339
340 #[allow(dead_code)]
342 fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
343 fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
345 #[allow(dead_code)]
347 fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
348 fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
350 #[allow(dead_code)]
352 fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
353 fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
355 #[allow(dead_code)]
357 fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
358 #[allow(dead_code)]
360 fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
361
362 #[allow(dead_code)]
364 #[cfg(not(target_arch = "aarch64"))]
365 fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
366 #[cfg(not(target_arch = "aarch64"))]
368 fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
369 #[cfg(test)]
371 #[cfg(not(target_arch = "aarch64"))]
372 fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
373
374 #[cfg(target_arch = "aarch64")]
376 fn can_reset_vcpu(&self) -> bool {
377 false
378 }
379 #[cfg(target_arch = "aarch64")]
380 fn reset_vcpu(&mut self) -> std::result::Result<(), ResetVcpuError> {
381 Err(ResetVcpuError::NotSupported)
382 }
383 #[cfg(target_os = "windows")]
385 fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
386}
387
388#[cfg(test)]
389mod tests {
390
391 #[test]
392 #[cfg(target_os = "linux")]
394 fn is_hypervisor_present() {
395 use std::path::Path;
396
397 cfg_if::cfg_if! {
398 if #[cfg(all(kvm, mshv3))] {
399 assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
400 } else if #[cfg(kvm)] {
401 assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
402 } else if #[cfg(mshv3)] {
403 assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
404 } else {
405 assert!(!super::is_hypervisor_present());
406 }
407 }
408 }
409}