arcbox-hypervisor 0.4.10

Cross-platform hypervisor abstraction layer for ArcBox
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
//! Virtual CPU implementation for macOS.
//!
//! On macOS, Virtualization.framework uses a managed execution model where
//! the framework internally handles vCPU scheduling and execution. Unlike
//! KVM or Hypervisor.framework, there is no direct `run vCPU` API.
//!
//! The `DarwinVcpu` implementation adapts to this model:
//! - `run()` waits for VM state changes (from Running to another state)
//! - Register access is not supported (Virtualization.framework doesn't expose this)
//! - The actual vCPU execution is handled by `VZVirtualMachine.start()`

use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;

use arcbox_vz::VirtualMachineState;
use objc2::runtime::AnyObject;

use crate::{
    error::HypervisorError,
    traits::Vcpu,
    types::{CpuArch, Registers, VcpuExit, VcpuSnapshot},
};

/// Virtual CPU implementation for Darwin (macOS).
///
/// On macOS with Virtualization.framework, vCPU execution is managed internally
/// by the framework. This struct provides a compatible interface with the `Vcpu`
/// trait, but the actual execution model is different:
///
/// - **Managed execution**: The VM's vCPUs run automatically after `VZVirtualMachine.start()`
/// - **`run()` semantics**: Instead of executing guest code, `run()` waits for VM state changes
/// - **No register access**: Virtualization.framework doesn't expose vCPU registers
///
/// For code that needs to distinguish between managed and manual execution,
/// check `VirtualMachine::is_managed_execution()`.
pub struct DarwinVcpu {
    /// vCPU ID.
    id: u32,
    /// Whether the vCPU's `run()` method is currently blocking.
    running: Arc<AtomicBool>,
    /// Current register state (cached, not synced with actual vCPU).
    /// Note: Virtualization.framework doesn't expose register access.
    regs: Registers,
    /// Raw pointer to the `VZVirtualMachine` for state queries.
    /// This is safe to read from any thread (state property is thread-safe).
    vz_vm: *mut AnyObject,
}

// Safety: The vz_vm pointer is only used for reading the state property,
// which is documented as thread-safe by Apple.
unsafe impl Send for DarwinVcpu {}

impl DarwinVcpu {
    /// Creates a new vCPU.
    ///
    /// For managed execution VMs (Virtualization.framework), use `new_managed()`
    /// to pass the VM pointer for state queries.
    #[allow(dead_code)]
    pub(crate) fn new(id: u32) -> Self {
        Self {
            id,
            running: Arc::new(AtomicBool::new(false)),
            regs: Registers::default(),
            vz_vm: std::ptr::null_mut(),
        }
    }

    /// Creates a new vCPU with a reference to the VZ VM for state queries.
    ///
    /// This is the preferred constructor for Virtualization.framework VMs,
    /// as it enables `run()` to properly wait for VM state changes.
    pub(crate) fn new_managed(id: u32, vz_vm: *mut AnyObject) -> Self {
        Self {
            id,
            running: Arc::new(AtomicBool::new(false)),
            regs: Registers::default(),
            vz_vm,
        }
    }

    /// Queries the current VM state from Virtualization.framework.
    ///
    /// Returns `None` if no VZ VM pointer is available.
    fn query_vm_state(&self) -> Option<VirtualMachineState> {
        if self.vz_vm.is_null() {
            return None;
        }

        unsafe {
            // VZVirtualMachine.state is thread-safe to read
            let sel = objc2::sel!(state);
            let func: unsafe extern "C" fn(*const AnyObject, objc2::runtime::Sel) -> i64 =
                std::mem::transmute(objc2::ffi::objc_msgSend as *const std::ffi::c_void);
            let state = func(self.vz_vm as *const AnyObject, sel);
            Some(VirtualMachineState::from(state))
        }
    }

    /// Returns whether the vCPU is currently running.
    #[must_use]
    pub fn is_running(&self) -> bool {
        self.running.load(Ordering::SeqCst)
    }

    /// Returns a clone of the running flag for external monitoring.
    #[must_use]
    pub fn running_flag(&self) -> Arc<AtomicBool> {
        Arc::clone(&self.running)
    }

