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#[cfg(guest_arch = "x86_64")]
16mod snp;
17
18pub use arch::Kvm;
19pub use memory::MemoryError;
20#[cfg(guest_arch = "x86_64")]
21pub use snp::SnpError;
22
23use guestmem::GuestMemory;
24use inspect::Inspect;
25use memory::KvmMemoryBackingMode;
26use memory::KvmMemoryRangeState;
27use memory_range::MemoryRange;
28use parking_lot::Mutex;
29use std::sync::Arc;
30use thiserror::Error;
31use virt::state::StateError;
32
33/// Returns whether KVM is available on this machine.
34pub fn is_available() -> Result<bool, KvmError> {
35    match std::fs::metadata("/dev/kvm") {
36        Ok(_) => Ok(true),
37        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
38        Err(err) => Err(KvmError::AvailableCheck(err)),
39    }
40}
41
42use arch::KvmVpInner;
43#[cfg(guest_arch = "x86_64")]
44use snp::SnpLaunchState;
45use std::sync::atomic::Ordering;
46use virt::VpIndex;
47use vmcore::vmtime::VmTimeAccess;
48
49#[derive(Error, Debug)]
50pub enum KvmError {
51    #[error("operation not supported")]
52    NotSupported,
53    #[error("vtl2 is not supported on this hypervisor")]
54    Vtl2NotSupported,
55    #[error("isolation is not supported on this hypervisor")]
56    IsolationNotSupported,
57    #[error("kvm error")]
58    Kvm(#[from] kvm::Error),
59    #[error(transparent)]
60    Memory(#[from] MemoryError),
61    #[cfg(guest_arch = "x86_64")]
62    #[error(transparent)]
63    Snp(#[from] SnpError),
64    #[error("failed to stat /dev/kvm")]
65    AvailableCheck(#[source] std::io::Error),
66    #[error(transparent)]
67    State(#[from] Box<StateError<KvmError>>),
68    #[error("invalid state while restoring: {0}")]
69    InvalidState(&'static str),
70    #[error("unsupported isolation configuration: {0}")]
71    UnsupportedIsolationConfiguration(&'static str),
72    #[error("misaligned gic base address")]
73    Misaligned,
74    #[error("host does not support GICv2 or GICv3")]
75    NoGic,
76    #[error("host does not support required cpu capabilities")]
77    Capabilities(virt::PartitionCapabilitiesError),
78    #[cfg(guest_arch = "aarch64")]
79    #[error("failed to set MPIDR_EL1 for VP {vp_index}")]
80    SetMpidr {
81        vp_index: u32,
82        #[source]
83        err: kvm::Error,
84    },
85    #[cfg(guest_arch = "x86_64")]
86    #[error("nested virtualization was requested but the host does not support it")]
87    NestedVirtUnsupported,
88    #[cfg(guest_arch = "x86_64")]
89    #[error("unsupported CPU vendor")]
90    UnsupportedCpuVendor,
91    #[cfg(guest_arch = "x86_64")]
92    #[error("failed to compute topology cpuid")]
93    TopologyCpuid(#[source] virt::x86::topology::UnknownVendor),
94}
95
96#[derive(Inspect)]
97pub struct KvmPartition {
98    #[inspect(flatten)]
99    inner: Arc<KvmPartitionInner>,
100    #[cfg(guest_arch = "x86_64")]
101    #[inspect(skip)]
102    synic_ports: Arc<virt::synic::SynicPorts<KvmPartitionInner>>,
103    #[inspect(skip)]
104    irqfd_state: Arc<gsi::KvmIrqFdState>,
105}
106
107#[derive(Inspect)]
108struct KvmPartitionInner {
109    #[inspect(skip)]
110    kvm: kvm::Partition,
111    #[cfg(guest_arch = "x86_64")]
112    #[inspect(skip)]
113    sev: Option<std::fs::File>,
114    #[cfg(guest_arch = "x86_64")]
115    #[inspect(skip)]
116    snp_config: Option<snp::KvmSnpConfig>,
117    #[cfg(guest_arch = "x86_64")]
118    #[inspect(skip)]
119    snp_launch_state: Mutex<SnpLaunchState>,
120    memory: Mutex<KvmMemoryRangeState>,
121    memory_backing_mode: KvmMemoryBackingMode,
122    #[inspect(iter_by_index)]
123    ram_ranges: Vec<MemoryRange>,
124    hv1_enabled: bool,
125    gm: GuestMemory,
126    #[cfg(guest_arch = "x86_64")]
127    #[inspect(skip)]
128    bsp_cpuid: Vec<kvm::kvm_cpuid_entry2>,
129    #[inspect(skip)]
130    vps: Vec<KvmVpInner>,
131    #[inspect(skip)]
132    gsi_routing: Mutex<gsi::GsiRouting>,
133    caps: virt::PartitionCapabilities,
134
135    // This is used for debugging via Inspect
136    #[cfg(guest_arch = "x86_64")]
137    cpuid: virt::CpuidLeafSet,
138
139    #[cfg(guest_arch = "x86_64")]
140    reserved_vps_per_socket: u32,
141
142    /// Whether the host allows advertising `MCG_CMCI_P` in the guest's
143    /// `IA32_MCG_CAP` (required for KVM to expose the CMCI LVT register).
144    #[cfg(guest_arch = "x86_64")]
145    mce_cmci_supported: bool,
146
147    /// The GIC device fd, kept alive for the VM lifetime.
148    #[cfg(guest_arch = "aarch64")]
149    #[inspect(skip)]
150    _gic_device: kvm::Device,
151    /// The ITS device fd, kept alive for the VM lifetime.
152    #[cfg(guest_arch = "aarch64")]
153    #[inspect(skip)]
154    _its_device: Option<kvm::Device>,
155    /// MSI controller configuration (v2m, ITS, or none).
156    #[cfg(guest_arch = "aarch64")]
157    #[inspect(skip)]
158    gic_msi: vm_topology::processor::aarch64::GicMsiController,
159    /// Total configured GIC interrupt count (SGIs + PPIs + SPIs).
160    #[cfg(guest_arch = "aarch64")]
161    gic_nr_irqs: u32,
162    #[cfg(guest_arch = "x86_64")]
163    synic_ports: virt::synic::SynicPortMap,
164}
165
166// TODO: Chunk this up into smaller types.
167#[derive(Debug, Error)]
168enum KvmRunVpError {
169    #[error("KVM internal error: {0:#x}")]
170    InternalError(u32),
171    #[error("invalid vp state")]
172    InvalidVpState,
173    #[error("failed to run VP")]
174    Run(#[source] kvm::Error),
175    #[error("unhandled system event type: {0:#x}")]
176    UnhandledSystemEvent(u32),
177    #[cfg(guest_arch = "x86_64")]
178    #[error("unhandled KVM hypercall: nr={nr:#x}, flags={flags:#x}")]
179    UnhandledHypercall { nr: u64, flags: u64 },
180    #[cfg(guest_arch = "x86_64")]
181    #[error(
182        "SEV guest requested termination: ghcb_msr={ghcb_msr:#x} reason_set={reason_set:#x} reason={reason:#x}"
183    )]
184    SevTermination {
185        ghcb_msr: u64,
186        reason_set: u64,
187        reason: u64,
188    },
189    #[cfg(guest_arch = "x86_64")]
190    #[error("failed to inject an extint interrupt")]
191    ExtintInterrupt(#[source] kvm::Error),
192}
193
194pub struct KvmProcessorBinder {
195    partition: Arc<KvmPartitionInner>,
196    vpindex: VpIndex,
197    vmtime: VmTimeAccess,
198}
199
200impl KvmPartitionInner {
201    #[cfg(guest_arch = "x86_64")]
202    fn bsp(&self) -> &KvmVpInner {
203        &self.vps[0]
204    }
205
206    fn vp(&self, vp_index: VpIndex) -> Option<&KvmVpInner> {
207        self.vps.get(vp_index.index() as usize)
208    }
209
210    fn evaluate_vp(&self, vp_index: VpIndex) {
211        let Some(vp) = self.vp(vp_index) else { return };
212        vp.set_eval(true, Ordering::Relaxed);
213
214        #[cfg(guest_arch = "x86_64")]
215        self.kvm.vp(vp.vp_info().apic_id).force_exit();
216
217        #[cfg(guest_arch = "aarch64")]
218        self.kvm.vp(vp.vp_info().base.vp_index.index()).force_exit();
219    }
220}