Skip to main content

virt_kvm/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! KVM implementation of the virt::generic interfaces.
5
6#![cfg(all(target_os = "linux", guest_is_native))]
7#![expect(missing_docs)]
8// UNSAFETY: Calling KVM APIs and manually managing memory.
9#![expect(unsafe_code)]
10#![expect(clippy::undocumented_unsafe_blocks)]
11
12mod arch;
13mod gsi;
14mod memory;
15
16pub use arch::Kvm;
17
18use guestmem::GuestMemory;
19use inspect::Inspect;
20use memory::KvmMemoryBackingMode;
21use memory::KvmMemoryRangeState;
22use memory_range::MemoryRange;
23use parking_lot::Mutex;
24use std::sync::Arc;
25use thiserror::Error;
26use virt::state::StateError;
27
28/// Returns whether KVM is available on this machine.
29pub fn is_available() -> Result<bool, KvmError> {
30    match std::fs::metadata("/dev/kvm") {
31        Ok(_) => Ok(true),
32        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
33        Err(err) => Err(KvmError::AvailableCheck(err)),
34    }
35}
36
37use arch::KvmVpInner;
38use std::sync::atomic::Ordering;
39use virt::VpIndex;
40use vmcore::vmtime::VmTimeAccess;
41
42#[derive(Error, Debug)]
43pub enum KvmError {
44    #[error("operation not supported")]
45    NotSupported,
46    #[error("vtl2 is not supported on this hypervisor")]
47    Vtl2NotSupported,
48    #[error("isolation is not supported on this hypervisor")]
49    IsolationNotSupported,
50    #[error("kvm error")]
51    Kvm(#[from] kvm::Error),
52    #[error("failed to stat /dev/kvm")]
53    AvailableCheck(#[source] std::io::Error),
54    #[error(transparent)]
55    State(#[from] Box<StateError<KvmError>>),
56    #[error("invalid state while restoring: {0}")]
57    InvalidState(&'static str),
58    #[error("unsupported isolation configuration: {0}")]
59    UnsupportedIsolationConfiguration(&'static str),
60    #[error("cannot resize KVM guest_memfd memory slot")]
61    CannotResizeGuestMemfdSlot,
62    #[error("private memory range is not contained in guest_memfd private memory")]
63    InvalidPrivateMemoryRange,
64    #[error("misaligned gic base address")]
65    Misaligned,
66    #[error("host does not support GICv2 or GICv3")]
67    NoGic,
68    #[error("host does not support required cpu capabilities")]
69    Capabilities(virt::PartitionCapabilitiesError),
70    #[cfg(guest_arch = "x86_64")]
71    #[error("nested virtualization was requested but the host does not support it")]
72    NestedVirtUnsupported,
73    #[cfg(guest_arch = "x86_64")]
74    #[error("unsupported CPU vendor")]
75    UnsupportedCpuVendor,
76    #[cfg(guest_arch = "x86_64")]
77    #[error("failed to compute topology cpuid")]
78    TopologyCpuid(#[source] virt::x86::topology::UnknownVendor),
79}
80
81#[derive(Inspect)]
82pub struct KvmPartition {
83    #[inspect(flatten)]
84    inner: Arc<KvmPartitionInner>,
85    #[cfg(guest_arch = "x86_64")]
86    #[inspect(skip)]
87    synic_ports: Arc<virt::synic::SynicPorts<KvmPartitionInner>>,
88    #[inspect(skip)]
89    irqfd_state: Arc<gsi::KvmIrqFdState>,
90}
91
92#[derive(Inspect)]
93struct KvmPartitionInner {
94    #[inspect(skip)]
95    kvm: kvm::Partition,
96    memory: Mutex<KvmMemoryRangeState>,
97    memory_backing_mode: KvmMemoryBackingMode,
98    #[inspect(iter_by_index)]
99    ram_ranges: Vec<MemoryRange>,
100    hv1_enabled: bool,
101    gm: GuestMemory,
102    #[inspect(skip)]
103    vps: Vec<KvmVpInner>,
104    #[inspect(skip)]
105    gsi_routing: Mutex<gsi::GsiRouting>,
106    caps: virt::PartitionCapabilities,
107
108    // This is used for debugging via Inspect
109    #[cfg(guest_arch = "x86_64")]
110    cpuid: virt::CpuidLeafSet,
111
112    #[cfg(guest_arch = "x86_64")]
113    reserved_vps_per_socket: u32,
114
115    /// Whether the host allows advertising `MCG_CMCI_P` in the guest's
116    /// `IA32_MCG_CAP` (required for KVM to expose the CMCI LVT register).
117    #[cfg(guest_arch = "x86_64")]
118    mce_cmci_supported: bool,
119
120    /// The GIC device fd, kept alive for the VM lifetime.
121    #[cfg(guest_arch = "aarch64")]
122    #[inspect(skip)]
123    _gic_device: kvm::Device,
124    /// The ITS device fd, kept alive for the VM lifetime.
125    #[cfg(guest_arch = "aarch64")]
126    #[inspect(skip)]
127    _its_device: Option<kvm::Device>,
128    /// MSI controller configuration (v2m, ITS, or none).
129    #[cfg(guest_arch = "aarch64")]
130    #[inspect(skip)]
131    gic_msi: vm_topology::processor::aarch64::GicMsiController,
132    /// Total configured GIC interrupt count (SGIs + PPIs + SPIs).
133    #[cfg(guest_arch = "aarch64")]
134    gic_nr_irqs: u32,
135    #[cfg(guest_arch = "x86_64")]
136    synic_ports: virt::synic::SynicPortMap,
137}
138
139// TODO: Chunk this up into smaller types.
140#[derive(Debug, Error)]
141enum KvmRunVpError {
142    #[error("KVM internal error: {0:#x}")]
143    InternalError(u32),
144    #[error("invalid vp state")]
145    InvalidVpState,
146    #[error("failed to run VP")]
147    Run(#[source] kvm::Error),
148    #[error("unhandled system event type: {0:#x}")]
149    UnhandledSystemEvent(u32),
150    #[cfg(guest_arch = "x86_64")]
151    #[error("unhandled KVM hypercall: nr={nr:#x}, flags={flags:#x}")]
152    UnhandledHypercall { nr: u64, flags: u64 },
153    #[cfg(guest_arch = "x86_64")]
154    #[error("failed to inject an extint interrupt")]
155    ExtintInterrupt(#[source] kvm::Error),
156}
157
158pub struct KvmProcessorBinder {
159    partition: Arc<KvmPartitionInner>,
160    vpindex: VpIndex,
161    vmtime: VmTimeAccess,
162}
163
164impl KvmPartitionInner {
165    #[cfg(guest_arch = "x86_64")]
166    fn bsp(&self) -> &KvmVpInner {
167        &self.vps[0]
168    }
169
170    fn vp(&self, vp_index: VpIndex) -> Option<&KvmVpInner> {
171        self.vps.get(vp_index.index() as usize)
172    }
173
174    fn evaluate_vp(&self, vp_index: VpIndex) {
175        let Some(vp) = self.vp(vp_index) else { return };
176        vp.set_eval(true, Ordering::Relaxed);
177
178        #[cfg(guest_arch = "x86_64")]
179        self.kvm.vp(vp.vp_info().apic_id).force_exit();
180
181        #[cfg(guest_arch = "aarch64")]
182        self.kvm.vp(vp.vp_info().base.vp_index.index()).force_exit();
183    }
184}