    /// Sets the instruction pointer.
    pub const fn set_instruction_pointer(&mut self, ip: u64) {
        self.regs.rip = ip;
    }

    /// Sets the stack pointer.
    pub const fn set_stack_pointer(&mut self, sp: u64) {
        self.regs.rsp = sp;
    }

    /// Sets up initial register state for Linux boot.
    ///
    /// This configures registers as expected by the Linux boot protocol.
    #[cfg(target_arch = "x86_64")]
    pub fn setup_linux_boot(&mut self, entry_point: u64, boot_params_addr: u64) {
        // Clear all registers
        self.regs = Registers::default();

        // Set instruction pointer to kernel entry
        self.regs.rip = entry_point;

        // RSI points to boot_params structure
        self.regs.rsi = boot_params_addr;

        // Flags: interrupts disabled, reserved bit set
        self.regs.rflags = 0x2;

        tracing::debug!(
            "vCPU {} setup for Linux boot: entry={:#x}, boot_params={:#x}",
            self.id,
            entry_point,
            boot_params_addr
        );
    }

    #[cfg(target_arch = "aarch64")]
    pub fn setup_linux_boot(&mut self, entry_point: u64, dtb_addr: u64) {
        // Clear all registers
        self.regs = Registers::default();

        // ARM64 Linux boot: x0 = dtb address, PC = entry point
        // Note: We use rip/rax here as placeholders for PC/x0
        // Real implementation would use ARM64-specific register struct
        self.regs.rip = entry_point; // PC
        self.regs.rax = dtb_addr; // x0

        tracing::debug!(
            "vCPU {} setup for Linux boot: entry={:#x}, dtb={:#x}",
            self.id,
            entry_point,
            dtb_addr
        );
    }
}

impl Vcpu for DarwinVcpu {
    fn run(&mut self) -> Result<VcpuExit, HypervisorError> {
        self.running.store(true, Ordering::SeqCst);

        // On Virtualization.framework, vCPU execution is managed internally.
        // The run() method waits for VM state changes instead of executing guest code.
        //
        // This implementation polls the VM state until:
        // - The VM exits Running state (pause, stop, error)
        // - The VZ VM pointer is not available (legacy mode, return immediately)

        let poll_interval = Duration::from_millis(100);

        // If no VZ VM pointer, return immediately (legacy behavior or placeholder vCPU)
        if self.vz_vm.is_null() {
            tracing::warn!(
                "vCPU {} run() called without VZ VM pointer, returning Halt",
                self.id
            );
            self.running.store(false, Ordering::SeqCst);
            return Ok(VcpuExit::Halt);
        }

        tracing::debug!("vCPU {} entering managed execution wait loop", self.id);

        loop {
            let state = if let Some(s) = self.query_vm_state() {
                s
            } else {
                // VM pointer became invalid
                self.running.store(false, Ordering::SeqCst);
                return Ok(VcpuExit::Halt);
            };

            match state {
                VirtualMachineState::Running => {
                    // VM is still running, continue waiting
                    std::thread::sleep(poll_interval);
                }
                VirtualMachineState::Stopped => {
                    tracing::debug!("vCPU {} detected VM stopped", self.id);
                    self.running.store(false, Ordering::SeqCst);
                    return Ok(VcpuExit::Shutdown);
                }
                VirtualMachineState::Paused => {
                    tracing::debug!("vCPU {} detected VM paused", self.id);
                    self.running.store(false, Ordering::SeqCst);
                    return Ok(VcpuExit::Halt);
                }
                VirtualMachineState::Error => {
                    tracing::error!("vCPU {} detected VM error state", self.id);
                    self.running.store(false, Ordering::SeqCst);
                    return Err(HypervisorError::VcpuRunError(
                        "VM entered error state".to_string(),
                    ));
                }
                VirtualMachineState::Starting
                | VirtualMachineState::Pausing
                | VirtualMachineState::Resuming
                | VirtualMachineState::Stopping => {
                    // Transitional states, wait for final state
                    tracing::trace!("vCPU {} VM in transitional state: {:?}", self.id, state);
                    std::thread::sleep(poll_interval);
                }
                VirtualMachineState::Saving | VirtualMachineState::Restoring => {
                    // Snapshot operations (macOS 14+), wait for completion
                    tracing::trace!("vCPU {} VM performing snapshot operation", self.id);
                    std::thread::sleep(poll_interval);
                }
            }
        }
    }

