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(any(mshv3, target_os = "windows"))]
111pub(crate) const XSAVE_MIN_SIZE: usize = 576;
112
113#[cfg(all(any(kvm, mshv3), test, not(feature = "nanvix-unstable")))]
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 #[error("Unknown error: {0}")]
206 Unknown(HypervisorError),
207}
208
209#[derive(Debug, Clone, thiserror::Error)]
211pub enum RegisterError {
212 #[error("Failed to get registers: {0}")]
213 GetRegs(HypervisorError),
214 #[error("Failed to set registers: {0}")]
215 SetRegs(HypervisorError),
216 #[error("Failed to get FPU registers: {0}")]
217 GetFpu(HypervisorError),
218 #[error("Failed to set FPU registers: {0}")]
219 SetFpu(HypervisorError),
220 #[error("Failed to get special registers: {0}")]
221 GetSregs(HypervisorError),
222 #[error("Failed to set special registers: {0}")]
223 SetSregs(HypervisorError),
224 #[error("Failed to get debug registers: {0}")]
225 GetDebugRegs(HypervisorError),
226 #[error("Failed to set debug registers: {0}")]
227 SetDebugRegs(HypervisorError),
228 #[error("Failed to get xsave: {0}")]
229 GetXsave(HypervisorError),
230 #[error("Failed to set xsave: {0}")]
231 SetXsave(HypervisorError),
232 #[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
233 XsaveSizeMismatch {
234 expected: u32,
236 actual: u32,
238 },
239 #[error("Invalid xsave alignment")]
240 InvalidXsaveAlignment,
241 #[cfg(target_os = "windows")]
242 #[error("Failed to get xsave size: {0}")]
243 GetXsaveSize(#[from] HypervisorError),
244 #[cfg(target_os = "windows")]
245 #[error("Failed to convert WHP registers: {0}")]
246 ConversionFailed(String),
247}
248
249#[derive(Debug, Clone, thiserror::Error)]
251pub enum MapMemoryError {
252 #[cfg(target_os = "windows")]
253 #[error("Address conversion failed: {0}")]
254 AddressConversion(std::num::TryFromIntError),
255 #[error("Hypervisor error: {0}")]
256 Hypervisor(HypervisorError),
257 #[cfg(target_os = "windows")]
258 #[error("Invalid memory region flags: {0}")]
259 InvalidFlags(String),
260 #[cfg(target_os = "windows")]
261 #[error("Failed to load API '{api_name}': {source}")]
262 LoadApi {
263 api_name: &'static str,
264 source: windows_result::Error,
265 },
266 #[cfg(target_os = "windows")]
267 #[error("Operation not supported: {0}")]
268 NotSupported(String),
269 #[cfg(target_os = "windows")]
270 #[error("Surrogate process creation failed: {0}")]
271 SurrogateProcess(String),
272}
273
274#[derive(Debug, Clone, thiserror::Error)]
276pub enum UnmapMemoryError {
277 #[error("Hypervisor error: {0}")]
278 Hypervisor(HypervisorError),
279}
280
281#[derive(Debug, Clone, thiserror::Error)]
283pub enum HypervisorError {
284 #[cfg(kvm)]
285 #[error("KVM error: {0}")]
286 KvmError(#[from] kvm_ioctls::Error),
287 #[cfg(mshv3)]
288 #[error("MSHV error: {0}")]
289 MshvError(#[from] mshv_ioctls::MshvError),
290 #[cfg(target_os = "windows")]
291 #[error("Windows error: {0}")]
292 WindowsError(#[from] windows_result::Error),
293}
294
295pub(crate) trait VirtualMachine: Debug + Send {
298 unsafe fn map_memory(
307 &mut self,
308 region: (u32, &MemoryRegion),
309 ) -> std::result::Result<(), MapMemoryError>;
310
311 fn unmap_memory(
313 &mut self,
314 region: (u32, &MemoryRegion),
315 ) -> std::result::Result<(), UnmapMemoryError>;
316
317 fn run_vcpu(
321 &mut self,
322 #[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
323 ) -> std::result::Result<VmExit, RunVcpuError>;
324
325 #[allow(dead_code)]
327 fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
328 fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
330 #[allow(dead_code)]
332 fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
333 fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
335 #[allow(dead_code)]
337 fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
338 fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
340 #[allow(dead_code)]
342 fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
343 fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
345
346 #[allow(dead_code)]
348 fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
349 fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
351 #[cfg(test)]
353 #[cfg(not(feature = "nanvix-unstable"))]
354 fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
355
356 #[cfg(target_os = "windows")]
358 fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
359}
360
361#[cfg(test)]
362mod tests {
363
364 #[test]
365 #[cfg(target_os = "linux")]
367 fn is_hypervisor_present() {
368 use std::path::Path;
369
370 cfg_if::cfg_if! {
371 if #[cfg(all(kvm, mshv3))] {
372 assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
373 } else if #[cfg(kvm)] {
374 assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
375 } else if #[cfg(mshv3)] {
376 assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
377 } else {
378 assert!(!super::is_hypervisor_present());
379 }
380 }
381 }
382}