Skip to main content

nub_host_kvm/hypervisor/virtual_machine/
mod.rs

1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::fmt::Debug;
18use std::sync::OnceLock;
19
20use tracing::{Span, instrument};
21
22use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
23use crate::mem::memory_region::MemoryRegion;
24
25/// KVM (Kernel-based Virtual Machine) functionality (linux)
26#[cfg(kvm)]
27pub(crate) mod kvm;
28
29static AVAILABLE_HYPERVISOR: OnceLock<Option<HypervisorType>> = OnceLock::new();
30
31/// Returns which type of hypervisor is available, if any
32pub fn get_available_hypervisor() -> &'static Option<HypervisorType> {
33    AVAILABLE_HYPERVISOR.get_or_init(|| {
34        #[cfg(kvm)]
35        {
36            if kvm::is_hypervisor_present() {
37                Some(HypervisorType::Kvm)
38            } else {
39                None
40            }
41        }
42        #[cfg(not(kvm))]
43        {
44            None
45        }
46    })
47}
48
49/// Returns `true` if a suitable hypervisor is available.
50/// If this returns `false`, no hypervisor-backed sandboxes can be created.
51#[instrument(skip_all, parent = Span::current())]
52pub fn is_hypervisor_present() -> bool {
53    get_available_hypervisor().is_some()
54}
55
56/// The hypervisor types available for the current platform
57#[derive(PartialEq, Eq, Debug, Copy, Clone)]
58pub(crate) enum HypervisorType {
59    #[cfg(kvm)]
60    Kvm,
61}
62
63// Compiler error if the kvm feature is disabled — there is no other hypervisor backend.
64#[cfg(not(kvm))]
65compile_error!(
66    "No hypervisor type is available for the current platform. Please enable the `kvm` cargo feature."
67);
68
69/// The various reasons a VM's vCPU can exit
70pub(crate) enum VmExit {
71    /// The vCPU has halted
72    Halt(),
73    /// The vCPU has issued a write to the given port with the given value
74    IoOut(u16, Vec<u8>),
75    /// The vCPU tried to read from the given (unmapped) addr
76    MmioRead(u64),
77    /// The vCPU tried to write to the given (unmapped) addr
78    MmioWrite(u64),
79    /// The vCPU execution has been cancelled
80    Cancelled(),
81    /// The vCPU has exited for a reason that is not handled by Hyperlight
82    Unknown(String),
83    /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN
84    Retry(),
85}
86
87/// Stable index of a vCPU in the VM's fixed vCPU pool.
88#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
89pub(crate) struct VcpuLane(usize);
90
91impl VcpuLane {
92    /// The legacy control lane used by the existing host dispatch path.
93    pub(crate) const PRIMARY: Self = Self(0);
94
95    pub(crate) fn new(index: usize) -> Self {
96        Self(index)
97    }
98
99    pub(crate) fn index(self) -> usize {
100        self.0
101    }
102}
103
104/// VM error
105#[derive(Debug, Clone, thiserror::Error)]
106pub enum VmError {
107    #[error("Failed to create vm: {0}")]
108    CreateVm(#[from] CreateVmError),
109    #[error("Map memory operation failed: {0}")]
110    MapMemory(#[from] MapMemoryError),
111    #[error("Register operation failed: {0}")]
112    Register(#[from] RegisterError),
113    #[error("Failed to run vcpu: {0}")]
114    RunVcpu(#[from] RunVcpuError),
115    #[error("Unmap memory operation failed: {0}")]
116    UnmapMemory(#[from] UnmapMemoryError),
117}
118
119/// Create VM error
120#[derive(Debug, Clone, thiserror::Error)]
121pub enum CreateVmError {
122    #[error("VCPU creation failed: {0}")]
123    CreateVcpuFd(HypervisorError),
124    #[error("VM creation failed: {0}")]
125    CreateVmFd(HypervisorError),
126    #[error("Hypervisor is not available: {0}")]
127    HypervisorNotAvailable(HypervisorError),
128    #[error("Initialize VM failed: {0}")]
129    InitializeVm(HypervisorError),
130    #[error("Set Partition Property failed: {0}")]
131    SetPartitionProperty(HypervisorError),
132}
133
134/// RunVCPU error
135#[derive(Debug, Clone, thiserror::Error)]
136pub enum RunVcpuError {
137    #[error("Invalid vCPU lane: {0}")]
138    InvalidVcpuLane(usize),
139    #[error("vCPU lane lock poisoned: {0}")]
140    VcpuLanePoisoned(usize),
141    #[error("Failed to decode message type: {0}")]
142    DecodeIOMessage(u32),
143    #[error("Increment RIP failed: {0}")]
144    IncrementRip(HypervisorError),
145    #[error("Parse GPA access info failed")]
146    ParseGpaAccessInfo,
147    #[error("Unknown error: {0}")]
148    Unknown(HypervisorError),
149}
150
151/// Register error
152#[derive(Debug, Clone, thiserror::Error)]
153pub enum RegisterError {
154    #[error("Invalid vCPU lane: {0}")]
155    InvalidVcpuLane(usize),
156    #[error("vCPU lane lock poisoned: {0}")]
157    VcpuLanePoisoned(usize),
158    #[error("Failed to get registers: {0}")]
159    GetRegs(HypervisorError),
160    #[error("Failed to set registers: {0}")]
161    SetRegs(HypervisorError),
162    #[error("Failed to set FPU registers: {0}")]
163    SetFpu(HypervisorError),
164    #[error("Failed to set special registers: {0}")]
165    SetSregs(HypervisorError),
166}
167
168/// Map memory error
169#[derive(Debug, Clone, thiserror::Error)]
170pub enum MapMemoryError {
171    #[error("Hypervisor error: {0}")]
172    Hypervisor(HypervisorError),
173}
174
175/// Unmap memory error
176#[derive(Debug, Clone, thiserror::Error)]
177pub enum UnmapMemoryError {
178    #[error("Hypervisor error: {0}")]
179    Hypervisor(HypervisorError),
180}
181
182/// Implementation-specific Hypervisor error
183#[derive(Debug, Clone, thiserror::Error)]
184pub enum HypervisorError {
185    #[cfg(kvm)]
186    #[error("KVM error: {0}")]
187    KvmError(#[from] kvm_ioctls::Error),
188}
189
190/// Common interface for a VM with a fixed vCPU pool.
191pub(crate) trait VirtualMachine: Debug + Send + Sync {
192    /// Map memory region into this VM
193    ///
194    /// # Safety
195    /// The caller must ensure that the memory region is valid and points to valid memory,
196    /// and lives long enough for the VM to use it.
197    /// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped
198    /// memory regions may be overwritten.
199    /// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries.
200    unsafe fn map_memory(
201        &mut self,
202        region: (u32, &MemoryRegion),
203    ) -> std::result::Result<(), MapMemoryError>;
204
205    /// Number of vCPU lanes created for this VM.
206    fn vcpu_count(&self) -> usize;
207
208    /// Runs the selected vCPU until it exits.
209    /// Note: this function emits traces spans for guests;
210    /// the span setup is called right before the KVM run-vcpu ioctl.
211    fn run_vcpu_on(&self, lane: VcpuLane) -> std::result::Result<VmExit, RunVcpuError>;
212
213    /// Runs the primary control vCPU until it exits.
214    fn run_vcpu(&self) -> std::result::Result<VmExit, RunVcpuError> {
215        self.run_vcpu_on(VcpuLane::PRIMARY)
216    }
217
218    /// Get regs
219    fn regs_on(&self, lane: VcpuLane) -> std::result::Result<CommonRegisters, RegisterError>;
220
221    /// Get regs on the primary control vCPU.
222    fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError> {
223        self.regs_on(VcpuLane::PRIMARY)
224    }
225
226    /// Set regs
227    fn set_regs_on(
228        &self,
229        lane: VcpuLane,
230        regs: &CommonRegisters,
231    ) -> std::result::Result<(), RegisterError>;
232
233    /// Set regs on the primary control vCPU.
234    fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError> {
235        self.set_regs_on(VcpuLane::PRIMARY, regs)
236    }
237
238    /// Set fpu regs
239    fn set_fpu_on(&self, lane: VcpuLane, fpu: &CommonFpu)
240    -> std::result::Result<(), RegisterError>;
241
242    /// Set fpu regs on the primary control vCPU.
243    fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError> {
244        self.set_fpu_on(VcpuLane::PRIMARY, fpu)
245    }
246
247    /// Set special regs
248    fn set_sregs_on(
249        &self,
250        lane: VcpuLane,
251        sregs: &CommonSpecialRegisters,
252    ) -> std::result::Result<(), RegisterError>;
253
254    /// Set special regs on the primary control vCPU.
255    fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError> {
256        self.set_sregs_on(VcpuLane::PRIMARY, sregs)
257    }
258
259    /// Get special regs
260    fn sregs_on(
261        &self,
262        lane: VcpuLane,
263    ) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
264
265    /// Get special regs on the primary control vCPU.
266    fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError> {
267        self.sregs_on(VcpuLane::PRIMARY)
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    #[test]
274    #[cfg(kvm)]
275    fn is_hypervisor_present() {
276        use std::path::Path;
277        assert_eq!(
278            Path::new("/dev/kvm").exists(),
279            super::is_hypervisor_present()
280        );
281    }
282}