    fn get_regs(&self) -> Result<Registers, HypervisorError> {
        // LIMITATION: Virtualization.framework does not expose vCPU register access.
        //
        // Unlike Hypervisor.framework or KVM, Virtualization.framework is a
        // high-level API that abstracts away direct vCPU control. There is no
        // public API to read or write guest CPU registers.
        //
        // Options for working around this limitation:
        // 1. Use the guest agent to query register state (requires guest cooperation)
        // 2. Use Hypervisor.framework instead (lower-level, more complex)
        // 3. Accept that register debugging is not available on this platform
        //
        // Currently we return cached values that may be set during setup but
        // are NOT synchronized with actual vCPU state during execution.
        tracing::trace!(
            "get_regs() called on vCPU {} - returning cached values (not live)",
            self.id
        );
        Ok(self.regs.clone())
    }

    fn set_regs(&mut self, regs: &Registers) -> Result<(), HypervisorError> {
        // LIMITATION: Virtualization.framework does not expose vCPU register access.
        //
        // This method caches the register values but DOES NOT actually set
        // the guest vCPU registers. On Virtualization.framework, the initial
        // register state (entry point, boot params) is configured through
        // VZLinuxBootLoader, not through direct register manipulation.
        //
        // For managed execution VMs, this method is effectively a no-op for
        // actual vCPU state but can be used for internal bookkeeping.
        tracing::trace!(
            "set_regs() called on vCPU {} - caching values (not applied to vCPU)",
            self.id
        );
        self.regs = regs.clone();
        Ok(())
    }

    fn id(&self) -> u32 {
        self.id
    }

    fn set_io_result(&mut self, _value: u64) -> Result<(), HypervisorError> {
        // NOT APPLICABLE: Virtualization.framework handles I/O internally.
        //
        // On Virtualization.framework, all device I/O (port I/O, MMIO) is
        // handled internally by the framework through its device abstraction
        // layer (VZVirtioBlockDeviceConfiguration, VZVirtioNetworkDeviceConfiguration,
        // etc.). There is no vCPU exit for I/O that we need to handle.
        //
        // This method exists for API compatibility with manual execution
        // backends (KVM, Hypervisor.framework) but is a no-op on Darwin.
        Ok(())
    }

    fn set_mmio_result(&mut self, _value: u64) -> Result<(), HypervisorError> {
        // NOT APPLICABLE: Virtualization.framework handles MMIO internally.
        //
        // Similar to set_io_result(), MMIO accesses are handled by the
        // framework's device emulation layer. VirtIO devices configured
        // through VZ*DeviceConfiguration automatically handle their MMIO
        // regions without exposing exits to userspace.
        //
        // For custom devices not supported by Virtualization.framework,
        // one would need to use Hypervisor.framework or implement a
        // paravirtualized interface through vsock.
        Ok(())
    }

    fn snapshot(&self) -> Result<VcpuSnapshot, HypervisorError> {
        // LIMITATION: Virtualization.framework does not expose vCPU register access.
        //
        // We return the cached register state, which may not reflect the actual
        // vCPU state during execution. For accurate snapshots, the VM should be
        // paused first and register state should be obtained through other means
        // (e.g., guest agent).
        tracing::warn!(
            "vCPU {} snapshot: returning cached registers (not live state)",
            self.id
        );

        #[cfg(target_arch = "aarch64")]
        {
            // On ARM64, create an ARM64 snapshot with default registers
            // since we don't have access to actual register state
            let mut arm_regs = crate::types::Arm64Registers::default();
            // Map cached x86 style regs to ARM64 format as best we can
            arm_regs.pc = self.regs.rip;
            arm_regs.sp = self.regs.rsp;
            arm_regs.x[0] = self.regs.rax;
            arm_regs.x[1] = self.regs.rbx;
            arm_regs.x[2] = self.regs.rcx;
            arm_regs.x[3] = self.regs.rdx;

            Ok(VcpuSnapshot::new_arm64(self.id, arm_regs))
        }

        #[cfg(target_arch = "x86_64")]
        {
            Ok(VcpuSnapshot::new_x86(self.id, self.regs.clone()))
        }
    }

