1#![cfg(all(target_os = "linux", guest_is_native))]
7#![expect(missing_docs)]
8#![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
33pub 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 = "x86_64")]
79 #[error("nested virtualization was requested but the host does not support it")]
80 NestedVirtUnsupported,
81 #[cfg(guest_arch = "x86_64")]
82 #[error("unsupported CPU vendor")]
83 UnsupportedCpuVendor,
84 #[cfg(guest_arch = "x86_64")]
85 #[error("failed to compute topology cpuid")]
86 TopologyCpuid(#[source] virt::x86::topology::UnknownVendor),
87}
88
89#[derive(Inspect)]
90pub struct KvmPartition {
91 #[inspect(flatten)]
92 inner: Arc<KvmPartitionInner>,
93 #[cfg(guest_arch = "x86_64")]
94 #[inspect(skip)]
95 synic_ports: Arc<virt::synic::SynicPorts<KvmPartitionInner>>,
96 #[inspect(skip)]
97 irqfd_state: Arc<gsi::KvmIrqFdState>,
98}
99
100#[derive(Inspect)]
101struct KvmPartitionInner {
102 #[inspect(skip)]
103 kvm: kvm::Partition,
104 #[cfg(guest_arch = "x86_64")]
105 #[inspect(skip)]
106 sev: Option<std::fs::File>,
107 #[cfg(guest_arch = "x86_64")]
108 #[inspect(skip)]
109 snp_config: Option<snp::KvmSnpConfig>,
110 #[cfg(guest_arch = "x86_64")]
111 #[inspect(skip)]
112 snp_launch_state: Mutex<SnpLaunchState>,
113 memory: Mutex<KvmMemoryRangeState>,
114 memory_backing_mode: KvmMemoryBackingMode,
115 #[inspect(iter_by_index)]
116 ram_ranges: Vec<MemoryRange>,
117 hv1_enabled: bool,
118 gm: GuestMemory,
119 #[cfg(guest_arch = "x86_64")]
120 #[inspect(skip)]
121 bsp_cpuid: Vec<kvm::kvm_cpuid_entry2>,
122 #[inspect(skip)]
123 vps: Vec<KvmVpInner>,
124 #[inspect(skip)]
125 gsi_routing: Mutex<gsi::GsiRouting>,
126 caps: virt::PartitionCapabilities,
127
128 #[cfg(guest_arch = "x86_64")]
130 cpuid: virt::CpuidLeafSet,
131
132 #[cfg(guest_arch = "x86_64")]
133 reserved_vps_per_socket: u32,
134
135 #[cfg(guest_arch = "x86_64")]
138 mce_cmci_supported: bool,
139
140 #[cfg(guest_arch = "aarch64")]
142 #[inspect(skip)]
143 _gic_device: kvm::Device,
144 #[cfg(guest_arch = "aarch64")]
146 #[inspect(skip)]
147 _its_device: Option<kvm::Device>,
148 #[cfg(guest_arch = "aarch64")]
150 #[inspect(skip)]
151 gic_msi: vm_topology::processor::aarch64::GicMsiController,
152 #[cfg(guest_arch = "aarch64")]
154 gic_nr_irqs: u32,
155 #[cfg(guest_arch = "x86_64")]
156 synic_ports: virt::synic::SynicPortMap,
157}
158
159#[derive(Debug, Error)]
161enum KvmRunVpError {
162 #[error("KVM internal error: {0:#x}")]
163 InternalError(u32),
164 #[error("invalid vp state")]
165 InvalidVpState,
166 #[error("failed to run VP")]
167 Run(#[source] kvm::Error),
168 #[error("unhandled system event type: {0:#x}")]
169 UnhandledSystemEvent(u32),
170 #[cfg(guest_arch = "x86_64")]
171 #[error("unhandled KVM hypercall: nr={nr:#x}, flags={flags:#x}")]
172 UnhandledHypercall { nr: u64, flags: u64 },
173 #[cfg(guest_arch = "x86_64")]
174 #[error(
175 "SEV guest requested termination: ghcb_msr={ghcb_msr:#x} reason_set={reason_set:#x} reason={reason:#x}"
176 )]
177 SevTermination {
178 ghcb_msr: u64,
179 reason_set: u64,
180 reason: u64,
181 },
182 #[cfg(guest_arch = "x86_64")]
183 #[error("failed to inject an extint interrupt")]
184 ExtintInterrupt(#[source] kvm::Error),
185}
186
187pub struct KvmProcessorBinder {
188 partition: Arc<KvmPartitionInner>,
189 vpindex: VpIndex,
190 vmtime: VmTimeAccess,
191}
192
193impl KvmPartitionInner {
194 #[cfg(guest_arch = "x86_64")]
195 fn bsp(&self) -> &KvmVpInner {
196 &self.vps[0]
197 }
198
199 fn vp(&self, vp_index: VpIndex) -> Option<&KvmVpInner> {
200 self.vps.get(vp_index.index() as usize)
201 }
202
203 fn evaluate_vp(&self, vp_index: VpIndex) {
204 let Some(vp) = self.vp(vp_index) else { return };
205 vp.set_eval(true, Ordering::Relaxed);
206
207 #[cfg(guest_arch = "x86_64")]
208 self.kvm.vp(vp.vp_info().apic_id).force_exit();
209
210 #[cfg(guest_arch = "aarch64")]
211 self.kvm.vp(vp.vp_info().base.vp_index.index()).force_exit();
212 }
213}