Skip to main content

arcbox_hypervisor/linux/vm/
mod.rs

1//! Virtual machine implementation for Linux KVM.
2
3mod dirty;
4mod drop;
5mod irq;
6#[cfg(test)]
7mod tests;
8mod virtio;
9mod virtual_machine;
10
11use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
12use std::sync::{Arc, RwLock};
13
14use crate::{config::VmConfig, error::HypervisorError};
15
16#[cfg(target_arch = "x86_64")]
17use super::ffi::KvmPitConfig;
18use super::ffi::{self, KvmSystem, KvmUserspaceMemoryRegion, KvmVmFd};
19use super::memory::KvmMemory;
20pub use virtio::VirtioDeviceInfo;
21
22/// Global VM ID counter.
23static VM_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
24
25/// Information about a memory slot for dirty page tracking.
26#[derive(Debug, Clone)]
27pub(super) struct MemorySlotInfo {
28    /// Slot ID.
29    slot: u32,
30    /// Guest physical address.
31    guest_phys_addr: u64,
32    /// Size in bytes.
33    size: u64,
34    /// Host virtual address.
35    userspace_addr: u64,
36}
37
38/// Virtual machine state.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum VmState {
41    /// VM is created but not started.
42    Created,
43    /// VM is starting.
44    Starting,
45    /// VM is running.
46    Running,
47    /// VM is paused.
48    Paused,
49    /// VM is stopping.
50    Stopping,
51    /// VM is stopped.
52    Stopped,
53    /// VM encountered an error.
54    Error,
55}
56
57/// Virtual machine implementation for Linux KVM.
58///
59/// This wraps a KVM VM and provides the platform-agnostic interface.
60pub struct KvmVm {
61    /// Unique VM ID.
62    id: u64,
63    /// VM configuration.
64    config: VmConfig,
65    /// KVM system handle.
66    #[allow(dead_code)]
67    kvm: Arc<KvmSystem>,
68    /// KVM VM file descriptor.
69    vm_fd: KvmVmFd,
70    /// vCPU mmap size.
71    vcpu_mmap_size: usize,
72    /// Guest memory.
73    memory: KvmMemory,
74    /// Next memory slot ID.
75    next_slot: AtomicU32,
76    /// Created vCPU IDs.
77    vcpus: RwLock<Vec<u32>>,
78    /// Current state.
79    state: RwLock<VmState>,
80    /// Whether the VM is running.
81    running: AtomicBool,
82    /// Attached VirtIO devices.
83    virtio_devices: RwLock<Vec<VirtioDeviceInfo>>,
84    /// Next VirtIO IRQ offset.
85    next_virtio_irq: AtomicU32,
86    /// Memory slots for dirty page tracking.
87    memory_slots: RwLock<Vec<MemorySlotInfo>>,
88    /// Whether dirty page tracking is enabled.
89    dirty_tracking_enabled: AtomicBool,
90}
91
92// Safety: All mutable state is properly synchronized.
93unsafe impl Send for KvmVm {}
94unsafe impl Sync for KvmVm {}
95
96impl KvmVm {
97    /// Creates a new KVM VM.
98    pub(crate) fn new(
99        kvm: Arc<KvmSystem>,
100        vcpu_mmap_size: usize,
101        config: VmConfig,
102    ) -> Result<Self, HypervisorError> {
103        let id = VM_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
104
105        // Create the VM
106        let vm_fd = kvm.create_vm().map_err(|e| {
107            HypervisorError::VmCreationFailed(format!("Failed to create KVM VM: {}", e))
108        })?;
109
110        // Setup architecture-specific components
111        #[cfg(target_arch = "x86_64")]
112        Self::setup_x86_vm(&vm_fd)?;
113
114        // Allocate guest memory
115        let memory = KvmMemory::new(config.memory_size)?;
116
117        // Map memory to the VM
118        let region = KvmUserspaceMemoryRegion {
119            slot: 0,
120            flags: 0,
121            guest_phys_addr: 0,
122            memory_size: config.memory_size,
123            userspace_addr: memory.host_address() as u64,
124        };
125
126        vm_fd.set_user_memory_region(&region).map_err(|e| {
127            HypervisorError::VmCreationFailed(format!("Failed to map guest memory: {}", e))
128        })?;
129
130        // Track the main memory slot for dirty page tracking.
131        let main_slot = MemorySlotInfo {
132            slot: 0,
133            guest_phys_addr: 0,
134            size: config.memory_size,
135            userspace_addr: memory.host_address() as u64,
136        };
137
138        memory.attach_vm_fd(vm_fd.as_raw_fd());
139        memory.register_slot(
140            main_slot.slot,
141            main_slot.guest_phys_addr,
142            main_slot.size,
143            main_slot.userspace_addr,
144            0,
145        )?;
146
147        tracing::info!(
148            "Created KVM VM {}: vcpus={}, memory={}MB",
149            id,
150            config.vcpu_count,
151            config.memory_size / (1024 * 1024)
152        );
153
154        Ok(Self {
155            id,
156            config,
157            kvm,
158            vm_fd,
159            vcpu_mmap_size,
160            memory,
161            next_slot: AtomicU32::new(1), // Slot 0 is used for main memory
162            vcpus: RwLock::new(Vec::new()),
163            state: RwLock::new(VmState::Created),
164            running: AtomicBool::new(false),
165            virtio_devices: RwLock::new(Vec::new()),
166            next_virtio_irq: AtomicU32::new(0),
167            memory_slots: RwLock::new(vec![main_slot]),
168            dirty_tracking_enabled: AtomicBool::new(false),
169        })
170    }
171
172    /// Sets up x86-specific VM components.
173    #[cfg(target_arch = "x86_64")]
174    fn setup_x86_vm(vm_fd: &KvmVmFd) -> Result<(), HypervisorError> {
175        // Set TSS address (required for Intel VT-x)
176        // The TSS is placed at the end of the 4GB space to avoid conflicts
177        const TSS_ADDR: u64 = 0xfffb_d000;
178        vm_fd
179            .set_tss_addr(TSS_ADDR)
180            .map_err(|e| HypervisorError::VmCreationFailed(format!("Failed to set TSS: {}", e)))?;
181
182        // Set identity map address
183        const IDENTITY_MAP_ADDR: u64 = 0xfffb_c000;
184        vm_fd
185            .set_identity_map_addr(IDENTITY_MAP_ADDR)
186            .map_err(|e| {
187                HypervisorError::VmCreationFailed(format!("Failed to set identity map: {}", e))
188            })?;
189
190        // Create in-kernel IRQ chip (APIC, IOAPIC, PIC)
191        vm_fd.create_irqchip().map_err(|e| {
192            HypervisorError::VmCreationFailed(format!("Failed to create IRQ chip: {}", e))
193        })?;
194
195        // Create PIT (Programmable Interval Timer)
196        let pit_config = KvmPitConfig::default();
197        vm_fd.create_pit2(&pit_config).map_err(|e| {
198            HypervisorError::VmCreationFailed(format!("Failed to create PIT: {}", e))
199        })?;
200
201        Ok(())
202    }
203
204    /// Returns the VM ID.
205    #[must_use]
206    pub fn id(&self) -> u64 {
207        self.id
208    }
209
210    /// Returns the VM configuration.
211    #[must_use]
212    pub fn config(&self) -> &VmConfig {
213        &self.config
214    }
215
216    /// Returns the current VM state.
217    pub fn state(&self) -> VmState {
218        *self.state.read().unwrap()
219    }
220
221    /// Returns whether the VM is running.
222    #[must_use]
223    pub fn is_running(&self) -> bool {
224        self.running.load(Ordering::SeqCst)
225    }
226
227    /// Sets the VM state.
228    pub(super) fn set_state(&self, new_state: VmState) {
229        let mut state = self.state.write().unwrap();
230        tracing::debug!("VM {} state: {:?} -> {:?}", self.id, *state, new_state);
231        *state = new_state;
232    }
233
234    /// Returns the KVM VM file descriptor.
235    pub(crate) fn vm_fd(&self) -> &KvmVmFd {
236        &self.vm_fd
237    }
238
239    /// Returns the vCPU mmap size.
240    pub(crate) fn vcpu_mmap_size(&self) -> usize {
241        self.vcpu_mmap_size
242    }
243}