    fn restore(&mut self, snapshot: &VcpuSnapshot) -> Result<(), HypervisorError> {
        // LIMITATION: Virtualization.framework does not expose vCPU register access.
        //
        // We cache the register values but cannot actually restore them to the vCPU.
        // For actual restore functionality, one would need to use Hypervisor.framework
        // or rely on the guest agent to restore state.
        if snapshot.id != self.id {
            return Err(HypervisorError::SnapshotError(format!(
                "vCPU ID mismatch: expected {}, got {}",
                self.id, snapshot.id
            )));
        }

        tracing::warn!(
            "vCPU {} restore: caching registers (not applied to vCPU)",
            self.id
        );

        match snapshot.arch {
            CpuArch::Aarch64 => {
                if let Some(arm_regs) = &snapshot.arm64_regs {
                    // Map ARM64 registers back to x86-style cached regs
                    self.regs.rip = arm_regs.pc;
                    self.regs.rsp = arm_regs.sp;
                    self.regs.rax = arm_regs.x[0];
                    self.regs.rbx = arm_regs.x[1];
                    self.regs.rcx = arm_regs.x[2];
                    self.regs.rdx = arm_regs.x[3];
                }
            }
            CpuArch::X86_64 => {
                if let Some(x86_regs) = &snapshot.x86_regs {
                    self.regs = x86_regs.clone();
                }
            }
        }

        Ok(())
    }
}

/// Extended vCPU state for x86_64.
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Default)]
pub struct SpecialRegisters {
    /// Code segment.
    pub cs: SegmentRegister,
    /// Data segment.
    pub ds: SegmentRegister,
    /// Stack segment.
    pub ss: SegmentRegister,
    /// Extra segment.
    pub es: SegmentRegister,
    /// FS segment.
    pub fs: SegmentRegister,
    /// GS segment.
    pub gs: SegmentRegister,
    /// Global descriptor table.
    pub gdt: DescriptorTable,
    /// Interrupt descriptor table.
    pub idt: DescriptorTable,
    /// Control register 0.
    pub cr0: u64,
    /// Control register 3 (page table base).
    pub cr3: u64,
    /// Control register 4.
    pub cr4: u64,
    /// Extended feature enable register.
    pub efer: u64,
}

/// Segment register.
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Default)]
pub struct SegmentRegister {
    /// Base address.
    pub base: u64,
    /// Limit.
    pub limit: u32,
    /// Selector.
    pub selector: u16,
    /// Type.
    pub type_: u8,
    /// Present.
    pub present: u8,
    /// Descriptor privilege level.
    pub dpl: u8,
    /// Default operation size.
    pub db: u8,
    /// Granularity.
    pub granularity: u8,
    /// Long mode.
    pub long_mode: u8,
}

/// Descriptor table (GDT/IDT).
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Default)]
pub struct DescriptorTable {
    /// Base address.
    pub base: u64,
    /// Limit.
    pub limit: u16,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;

    // ========================================================================
    // Basic Creation Tests
    // ========================================================================

    #[test]
    fn test_vcpu_creation() {
        let vcpu = DarwinVcpu::new(0);
        assert_eq!(vcpu.id(), 0);
        assert!(!vcpu.is_running());
        assert!(vcpu.vz_vm.is_null());
    }

    #[test]
    fn test_vcpu_creation_managed() {
        // Create with null pointer (for testing)
        let vcpu = DarwinVcpu::new_managed(1, std::ptr::null_mut());
        assert_eq!(vcpu.id(), 1);
        assert!(!vcpu.is_running());
        assert!(vcpu.vz_vm.is_null());
    }

    #[test]
    fn test_vcpu_creation_with_different_ids() {
        // Test creating vCPUs with various IDs
        for id in [0, 1, 7, 63, 255] {
            let vcpu = DarwinVcpu::new(id);
            assert_eq!(vcpu.id(), id);
        }
    }

