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
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
28pub 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 #[cfg(guest_arch = "x86_64")]
110 cpuid: virt::CpuidLeafSet,
111
112 #[cfg(guest_arch = "x86_64")]
113 reserved_vps_per_socket: u32,
114
115 #[cfg(guest_arch = "aarch64")]
117 #[inspect(skip)]
118 _gic_device: kvm::Device,
119 #[cfg(guest_arch = "aarch64")]
121 #[inspect(skip)]
122 _its_device: Option<kvm::Device>,
123 #[cfg(guest_arch = "aarch64")]
125 #[inspect(skip)]
126 gic_msi: vm_topology::processor::aarch64::GicMsiController,
127 #[cfg(guest_arch = "aarch64")]
129 gic_nr_irqs: u32,
130 #[cfg(guest_arch = "x86_64")]
131 synic_ports: virt::synic::SynicPortMap,
132}
133
134#[derive(Debug, Error)]
136enum KvmRunVpError {
137 #[error("KVM internal error: {0:#x}")]
138 InternalError(u32),
139 #[error("invalid vp state")]
140 InvalidVpState,
141 #[error("failed to run VP")]
142 Run(#[source] kvm::Error),
143 #[error("unhandled system event type: {0:#x}")]
144 UnhandledSystemEvent(u32),
145 #[cfg(guest_arch = "x86_64")]
146 #[error("unhandled KVM hypercall: nr={nr:#x}, flags={flags:#x}")]
147 UnhandledHypercall { nr: u64, flags: u64 },
148 #[cfg(guest_arch = "x86_64")]
149 #[error("failed to inject an extint interrupt")]
150 ExtintInterrupt(#[source] kvm::Error),
151}
152
153pub struct KvmProcessorBinder {
154 partition: Arc<KvmPartitionInner>,
155 vpindex: VpIndex,
156 vmtime: VmTimeAccess,
157}
158
159impl KvmPartitionInner {
160 #[cfg(guest_arch = "x86_64")]
161 fn bsp(&self) -> &KvmVpInner {
162 &self.vps[0]
163 }
164
165 fn vp(&self, vp_index: VpIndex) -> Option<&KvmVpInner> {
166 self.vps.get(vp_index.index() as usize)
167 }
168
169 fn evaluate_vp(&self, vp_index: VpIndex) {
170 let Some(vp) = self.vp(vp_index) else { return };
171 vp.set_eval(true, Ordering::Relaxed);
172
173 #[cfg(guest_arch = "x86_64")]
174 self.kvm.vp(vp.vp_info().apic_id).force_exit();
175
176 #[cfg(guest_arch = "aarch64")]
177 self.kvm.vp(vp.vp_info().base.vp_index.index()).force_exit();
178 }
179}