⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/virt/hypervisor/
mod.rs

1//! Advanced Hypervisor Enhancements
2//!
3//! Implements 6 hypervisor features for Phase 7.5 Wave 7:
4//! 1. Nested Virtualization -- L2 VMCS shadowing with field forwarding
5//! 2. VirtIO Device Passthrough -- device assignment, MMIO mapping, interrupt
6//!    forwarding
7//! 3. Live Migration -- VMCS serialization, dirty page pre-copy, stop-and-copy
8//! 4. Guest SMP -- multi-vCPU VMs with per-vCPU VMCS, IPI, SIPI emulation
9//! 5. Virtual LAPIC -- full LAPIC register emulation with timer modes
10//! 6. VM Snapshots -- complete state capture/restore with memory and device
11//!    state
12
13#[cfg(feature = "alloc")]
14extern crate alloc;
15
16use core::sync::atomic::{AtomicU64, Ordering};
17
18pub mod lapic;
19pub mod migration;
20pub mod nested;
21pub mod passthrough;
22pub mod smp;
23pub mod snapshot;
24
25// Re-export everything from submodules
26pub use self::{lapic::*, migration::*, nested::*, passthrough::*, smp::*, snapshot::*};
27
28// ---------------------------------------------------------------------------
29// Constants (shared across submodules)
30// ---------------------------------------------------------------------------
31
32/// Maximum vCPUs per VM
33pub(crate) const MAX_VCPUS: usize = 16;
34
35/// Maximum VMs tracked by the hypervisor
36pub(crate) const _MAX_VMS: usize = 64;
37
38/// LAPIC base MMIO address (standard x86 location)
39pub(crate) const LAPIC_BASE_ADDR: u64 = 0xFEE0_0000;
40
41/// LAPIC register space size (4 KiB)
42pub(crate) const LAPIC_REGION_SIZE: u64 = 0x1000;
43
44/// Page size constant
45pub(crate) const PAGE_SIZE: u64 = 4096;
46
47/// Number of VMCS field groups for serialization
48pub(crate) const _VMCS_FIELD_GROUP_COUNT: usize = 7;
49
50/// Maximum pages per pre-copy iteration
51pub(crate) const PRECOPY_BATCH_SIZE: u64 = 256;
52
53/// Dirty page bitmap granularity: bits per u64
54pub(crate) const BITS_PER_U64: u64 = 64;
55
56/// Snapshot magic number
57pub(crate) const SNAPSHOT_MAGIC: u32 = 0x564D_534E; // "VMSN"
58
59/// Snapshot format version
60pub(crate) const SNAPSHOT_VERSION: u32 = 1;
61
62/// Maximum passthrough devices per VM
63pub(crate) const _MAX_PASSTHROUGH_DEVICES: usize = 32;
64
65/// Maximum MSI-X vectors
66pub(crate) const MAX_MSIX_VECTORS: usize = 64;
67
68// ---------------------------------------------------------------------------
69// Shared types
70// ---------------------------------------------------------------------------
71
72/// General-purpose register state for a vCPU
73#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
74#[repr(C)]
75pub struct GuestRegisters {
76    pub rax: u64,
77    pub rbx: u64,
78    pub rcx: u64,
79    pub rdx: u64,
80    pub rsi: u64,
81    pub rdi: u64,
82    pub rbp: u64,
83    pub rsp: u64,
84    pub r8: u64,
85    pub r9: u64,
86    pub r10: u64,
87    pub r11: u64,
88    pub r12: u64,
89    pub r13: u64,
90    pub r14: u64,
91    pub r15: u64,
92    pub rip: u64,
93    pub rflags: u64,
94}
95
96// ---------------------------------------------------------------------------
97// Hypervisor Manager (ties everything together)
98// ---------------------------------------------------------------------------
99
100/// Hypervisor statistics
101#[derive(Debug, Default)]
102pub struct HypervisorStats {
103    pub total_vm_entries: AtomicU64,
104    pub total_vm_exits: AtomicU64,
105    pub total_ipis_sent: AtomicU64,
106    pub total_lapic_timer_fires: AtomicU64,
107    pub total_ept_violations: AtomicU64,
108    pub total_snapshots_taken: AtomicU64,
109    pub total_migrations_started: AtomicU64,
110    pub total_migrations_completed: AtomicU64,
111}
112
113impl HypervisorStats {
114    pub const fn new() -> Self {
115        Self {
116            total_vm_entries: AtomicU64::new(0),
117            total_vm_exits: AtomicU64::new(0),
118            total_ipis_sent: AtomicU64::new(0),
119            total_lapic_timer_fires: AtomicU64::new(0),
120            total_ept_violations: AtomicU64::new(0),
121            total_snapshots_taken: AtomicU64::new(0),
122            total_migrations_started: AtomicU64::new(0),
123            total_migrations_completed: AtomicU64::new(0),
124        }
125    }
126
127    pub fn record_vm_entry(&self) {
128        self.total_vm_entries.fetch_add(1, Ordering::Relaxed);
129    }
130
131    pub fn record_vm_exit(&self) {
132        self.total_vm_exits.fetch_add(1, Ordering::Relaxed);
133    }
134
135    pub fn record_ipi(&self) {
136        self.total_ipis_sent.fetch_add(1, Ordering::Relaxed);
137    }
138
139    pub fn record_timer_fire(&self) {
140        self.total_lapic_timer_fires.fetch_add(1, Ordering::Relaxed);
141    }
142
143    pub fn record_ept_violation(&self) {
144        self.total_ept_violations.fetch_add(1, Ordering::Relaxed);
145    }
146
147    pub fn record_snapshot(&self) {
148        self.total_snapshots_taken.fetch_add(1, Ordering::Relaxed);
149    }
150
151    pub fn record_migration_start(&self) {
152        self.total_migrations_started
153            .fetch_add(1, Ordering::Relaxed);
154    }
155
156    pub fn record_migration_complete(&self) {
157        self.total_migrations_completed
158            .fetch_add(1, Ordering::Relaxed);
159    }
160}
161
162static HYPERVISOR_STATS: HypervisorStats = HypervisorStats::new();
163
164/// Get global hypervisor statistics
165pub fn get_stats() -> &'static HypervisorStats {
166    &HYPERVISOR_STATS
167}
168
169// ---------------------------------------------------------------------------
170// Tests
171// ---------------------------------------------------------------------------
172
173#[cfg(test)]
174mod tests {
175    #[allow(unused_imports)]
176    use alloc::vec;
177
178    use super::*;
179
180    // --- Nested Virtualization Tests ---
181
182    #[test]
183    fn test_shadow_vmcs_read_write() {
184        use crate::virt::vmx::VmcsFields;
185        let mut shadow = ShadowVmcs::new();
186        shadow.write_field(VmcsFields::GUEST_RIP, 0x1000);
187        assert_eq!(shadow.read_field(VmcsFields::GUEST_RIP), Some(0x1000));
188        assert_eq!(shadow.read_field(VmcsFields::GUEST_RSP), None);
189        assert_eq!(shadow.field_count(), 1);
190    }
191
192    #[test]
193    fn test_shadow_vmcs_activate_deactivate() {
194        let mut shadow = ShadowVmcs::new();
195        assert!(!shadow.is_active());
196        shadow.activate(0x2000);
197        assert!(shadow.is_active());
198        assert_eq!(shadow.link_pointer(), 0x2000);
199        shadow.deactivate();
200        assert!(!shadow.is_active());
201        assert_eq!(shadow.link_pointer(), 0xFFFF_FFFF_FFFF_FFFF);
202    }
203
204    #[test]
205    fn test_shadow_vmcs_clear() {
206        let mut shadow = ShadowVmcs::new();
207        shadow.write_field(0x100, 42);
208        shadow.write_field(0x200, 84);
209        shadow.activate(0x3000);
210        shadow.clear();
211        assert_eq!(shadow.field_count(), 0);
212        assert!(!shadow.is_active());
213    }
214
215    #[test]
216    fn test_nested_controller_l1_vmwrite_passthrough() {
217        use crate::virt::vmx::VmcsFields;
218        let mut ctrl = NestedVirtController::new();
219        assert!(ctrl
220            .handle_l1_vmwrite(VmcsFields::GUEST_CR0, 0x80000011)
221            .is_ok());
222        assert_eq!(ctrl.handle_l1_vmread(VmcsFields::GUEST_CR0), Ok(0x80000011));
223    }
224
225    #[test]
226    fn test_nested_controller_l1_vmwrite_hidden_field() {
227        use crate::virt::vmx::VmcsFields;
228        let ctrl = NestedVirtController::new();
229        assert_eq!(
230            ctrl.handle_l1_vmread(VmcsFields::HOST_RIP),
231            Err(crate::virt::VmError::VmcsFieldError)
232        );
233    }
234
235    #[test]
236    fn test_nested_controller_l1_vmwrite_readonly_field() {
237        use crate::virt::vmx::VmcsFields;
238        let mut ctrl = NestedVirtController::new();
239        assert_eq!(
240            ctrl.handle_l1_vmwrite(VmcsFields::VM_EXIT_REASON, 42),
241            Err(crate::virt::VmError::VmcsFieldError)
242        );
243    }
244
245    #[test]
246    fn test_nested_enter_exit_l2() {
247        use crate::virt::vmx::VmcsFields;
248        let mut ctrl = NestedVirtController::new();
249        ctrl.enable_nested_vmx();
250        ctrl.handle_l1_vmwrite(VmcsFields::GUEST_RIP, 0x5000)
251            .unwrap();
252
253        let l1_regs = GuestRegisters {
254            rax: 1,
255            rip: 0x4000,
256            ..Default::default()
257        };
258        assert!(ctrl.enter_l2(&l1_regs).is_ok());
259        assert_eq!(ctrl.nesting_level(), NestingLevel::L2);
260
261        let l1_restored = ctrl.exit_l2(NestedExitReason::Vmcall).unwrap();
262        assert_eq!(l1_restored.rax, 1);
263        assert_eq!(l1_restored.rip, 0x4000);
264        assert_eq!(ctrl.nesting_level(), NestingLevel::L1);
265    }
266
267    #[test]
268    fn test_nested_exit_l2_stores_reason() {
269        use crate::virt::vmx::VmcsFields;
270        let mut ctrl = NestedVirtController::new();
271        ctrl.enable_nested_vmx();
272        ctrl.handle_l1_vmwrite(VmcsFields::GUEST_RIP, 0x1000)
273            .unwrap();
274        ctrl.enter_l2(&GuestRegisters::default()).unwrap();
275        ctrl.exit_l2(NestedExitReason::EptViolation).unwrap();
276        // Exit reason 48 stored in shadow VMCS
277        assert_eq!(
278            ctrl.shadow_vmcs.read_field(VmcsFields::VM_EXIT_REASON),
279            Some(48)
280        );
281    }
282
283    #[test]
284    fn test_nested_should_forward() {
285        let ctrl = NestedVirtController::new();
286        assert!(ctrl.should_forward_to_l1(NestedExitReason::VmxInstruction));
287        assert!(ctrl.should_forward_to_l1(NestedExitReason::Vmcall));
288        assert!(!ctrl.should_forward_to_l1(NestedExitReason::ExternalInterrupt));
289    }
290
291    // --- VirtIO Device Passthrough Tests ---
292
293    #[test]
294    fn test_passthrough_device_assign_unassign() {
295        let mut dev = PassthroughDevice::new(
296            PassthroughDeviceType::VirtioNet,
297            0x1AF4,
298            0x1041,
299            0x0000_0800,
300        );
301        assert!(!dev.is_assigned());
302        assert!(dev.assign_to_vm(1).is_ok());
303        assert!(dev.is_assigned());
304        assert_eq!(dev.owner_vm_id, 1);
305        // Double assign fails
306        assert_eq!(dev.assign_to_vm(2), Err(crate::virt::VmError::DeviceError));
307        dev.unassign();
308        assert!(!dev.is_assigned());
309    }
310
311    #[test]
312    fn test_passthrough_msix_remap() {
313        let mut dev = PassthroughDevice::new(
314            PassthroughDeviceType::VirtioBlk,
315            0x1AF4,
316            0x1042,
317            0x0000_1000,
318        );
319        dev.add_msix_remap(32, 64, 0);
320        dev.add_msix_remap(33, 65, 1);
321        assert_eq!(dev.msix_remap_count(), 2);
322        assert_eq!(dev.remap_interrupt(32), Some((64, 0)));
323        assert_eq!(dev.remap_interrupt(33), Some((65, 1)));
324        assert_eq!(dev.remap_interrupt(99), None);
325    }
326
327    #[test]
328    fn test_passthrough_mmio_region() {
329        let mut dev = PassthroughDevice::new(
330            PassthroughDeviceType::VirtioGpu,
331            0x1AF4,
332            0x1050,
333            0x0000_1800,
334        );
335        dev.add_mmio_region(0xFE00_0000, 0xC000_0000, 0x1000);
336        assert_eq!(dev.mmio_region_count(), 1);
337        assert!(dev.mmio_regions[0].mapped);
338    }
339
340    #[test]
341    fn test_pci_config_passthrough() {
342        let mut pci = PciConfigPassthrough::new(0x1AF4, 0x1041, 0);
343        assert_eq!(pci.read_config(0), 0xF4); // vendor low
344        assert_eq!(pci.read_config(1), 0x1A); // vendor high
345        assert_eq!(pci.read_config(2), 0x41); // device low
346        assert_eq!(pci.read_config(3), 0x10); // device high
347                                              // Write to command register (writable)
348        pci.write_config(4, 0x07);
349        assert_eq!(pci.read_config(4), 0x07);
350        // Write to read-only area (should be masked)
351        pci.write_config(0, 0xFF);
352        assert_eq!(pci.read_config(0), 0xF4); // Unchanged
353    }
354
355    // --- Live Migration Tests ---
356
357    #[test]
358    fn test_dirty_page_bitmap() {
359        let mut bm = DirtyPageBitmap::new(256);
360        assert_eq!(bm.dirty_count(), 0);
361        assert_eq!(bm.total_pages(), 256);
362
363        bm.set_dirty(0);
364        bm.set_dirty(63);
365        bm.set_dirty(64);
366        bm.set_dirty(255);
367        assert_eq!(bm.dirty_count(), 4);
368        assert!(bm.is_dirty(0));
369        assert!(bm.is_dirty(63));
370        assert!(bm.is_dirty(64));
371        assert!(bm.is_dirty(255));
372        assert!(!bm.is_dirty(1));
373
374        bm.clear_dirty(63);
375        assert_eq!(bm.dirty_count(), 3);
376        assert!(!bm.is_dirty(63));
377    }
378
379    #[test]
380    fn test_dirty_page_bitmap_idempotent() {
381        let mut bm = DirtyPageBitmap::new(128);
382        bm.set_dirty(10);
383        bm.set_dirty(10); // Double set
384        assert_eq!(bm.dirty_count(), 1);
385        bm.clear_dirty(10);
386        bm.clear_dirty(10); // Double clear
387        assert_eq!(bm.dirty_count(), 0);
388    }
389
390    #[test]
391    fn test_dirty_page_iterator() {
392        let mut bm = DirtyPageBitmap::new(200);
393        bm.set_dirty(5);
394        bm.set_dirty(100);
395        bm.set_dirty(199);
396        let pages: Vec<u64> = bm.dirty_pages().collect();
397        assert_eq!(pages, vec![5, 100, 199]);
398    }
399
400    #[test]
401    fn test_dirty_page_clear_all() {
402        let mut bm = DirtyPageBitmap::new(128);
403        bm.set_dirty(0);
404        bm.set_dirty(50);
405        bm.set_dirty(127);
406        let old_count = bm.clear_all();
407        assert_eq!(old_count, 3);
408        assert_eq!(bm.dirty_count(), 0);
409    }
410
411    #[test]
412    fn test_migration_progress_bandwidth() {
413        let mut progress = MigrationProgress::default();
414        progress.total_bytes = 1_000_000;
415        progress.update_bandwidth(500_000, 100); // 5000 bytes/ms
416        assert_eq!(progress.bandwidth_bytes_per_ms, 5000);
417        assert_eq!(progress.transferred_bytes, 500_000);
418        progress.estimate_remaining();
419        assert_eq!(progress.estimated_remaining_ms, 100); // 500000 / 5000
420    }
421
422    #[test]
423    fn test_migration_progress_completion() {
424        let mut progress = MigrationProgress::default();
425        progress.total_bytes = 1000;
426        progress.transferred_bytes = 750;
427        assert_eq!(progress.completion_percent(), 75);
428    }
429
430    #[test]
431    fn test_migration_convergence() {
432        let mut progress = MigrationProgress::default();
433        progress.previous_dirty_pages = 1000;
434        progress.current_dirty_pages = 700;
435        // 30% reduction, threshold 20% -> converged
436        assert!(progress.has_converged(20));
437        progress.current_dirty_pages = 900;
438        // 10% reduction, threshold 20% -> not converged
439        assert!(!progress.has_converged(20));
440    }
441
442    #[test]
443    fn test_migration_state_machine() {
444        let mut ctrl = MigrationController::new(1);
445        assert_eq!(ctrl.state(), MigrationState::Idle);
446
447        ctrl.begin_setup(100).unwrap();
448        assert_eq!(ctrl.state(), MigrationState::Setup);
449
450        ctrl.begin_precopy().unwrap();
451        assert_eq!(ctrl.state(), MigrationState::PreCopy);
452
453        let pages = ctrl.precopy_iteration().unwrap();
454        assert!(!pages.is_empty());
455    }
456
457    #[test]
458    fn test_serialized_vmcs() {
459        use crate::virt::vmx::VmcsFields;
460        let mut vmcs = SerializedVmcs::new();
461        vmcs.add_field(VmcsFields::GUEST_RIP, 0x1000);
462        vmcs.add_field(VmcsFields::GUEST_RSP, 0x7FF0);
463        assert_eq!(vmcs.field_count(), 2);
464        assert_eq!(vmcs.find_field(VmcsFields::GUEST_RIP), Some(0x1000));
465        assert_eq!(vmcs.find_field(VmcsFields::GUEST_CR0), None);
466    }
467
468    // --- Guest SMP Tests ---
469
470    #[test]
471    fn test_smp_vm_creation() {
472        let vm = SmpVm::new(1, 4).unwrap();
473        assert_eq!(vm.vcpu_count(), 4);
474        assert!(vm.vcpu(0).unwrap().is_bsp);
475        assert!(!vm.vcpu(1).unwrap().is_bsp);
476        assert_eq!(vm.vcpu(1).unwrap().state, VcpuState::WaitingForSipi);
477    }
478
479    #[test]
480    fn test_smp_vm_max_vcpu_limit() {
481        assert!(SmpVm::new(1, 0).is_err());
482        assert!(SmpVm::new(1, MAX_VCPUS + 1).is_err());
483        assert!(SmpVm::new(1, MAX_VCPUS).is_ok());
484    }
485
486    #[test]
487    fn test_vcpu_sipi_startup() {
488        let mut vm = SmpVm::new(1, 2).unwrap();
489        // AP starts in WaitingForSipi
490        assert_eq!(vm.vcpu(1).unwrap().state, VcpuState::WaitingForSipi);
491
492        // BSP sends INIT + SIPI to AP
493        vm.startup_ap(1, 0x10).unwrap(); // Entry at 0x10000
494
495        let ap = vm.vcpu(1).unwrap();
496        assert_eq!(ap.state, VcpuState::Running);
497        assert_eq!(ap.registers.rip, 0x10000);
498        assert_eq!(ap.sipi_vector, 0x10);
499    }
500
501    #[test]
502    fn test_vcpu_ipi_delivery() {
503        let mut vm = SmpVm::new(1, 4).unwrap();
504        // Start all APs
505        for i in 1..4 {
506            vm.startup_ap(i, 0x20).unwrap();
507        }
508
509        // Send fixed IPI from vCPU 0 to vCPU 2
510        vm.send_ipi(0, 2, IpiDeliveryMode::Fixed, 0x30).unwrap();
511        assert_eq!(vm.vcpu(2).unwrap().pending_ipi_count(), 1);
512        let ipi = vm.vcpu_mut(2).unwrap().pop_ipi().unwrap();
513        assert_eq!(ipi.vector, 0x30);
514        assert_eq!(ipi.source, 0);
515    }
516
517    #[test]
518    fn test_vcpu_broadcast_ipi() {
519        let mut vm = SmpVm::new(1, 4).unwrap();
520        for i in 1..4 {
521            vm.startup_ap(i, 0x20).unwrap();
522        }
523
524        // Broadcast from vCPU 0
525        vm.send_ipi(0, 0xFF, IpiDeliveryMode::Fixed, 0x40).unwrap();
526        // All except sender should receive
527        assert_eq!(vm.vcpu(0).unwrap().pending_ipi_count(), 0);
528        assert_eq!(vm.vcpu(1).unwrap().pending_ipi_count(), 1);
529        assert_eq!(vm.vcpu(2).unwrap().pending_ipi_count(), 1);
530        assert_eq!(vm.vcpu(3).unwrap().pending_ipi_count(), 1);
531    }
532
533    #[test]
534    fn test_vcpu_halt_and_nmi_wake() {
535        let mut vcpu = VirtualCpu::new(0, true);
536        vcpu.state = VcpuState::Running;
537        vcpu.halt();
538        assert_eq!(vcpu.state, VcpuState::Halted);
539
540        vcpu.deliver_ipi(IpiMessage {
541            source: 1,
542            destination: 0,
543            delivery_mode: IpiDeliveryMode::Nmi,
544            vector: 0,
545            level: true,
546            trigger_level: false,
547        });
548        assert_eq!(vcpu.state, VcpuState::Running);
549    }
550
551    #[test]
552    fn test_vcpu_pause_resume() {
553        let mut vm = SmpVm::new(1, 2).unwrap();
554        vm.vcpu_mut(0).unwrap().state = VcpuState::Running;
555        vm.startup_ap(1, 0x10).unwrap();
556        assert_eq!(vm.running_vcpu_count(), 2);
557
558        vm.pause_all();
559        assert_eq!(vm.vcpu(0).unwrap().state, VcpuState::Paused);
560        // AP was Running, now Paused
561        assert_eq!(vm.vcpu(1).unwrap().state, VcpuState::Paused);
562
563        vm.resume_all();
564        assert_eq!(vm.running_vcpu_count(), 2);
565    }
566
567    // --- Virtual LAPIC Tests ---
568
569    #[test]
570    fn test_lapic_register_rw() {
571        let mut lapic = VirtualLapic::new(0);
572        // Write TPR
573        lapic.write_register(LapicRegs::TPR, 0x20);
574        assert_eq!(lapic.read_register(LapicRegs::TPR), 0x20);
575        // Read version
576        assert_eq!(lapic.read_register(LapicRegs::VERSION), 0x0005_0014);
577        // Read ID
578        assert_eq!(lapic.read_register(LapicRegs::ID), 0);
579    }
580
581    #[test]
582    fn test_lapic_enable_via_svr() {
583        let mut lapic = VirtualLapic::new(0);
584        assert!(!lapic.is_enabled());
585        lapic.write_register(LapicRegs::SVR, 0x1FF); // bit 8 set
586        assert!(lapic.is_enabled());
587    }
588
589    #[test]
590    fn test_lapic_accept_and_deliver_interrupt() {
591        let mut lapic = VirtualLapic::new(0);
592        lapic.write_register(LapicRegs::SVR, 0x1FF); // Enable
593        lapic.accept_interrupt(0x30);
594        // IRR should have bit 0x30
595        assert!(lapic.irr[1] & (1 << 16) != 0); // 0x30 = 48 = word 1, bit 16
596        let vec = lapic.deliver_pending_interrupt();
597        assert_eq!(vec, Some(0x30));
598        // Now in ISR
599        assert!(lapic.isr[1] & (1 << 16) != 0);
600    }
601
602    #[test]
603    fn test_lapic_eoi() {
604        let mut lapic = VirtualLapic::new(0);
605        lapic.write_register(LapicRegs::SVR, 0x1FF);
606        lapic.accept_interrupt(0x30);
607        lapic.deliver_pending_interrupt();
608        // ISR has 0x30
609        lapic.write_register(LapicRegs::EOI, 0);
610        // ISR should be cleared
611        assert_eq!(lapic.isr[1] & (1 << 16), 0);
612    }
613
614    #[test]
615    fn test_lapic_timer_oneshot() {
616        let mut lapic = VirtualLapic::new(0);
617        lapic.lvt_timer = LvtEntry { raw: 0x0000_0020 }; // vector 0x20, one-shot, unmasked
618        lapic.write_register(LapicRegs::TIMER_INITIAL_COUNT, 100);
619        assert!(!lapic.tick_timer(50));
620        assert_eq!(lapic.timer_current_count, 50);
621        assert!(lapic.tick_timer(60)); // Fires
622        assert_eq!(lapic.timer_current_count, 0);
623        assert!(!lapic.tick_timer(10)); // No more fires
624    }
625
626    #[test]
627    fn test_lapic_timer_periodic() {
628        let mut lapic = VirtualLapic::new(0);
629        // Periodic mode: bits 17 = 1
630        lapic.lvt_timer = LvtEntry { raw: 0x0002_0020 }; // vector 0x20, periodic
631        lapic.write_register(LapicRegs::TIMER_INITIAL_COUNT, 100);
632        assert!(lapic.tick_timer(110)); // Fires and reloads
633        assert_eq!(lapic.timer_current_count, 100); // Reloaded
634    }
635
636    #[test]
637    fn test_lapic_timer_divide_value() {
638        let mut lapic = VirtualLapic::new(0);
639        lapic.timer_divide_config = 0b0000; // divide by 2
640        assert_eq!(lapic.timer_divide_value(), 2);
641        lapic.timer_divide_config = 0b0011; // divide by 16
642        assert_eq!(lapic.timer_divide_value(), 16);
643        lapic.timer_divide_config = 0b1011; // divide by 1
644        assert_eq!(lapic.timer_divide_value(), 1);
645    }
646
647    #[test]
648    fn test_lapic_extract_ipi() {
649        let mut lapic = VirtualLapic::new(0);
650        lapic.icr_low = 0x0000_4030; // vector 0x30, INIT mode (5 << 8)
651                                     // Wait, INIT = 5 << 8 = 0x500. Let's set that properly.
652        lapic.icr_low = 0x0000_0530; // vector 0x30, INIT delivery mode (5 << 8)
653        lapic.icr_high = 0x0200_0000; // dest APIC ID 2
654        let ipi = lapic.extract_ipi();
655        assert_eq!(ipi.vector, 0x30);
656        assert_eq!(ipi.destination, 2);
657        assert_eq!(ipi.delivery_mode, IpiDeliveryMode::Init);
658    }
659
660    #[test]
661    fn test_lapic_priority() {
662        let mut lapic = VirtualLapic::new(0);
663        lapic.write_register(LapicRegs::SVR, 0x1FF);
664        lapic.write_register(LapicRegs::TPR, 0x40); // Priority class 4
665
666        // Interrupt with vector 0x30 (class 3) should NOT be delivered
667        // because TPR class (4) > vector class (3)
668        lapic.accept_interrupt(0x30);
669        assert_eq!(lapic.deliver_pending_interrupt(), None);
670
671        // Interrupt with vector 0x50 (class 5) should be delivered
672        lapic.accept_interrupt(0x50);
673        assert_eq!(lapic.deliver_pending_interrupt(), Some(0x50));
674    }
675
676    // --- Snapshot Tests ---
677
678    #[test]
679    fn test_snapshot_header_validation() {
680        let mut header = SnapshotHeader::default();
681        header.vm_id = 42;
682        header.vcpu_count = 4;
683        header.memory_pages = 1024;
684        header.checksum = header.compute_checksum();
685        assert!(header.is_valid());
686
687        // Corrupt magic
688        header.magic = 0;
689        assert!(!header.is_valid());
690    }
691
692    #[test]
693    fn test_snapshot_creation_and_finalize() {
694        let mut snap = VmSnapshot::new(1, 2, 1024, 123456);
695        snap.add_register_state(
696            0,
697            GuestRegisters {
698                rip: 0x1000,
699                ..Default::default()
700            },
701        );
702        snap.add_register_state(
703            1,
704            GuestRegisters {
705                rip: 0x2000,
706                ..Default::default()
707            },
708        );
709        snap.add_memory_page(0);
710        snap.add_memory_page(100);
711        snap.finalize();
712
713        assert!(snap.validate());
714        assert_eq!(snap.vcpu_state_count(), 2);
715        assert_eq!(snap.memory_page_count(), 2);
716        assert!(snap.header.total_size > 0);
717    }
718
719    #[test]
720    fn test_snapshot_lapic_roundtrip() {
721        let mut lapic = VirtualLapic::new(3);
722        lapic.write_register(LapicRegs::SVR, 0x1FF);
723        lapic.write_register(LapicRegs::TPR, 0x50);
724        lapic.accept_interrupt(0x80);
725        lapic.timer_initial_count = 5000;
726        lapic.timer_current_count = 2500;
727
728        let snap = LapicSnapshot::from_lapic(&lapic);
729        let mut restored = VirtualLapic::new(0);
730        snap.restore_to_lapic(&mut restored);
731
732        assert_eq!(restored.id, 3);
733        assert_eq!(restored.tpr, 0x50);
734        assert!(restored.is_enabled());
735        assert_eq!(restored.timer_initial_count, 5000);
736        assert_eq!(restored.timer_current_count, 2500);
737        // IRR should be preserved
738        assert!(restored.irr[4] & 1 != 0); // vector 0x80 = word 4, bit 0
739    }
740
741    #[test]
742    fn test_snapshot_device_state() {
743        use alloc::string::String;
744        let mut snap = VmSnapshot::new(1, 1, 256, 0);
745        snap.add_device_state(String::from("uart0"), vec![0x60, 0x00, 0x00, 0x00]);
746        assert_eq!(snap.device_state_count(), 1);
747        assert_eq!(snap.device_states[0].name, "uart0");
748        assert_eq!(snap.device_states[0].data.len(), 4);
749    }
750
751    // --- Hypervisor Stats Tests ---
752
753    #[test]
754    fn test_hypervisor_stats() {
755        let stats = HypervisorStats::new();
756        stats.record_vm_entry();
757        stats.record_vm_entry();
758        stats.record_vm_exit();
759        stats.record_ipi();
760        assert_eq!(stats.total_vm_entries.load(Ordering::Relaxed), 2);
761        assert_eq!(stats.total_vm_exits.load(Ordering::Relaxed), 1);
762        assert_eq!(stats.total_ipis_sent.load(Ordering::Relaxed), 1);
763    }
764
765    // --- LVT Entry Tests ---
766
767    #[test]
768    fn test_lvt_entry_fields() {
769        let entry = LvtEntry { raw: 0x0002_0030 }; // periodic, vector 0x30
770        assert_eq!(entry.vector(), 0x30);
771        assert!(!entry.is_masked());
772        assert_eq!(entry.timer_mode(), LapicTimerMode::Periodic);
773
774        let masked = LvtEntry { raw: 0x0001_0020 }; // masked
775        assert!(masked.is_masked());
776    }
777
778    #[test]
779    fn test_nesting_level_default() {
780        let level = NestingLevel::default();
781        assert_eq!(level, NestingLevel::L0);
782    }
783
784    #[test]
785    fn test_migration_state_default() {
786        let state = MigrationState::default();
787        assert_eq!(state, MigrationState::Idle);
788    }
789}