    // ========================================================================
    // Register Tests
    // ========================================================================

    #[test]
    fn test_vcpu_registers_cached() {
        // Test that register access works on cached values
        // (not synchronized with actual vCPU state on Virtualization.framework)
        let mut vcpu = DarwinVcpu::new(0);

        // Set some registers
        let mut regs = Registers::default();
        regs.rax = 0x1234;
        regs.rip = 0x5678;

        vcpu.set_regs(&regs).unwrap();

        // Read them back - should return cached values
        let read_regs = vcpu.get_regs().unwrap();
        assert_eq!(read_regs.rax, 0x1234);
        assert_eq!(read_regs.rip, 0x5678);
    }

    #[test]
    fn test_vcpu_registers_all_gprs() {
        // Test all general-purpose registers
        let mut vcpu = DarwinVcpu::new(0);

        let mut regs = Registers::default();
        regs.rax = 0x1111_1111_1111_1111;
        regs.rbx = 0x2222_2222_2222_2222;
        regs.rcx = 0x3333_3333_3333_3333;
        regs.rdx = 0x4444_4444_4444_4444;
        regs.rsi = 0x5555_5555_5555_5555;
        regs.rdi = 0x6666_6666_6666_6666;
        regs.rsp = 0x7777_7777_7777_7777;
        regs.rbp = 0x8888_8888_8888_8888;
        regs.r8 = 0x9999_9999_9999_9999;
        regs.r9 = 0xAAAA_AAAA_AAAA_AAAA;
        regs.r10 = 0xBBBB_BBBB_BBBB_BBBB;
        regs.r11 = 0xCCCC_CCCC_CCCC_CCCC;
        regs.r12 = 0xDDDD_DDDD_DDDD_DDDD;
        regs.r13 = 0xEEEE_EEEE_EEEE_EEEE;
        regs.r14 = 0xFFFF_FFFF_FFFF_FFFF;
        regs.r15 = 0x0123_4567_89AB_CDEF;
        regs.rip = 0xDEAD_BEEF_CAFE_BABE;
        regs.rflags = 0x0000_0000_0000_0202;

        vcpu.set_regs(&regs).unwrap();
        let read_regs = vcpu.get_regs().unwrap();

        assert_eq!(read_regs.rax, regs.rax);
        assert_eq!(read_regs.rbx, regs.rbx);
        assert_eq!(read_regs.rcx, regs.rcx);
        assert_eq!(read_regs.rdx, regs.rdx);
        assert_eq!(read_regs.rsi, regs.rsi);
        assert_eq!(read_regs.rdi, regs.rdi);
        assert_eq!(read_regs.rsp, regs.rsp);
        assert_eq!(read_regs.rbp, regs.rbp);
        assert_eq!(read_regs.r8, regs.r8);
        assert_eq!(read_regs.r9, regs.r9);
        assert_eq!(read_regs.r10, regs.r10);
        assert_eq!(read_regs.r11, regs.r11);
        assert_eq!(read_regs.r12, regs.r12);
        assert_eq!(read_regs.r13, regs.r13);
        assert_eq!(read_regs.r14, regs.r14);
        assert_eq!(read_regs.r15, regs.r15);
        assert_eq!(read_regs.rip, regs.rip);
        assert_eq!(read_regs.rflags, regs.rflags);
    }

    #[test]
    fn test_vcpu_registers_multiple_updates() {
        // Test that registers can be updated multiple times
        let mut vcpu = DarwinVcpu::new(0);

        for i in 0..10u64 {
            let mut regs = Registers::default();
            regs.rax = i * 1000;
            regs.rip = 0x1000 + i * 0x100;

            vcpu.set_regs(&regs).unwrap();
            let read_regs = vcpu.get_regs().unwrap();

            assert_eq!(read_regs.rax, i * 1000);
            assert_eq!(read_regs.rip, 0x1000 + i * 0x100);
        }
    }

    // ========================================================================
    // Run Behavior Tests
    // ========================================================================

    #[test]
    fn test_vcpu_run_without_vm() {
        // When run() is called without a VZ VM pointer, it should return Halt immediately
        let mut vcpu = DarwinVcpu::new(0);
        let exit = vcpu.run().unwrap();
        assert!(matches!(exit, VcpuExit::Halt));
        assert!(!vcpu.is_running());
    }

    #[test]
    fn test_vcpu_run_with_null_managed() {
        // new_managed with null pointer should also return Halt immediately
        let mut vcpu = DarwinVcpu::new_managed(0, std::ptr::null_mut());
        let exit = vcpu.run().unwrap();
        assert!(matches!(exit, VcpuExit::Halt));
        assert!(!vcpu.is_running());
    }

    #[test]
    fn test_vcpu_run_multiple_times() {
        // run() can be called multiple times
        let mut vcpu = DarwinVcpu::new(0);

        for _ in 0..5 {
            let exit = vcpu.run().unwrap();
            assert!(matches!(exit, VcpuExit::Halt));
            assert!(!vcpu.is_running());
        }
    }

    #[test]
    fn test_vcpu_running_flag_transitions() {
        // Test that the running flag properly transitions
        let mut vcpu = DarwinVcpu::new(0);

        // Initially not running
        assert!(!vcpu.is_running());

        // Clone the flag before run
        let running_flag = vcpu.running_flag();

        // Before run, flag should be false
        assert!(!running_flag.load(std::sync::atomic::Ordering::SeqCst));

        // After run (which returns immediately with null VM), should be false again
        let _ = vcpu.run();
        assert!(!running_flag.load(std::sync::atomic::Ordering::SeqCst));
    }

    // ========================================================================
    // VM State Query Tests
    // ========================================================================

    #[test]
    fn test_vcpu_query_vm_state_without_vm() {
        let vcpu = DarwinVcpu::new(0);
        // Without VZ VM pointer, should return None
        assert!(vcpu.query_vm_state().is_none());
    }

    #[test]
    fn test_vcpu_query_vm_state_null_managed() {
        let vcpu = DarwinVcpu::new_managed(0, std::ptr::null_mut());
        assert!(vcpu.query_vm_state().is_none());
    }

    // ========================================================================
    // Linux Boot Setup Tests
    // ========================================================================

    #[test]
    fn test_vcpu_linux_setup() {
        let mut vcpu = DarwinVcpu::new(0);

        #[cfg(target_arch = "x86_64")]
        {
            vcpu.setup_linux_boot(0x100000, 0x10000);
            let regs = vcpu.get_regs().unwrap();
            assert_eq!(regs.rip, 0x100000);
            assert_eq!(regs.rsi, 0x10000);
        }

        #[cfg(target_arch = "aarch64")]
        {
            vcpu.setup_linux_boot(0x40000000, 0x44000000);
            let regs = vcpu.get_regs().unwrap();
            assert_eq!(regs.rip, 0x40000000); // PC
            assert_eq!(regs.rax, 0x44000000); // x0 = dtb
        }
    }

    #[test]
    fn test_vcpu_linux_setup_clears_previous_regs() {
        let mut vcpu = DarwinVcpu::new(0);

        // Set some garbage values first
        let mut garbage = Registers::default();
        garbage.rax = 0xDEADBEEF;
        garbage.rbx = 0xCAFEBABE;
        vcpu.set_regs(&garbage).unwrap();

        // Setup Linux boot should clear and set proper values
        #[cfg(target_arch = "x86_64")]
        {
            vcpu.setup_linux_boot(0x100000, 0x10000);
            let regs = vcpu.get_regs().unwrap();
            // rax should be cleared (not DEADBEEF)
            assert_eq!(regs.rax, 0);
            assert_eq!(regs.rbx, 0);
            assert_eq!(regs.rip, 0x100000);
            assert_eq!(regs.rsi, 0x10000);
            assert_eq!(regs.rflags, 0x2); // Reserved bit set
        }

        #[cfg(target_arch = "aarch64")]
        {
            vcpu.setup_linux_boot(0x40000000, 0x44000000);
            let regs = vcpu.get_regs().unwrap();
            assert_eq!(regs.rip, 0x40000000);
            assert_eq!(regs.rax, 0x44000000);
        }
    }

    // ========================================================================
    // Helper Method Tests
    // ========================================================================

    #[test]
    fn test_vcpu_set_instruction_pointer() {
        let mut vcpu = DarwinVcpu::new(0);

        vcpu.set_instruction_pointer(0xFFFF_0000_0000_1000);

        let regs = vcpu.get_regs().unwrap();
        assert_eq!(regs.rip, 0xFFFF_0000_0000_1000);
    }

    #[test]
    fn test_vcpu_set_stack_pointer() {
        let mut vcpu = DarwinVcpu::new(0);

        vcpu.set_stack_pointer(0x7FFF_FFFF_FFFF_FF00);

        let regs = vcpu.get_regs().unwrap();
        assert_eq!(regs.rsp, 0x7FFF_FFFF_FFFF_FF00);
    }

    // ========================================================================
    // I/O Result Tests (No-op on Darwin)
    // ========================================================================

    #[test]
    fn test_vcpu_set_io_result_noop() {
        // set_io_result should succeed but is a no-op on Virtualization.framework
        let mut vcpu = DarwinVcpu::new(0);

        let result = vcpu.set_io_result(0x1234);
        assert!(result.is_ok());

        // Call multiple times with different values
        for value in [0u64, 0xFF, 0xFFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF] {
            assert!(vcpu.set_io_result(value).is_ok());
        }
    }

    #[test]
    fn test_vcpu_set_mmio_result_noop() {
        // set_mmio_result should succeed but is a no-op on Virtualization.framework
        let mut vcpu = DarwinVcpu::new(0);

        let result = vcpu.set_mmio_result(0xDEAD_BEEF);
        assert!(result.is_ok());

        // Call multiple times with different values
        for value in [0u64, 0xFF, 0xFFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF] {
            assert!(vcpu.set_mmio_result(value).is_ok());
        }
    }

    // ========================================================================
    // Thread Safety Tests
    // ========================================================================

    #[test]
    fn test_vcpu_running_flag_is_shared() {
        // Test that running_flag() returns a shared reference
        let vcpu = DarwinVcpu::new(0);

        let flag1 = vcpu.running_flag();
        let flag2 = vcpu.running_flag();

        // Both flags should reference the same atomic
        flag1.store(true, std::sync::atomic::Ordering::SeqCst);
        assert!(flag2.load(std::sync::atomic::Ordering::SeqCst));

        flag2.store(false, std::sync::atomic::Ordering::SeqCst);
        assert!(!flag1.load(std::sync::atomic::Ordering::SeqCst));
    }

    #[test]
    fn test_vcpu_send_trait() {
        // Test that DarwinVcpu implements Send
        let vcpu = DarwinVcpu::new(0);

        // Move vcpu to another thread
        let handle = thread::spawn(move || {
            assert_eq!(vcpu.id(), 0);
        });

        handle.join().unwrap();
    }

    // ========================================================================
    // VZ State Mapping Tests (Unit tests for state conversion)
    // ========================================================================

    #[test]
    fn test_vz_state_from_i64() {
        // Test VirtualMachineState::from(i64) mapping
        assert_eq!(VirtualMachineState::from(0), VirtualMachineState::Stopped);
        assert_eq!(VirtualMachineState::from(1), VirtualMachineState::Running);
        assert_eq!(VirtualMachineState::from(2), VirtualMachineState::Paused);
        assert_eq!(VirtualMachineState::from(3), VirtualMachineState::Error);
        assert_eq!(VirtualMachineState::from(4), VirtualMachineState::Starting);
        assert_eq!(VirtualMachineState::from(5), VirtualMachineState::Pausing);
        assert_eq!(VirtualMachineState::from(6), VirtualMachineState::Resuming);
        assert_eq!(VirtualMachineState::from(7), VirtualMachineState::Stopping);
        assert_eq!(VirtualMachineState::from(8), VirtualMachineState::Saving);
        assert_eq!(VirtualMachineState::from(9), VirtualMachineState::Restoring);

        // Unknown values should map to Error
        assert_eq!(VirtualMachineState::from(10), VirtualMachineState::Error);
        assert_eq!(VirtualMachineState::from(-1), VirtualMachineState::Error);
        assert_eq!(VirtualMachineState::from(100), VirtualMachineState::Error);
    }
}