Skip to main content

arcbox_hypervisor/linux/
vcpu.rs

1//! Virtual CPU implementation for Linux KVM.
2
3use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use crate::{
7    error::HypervisorError,
8    traits::Vcpu,
9    types::{CpuArch, Registers, VcpuExit, VcpuSnapshot},
10};
11
12use super::ffi::{
13    KVM_EXIT_DEBUG, KVM_EXIT_FAIL_ENTRY, KVM_EXIT_HLT, KVM_EXIT_INTERNAL_ERROR, KVM_EXIT_IO,
14    KVM_EXIT_IO_IN, KVM_EXIT_IO_OUT, KVM_EXIT_MMIO, KVM_EXIT_SHUTDOWN, KVM_EXIT_SYSTEM_EVENT,
15    KvmVcpuFd,
16};
17
18#[cfg(target_arch = "x86_64")]
19use super::ffi::{KvmRegs, KvmSegment, KvmSregs};
20
21/// Virtual CPU implementation for Linux KVM.
22///
23/// Each vCPU represents a virtual processor that can execute guest code.
24/// vCPUs are created via the KVM VM file descriptor and run using the
25/// KVM_RUN ioctl.
26pub struct KvmVcpu {
27    /// vCPU ID.
28    id: u32,
29    /// KVM vCPU file descriptor.
30    vcpu_fd: KvmVcpuFd,
31    /// Whether the vCPU is running.
32    running: Arc<AtomicBool>,
33}
34
35impl KvmVcpu {
36    /// Creates a new vCPU wrapper.
37    pub(crate) fn new(id: u32, vcpu_fd: KvmVcpuFd) -> Result<Self, HypervisorError> {
38        let vcpu = Self {
39            id,
40            vcpu_fd,
41            running: Arc::new(AtomicBool::new(false)),
42        };
43
44        // Initialize architecture-specific state
45        #[cfg(target_arch = "x86_64")]
46        vcpu.init_x86()?;
47
48        #[cfg(target_arch = "aarch64")]
49        vcpu.init_arm64()?;
50
51        Ok(vcpu)
52    }
53
54    /// Initializes x86 vCPU state.
55    #[cfg(target_arch = "x86_64")]
56    fn init_x86(&self) -> Result<(), HypervisorError> {
57        // Set up initial special registers for real mode
58        let mut sregs =
59            self.vcpu_fd
60                .get_sregs()
61                .map_err(|e| HypervisorError::VcpuCreationFailed {
62                    id: self.id,
63                    reason: format!("Failed to get sregs: {}", e),
64                })?;
65
66        // Set up code segment for real mode
67        sregs.cs = KvmSegment {
68            base: 0,
69            limit: 0xffff,
70            selector: 0,
71            type_: 0xb, // Code: execute/read, accessed
72            present: 1,
73            dpl: 0,
74            db: 0,
75            s: 1,
76            l: 0,
77            g: 0,
78            avl: 0,
79            unusable: 0,
80            padding: 0,
81        };
82
83        // Set up data segment
84        sregs.ds = KvmSegment {
85            base: 0,
86            limit: 0xffff,
87            selector: 0,
88            type_: 0x3, // Data: read/write, accessed
89            present: 1,
90            dpl: 0,
91            db: 0,
92            s: 1,
93            l: 0,
94            g: 0,
95            avl: 0,
96            unusable: 0,
97            padding: 0,
98        };
99
100        sregs.es = sregs.ds.clone();
101        sregs.fs = sregs.ds.clone();
102        sregs.gs = sregs.ds.clone();
103        sregs.ss = sregs.ds.clone();
104
105        // CR0: PE=0 (real mode), disable paging
106        sregs.cr0 = 0x6000_0010; // ET=1, NE=1
107
108        self.vcpu_fd
109            .set_sregs(&sregs)
110            .map_err(|e| HypervisorError::VcpuCreationFailed {
111                id: self.id,
112                reason: format!("Failed to set sregs: {}", e),
113            })?;
114
115        Ok(())
116    }
117
118    /// Initializes ARM64 vCPU state.
119    #[cfg(target_arch = "aarch64")]
120    fn init_arm64(&self) -> Result<(), HypervisorError> {
121        // ARM64 vCPU initialization is handled differently
122        // The preferred target is set at VM creation time
123        // Individual registers are set using KVM_SET_ONE_REG
124
125        // For now, we don't do any special initialization here
126        // Real implementation would set up initial register state
127
128        Ok(())
129    }
130
131    /// Returns whether the vCPU is currently running.
132    #[must_use]
133    pub fn is_running(&self) -> bool {
134        self.running.load(Ordering::SeqCst)
135    }
136
137    /// Returns a clone of the running flag for external monitoring.
138    #[must_use]
139    pub fn running_flag(&self) -> Arc<AtomicBool> {
140        Arc::clone(&self.running)
141    }
142
143    /// Signals the vCPU to exit immediately.
144    ///
145    /// This causes the next KVM_RUN to return immediately.
146    pub fn signal_exit(&self) {
147        self.vcpu_fd.set_immediate_exit(true);
148    }
149
150    /// Sets up initial register state for Linux boot (x86_64).
151    #[cfg(target_arch = "x86_64")]
152    pub fn setup_linux_boot(
153        &self,
154        entry_point: u64,
155        boot_params_addr: u64,
156    ) -> Result<(), HypervisorError> {
157        // Set up special registers for protected mode
158        let mut sregs = self
159            .vcpu_fd
160            .get_sregs()
161            .map_err(|e| HypervisorError::VcpuRunError(format!("Failed to get sregs: {}", e)))?;
162
163        // Enable protected mode with paging disabled
164        // Linux 64-bit kernel expects to be entered in protected mode
165        sregs.cr0 = 0x6000_0011; // PE=1, ET=1, NE=1
166        sregs.cr3 = 0;
167        sregs.cr4 = 0;
168
169        // Set up code segment for 32-bit protected mode
170        // (Linux kernel will switch to long mode itself)
171        sregs.cs = KvmSegment {
172            base: 0,
173            limit: 0xffff_ffff,
174            selector: 0x10,
175            type_: 0xb, // Code: execute/read, accessed
176            present: 1,
177            dpl: 0,
178            db: 1, // 32-bit segment
179            s: 1,
180            l: 0,
181            g: 1, // 4KB granularity
182            avl: 0,
183            unusable: 0,
184            padding: 0,
185        };
186
187        // Set up data segment
188        sregs.ds = KvmSegment {
189            base: 0,
190            limit: 0xffff_ffff,
191            selector: 0x18,
192            type_: 0x3, // Data: read/write, accessed
193            present: 1,
194            dpl: 0,
195            db: 1,
196            s: 1,
197            l: 0,
198            g: 1,
199            avl: 0,
200            unusable: 0,
201            padding: 0,
202        };
203
204        sregs.es = sregs.ds.clone();
205        sregs.fs = sregs.ds.clone();
206        sregs.gs = sregs.ds.clone();
207        sregs.ss = sregs.ds.clone();
208
209        self.vcpu_fd
210            .set_sregs(&sregs)
211            .map_err(|e| HypervisorError::VcpuRunError(format!("Failed to set sregs: {}", e)))?;
212
213        // Set up general purpose registers
214        let regs = KvmRegs {
215            rip: entry_point,
216            rsi: boot_params_addr, // Linux boot protocol: RSI = boot_params
217            rflags: 0x2,           // Reserved bit always set
218            ..Default::default()
219        };
220
221        self.vcpu_fd
222            .set_regs(&regs)
223            .map_err(|e| HypervisorError::VcpuRunError(format!("Failed to set regs: {}", e)))?;
224
225        tracing::debug!(
226            "vCPU {} setup for Linux boot: entry={:#x}, boot_params={:#x}",
227            self.id,
228            entry_point,
229            boot_params_addr
230        );
231
232        Ok(())
233    }
234
235    /// Sets up initial register state for Linux boot (ARM64).
236    #[cfg(target_arch = "aarch64")]
237    pub fn setup_linux_boot(&self, entry_point: u64, dtb_addr: u64) -> Result<(), HypervisorError> {
238        use super::ffi::arm64_regs;
239
240        // ARM64 Linux boot protocol:
241        // x0 = physical address of DTB
242        // PC = kernel entry point
243        // All other registers should be 0
244        // PSTATE should be EL1h with interrupts masked
245
246        // Set x0 = DTB address
247        self.vcpu_fd
248            .set_one_reg(arm64_regs::X0, dtb_addr)
249            .map_err(|e| HypervisorError::VcpuRunError(format!("Failed to set x0: {}", e)))?;
250
251        // Set PC = entry point
252        self.vcpu_fd
253            .set_one_reg(arm64_regs::PC, entry_point)
254            .map_err(|e| HypervisorError::VcpuRunError(format!("Failed to set PC: {}", e)))?;
255
256        // Set PSTATE for EL1h with interrupts masked
257        let pstate = arm64_regs::PSTATE_EL1H
258            | arm64_regs::PSTATE_D
259            | arm64_regs::PSTATE_A
260            | arm64_regs::PSTATE_I
261            | arm64_regs::PSTATE_F;
262        self.vcpu_fd
263            .set_one_reg(arm64_regs::PSTATE, pstate)
264            .map_err(|e| HypervisorError::VcpuRunError(format!("Failed to set PSTATE: {}", e)))?;
265
266        // Clear other important registers
267        self.vcpu_fd.set_one_reg(arm64_regs::X1, 0).ok(); // Ignore errors for optional regs
268        self.vcpu_fd.set_one_reg(arm64_regs::X2, 0).ok();
269        self.vcpu_fd.set_one_reg(arm64_regs::X3, 0).ok();
270        self.vcpu_fd.set_one_reg(arm64_regs::SP, 0).ok();
271
272        tracing::debug!(
273            "vCPU {} setup for Linux boot: entry={:#x}, dtb={:#x}, pstate={:#x}",
274            self.id,
275            entry_point,
276            dtb_addr,
277            pstate
278        );
279
280        Ok(())
281    }
282
283    /// Converts KVM exit reason to our VcpuExit type.
284    fn convert_exit(&self) -> VcpuExit {
285        let exit_reason = self.vcpu_fd.exit_reason();
286
287        match exit_reason {
288            KVM_EXIT_HLT => VcpuExit::Halt,
289
290            KVM_EXIT_IO => {
291                let io = unsafe { (*self.vcpu_fd.kvm_run()).exit_data.io };
292                if io.direction == KVM_EXIT_IO_OUT {
293                    // For OUT instructions, data is at kvm_run + data_offset
294                    let data_ptr = unsafe {
295                        (self.vcpu_fd.kvm_run() as *const _ as *const u8)
296                            .add(io.data_offset as usize)
297                    };
298                    // SAFETY: data_ptr points into the kvm_run mmap region at
299                    // an offset determined by KVM. It may not be aligned for
300                    // u16/u32, so we use read_unaligned to avoid UB.
301                    let data = match io.size {
302                        1 => (unsafe { *data_ptr }) as u64,
303                        2 => (unsafe { std::ptr::read_unaligned(data_ptr as *const u16) }) as u64,
304                        4 => (unsafe { std::ptr::read_unaligned(data_ptr as *const u32) }) as u64,
305                        _ => 0,
306                    };
307                    VcpuExit::IoOut {
308                        port: io.port,
309                        size: io.size,
310                        data,
311                    }
312                } else {
313                    VcpuExit::IoIn {
314                        port: io.port,
315                        size: io.size,
316                    }
317                }
318            }
319
320            KVM_EXIT_MMIO => {
321                let mmio = unsafe { (*self.vcpu_fd.kvm_run()).exit_data.mmio };
322                if mmio.is_write != 0 {
323                    let data = match mmio.len {
324                        1 => mmio.data[0] as u64,
325                        2 => u16::from_le_bytes([mmio.data[0], mmio.data[1]]) as u64,
326                        4 => u32::from_le_bytes([
327                            mmio.data[0],
328                            mmio.data[1],
329                            mmio.data[2],
330                            mmio.data[3],
331                        ]) as u64,
332                        8 => u64::from_le_bytes([
333                            mmio.data[0],
334                            mmio.data[1],
335                            mmio.data[2],
336                            mmio.data[3],
337                            mmio.data[4],
338                            mmio.data[5],
339                            mmio.data[6],
340                            mmio.data[7],
341                        ]),
342                        _ => 0,
343                    };
344                    VcpuExit::MmioWrite {
345                        addr: mmio.phys_addr,
346                        size: mmio.len as u8,
347                        data,
348                    }
349                } else {
350                    VcpuExit::MmioRead {
351                        addr: mmio.phys_addr,
352                        size: mmio.len as u8,
353                    }
354                }
355            }
356
357            KVM_EXIT_SHUTDOWN => VcpuExit::Shutdown,
358
359            KVM_EXIT_DEBUG => VcpuExit::Debug,
360
361            KVM_EXIT_SYSTEM_EVENT => {
362                let event = unsafe { (*self.vcpu_fd.kvm_run()).exit_data.system_event };
363                match event.type_ {
364                    1 => VcpuExit::Shutdown,    // KVM_SYSTEM_EVENT_SHUTDOWN
365                    2 => VcpuExit::SystemReset, // KVM_SYSTEM_EVENT_RESET
366                    _ => VcpuExit::Unknown(exit_reason as i32),
367                }
368            }
369
370            KVM_EXIT_FAIL_ENTRY | KVM_EXIT_INTERNAL_ERROR => {
371                tracing::error!(
372                    "vCPU {} internal error: exit_reason={}",
373                    self.id,
374                    exit_reason
375                );
376                VcpuExit::Unknown(exit_reason as i32)
377            }
378
379            _ => VcpuExit::Unknown(exit_reason as i32),
380        }
381    }
382
383    /// Provides data for an I/O IN instruction.
384    pub fn set_io_in_data(&self, data: &[u8]) {
385        unsafe {
386            let kvm_run = self.vcpu_fd.kvm_run_mut();
387            let io = kvm_run.exit_data.io;
388            let data_ptr = (kvm_run as *mut _ as *mut u8).add(io.data_offset as usize);
389            std::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
390        }
391    }
392
393    /// Provides data for an MMIO read.
394    pub fn set_mmio_read_data(&self, data: &[u8]) {
395        unsafe {
396            let kvm_run = self.vcpu_fd.kvm_run_mut();
397            let mmio = &mut kvm_run.exit_data.mmio;
398            let len = std::cmp::min(data.len(), mmio.data.len());
399            mmio.data[..len].copy_from_slice(&data[..len]);
400        }
401    }
402}
403
404impl Vcpu for KvmVcpu {
405    fn run(&mut self) -> Result<VcpuExit, HypervisorError> {
406        self.running.store(true, Ordering::SeqCst);
407
408        // Clear immediate exit flag
409        self.vcpu_fd.set_immediate_exit(false);
410
411        // Run the vCPU
412        let result = self.vcpu_fd.run();
413
414        self.running.store(false, Ordering::SeqCst);
415
416        match result {
417            Ok(()) => Ok(self.convert_exit()),
418            Err(e) => Err(HypervisorError::VcpuRunError(format!(
419                "vCPU {} run failed: {}",
420                self.id, e
421            ))),
422        }
423    }
424
425    fn get_regs(&self) -> Result<Registers, HypervisorError> {
426        let kvm_regs = self.vcpu_fd.get_regs().map_err(|e| {
427            HypervisorError::VcpuRunError(format!("Failed to get registers: {}", e))
428        })?;
429
430        #[cfg(target_arch = "x86_64")]
431        {
432            Ok(Registers {
433                rax: kvm_regs.rax,
434                rbx: kvm_regs.rbx,
435                rcx: kvm_regs.rcx,
436                rdx: kvm_regs.rdx,
437                rsi: kvm_regs.rsi,
438                rdi: kvm_regs.rdi,
439                rsp: kvm_regs.rsp,
440                rbp: kvm_regs.rbp,
441                r8: kvm_regs.r8,
442                r9: kvm_regs.r9,
443                r10: kvm_regs.r10,
444                r11: kvm_regs.r11,
445                r12: kvm_regs.r12,
446                r13: kvm_regs.r13,
447                r14: kvm_regs.r14,
448                r15: kvm_regs.r15,
449                rip: kvm_regs.rip,
450                rflags: kvm_regs.rflags,
451            })
452        }
453
454        #[cfg(target_arch = "aarch64")]
455        {
456            // ARM64 uses a different register structure
457            // Map to our generic x86-style Registers for now
458            Ok(Registers {
459                rax: kvm_regs.regs[0],
460                rbx: kvm_regs.regs[1],
461                rcx: kvm_regs.regs[2],
462                rdx: kvm_regs.regs[3],
463                rsi: kvm_regs.regs[4],
464                rdi: kvm_regs.regs[5],
465                rsp: kvm_regs.sp,
466                rbp: kvm_regs.regs[29],
467                r8: kvm_regs.regs[8],
468                r9: kvm_regs.regs[9],
469                r10: kvm_regs.regs[10],
470                r11: kvm_regs.regs[11],
471                r12: kvm_regs.regs[12],
472                r13: kvm_regs.regs[13],
473                r14: kvm_regs.regs[14],
474                r15: kvm_regs.regs[15],
475                rip: kvm_regs.pc,
476                rflags: kvm_regs.pstate,
477            })
478        }
479    }
480
481    fn set_regs(&mut self, regs: &Registers) -> Result<(), HypervisorError> {
482        #[cfg(target_arch = "x86_64")]
483        {
484            let kvm_regs = KvmRegs {
485                rax: regs.rax,
486                rbx: regs.rbx,
487                rcx: regs.rcx,
488                rdx: regs.rdx,
489                rsi: regs.rsi,
490                rdi: regs.rdi,
491                rsp: regs.rsp,
492                rbp: regs.rbp,
493                r8: regs.r8,
494                r9: regs.r9,
495                r10: regs.r10,
496                r11: regs.r11,
497                r12: regs.r12,
498                r13: regs.r13,
499                r14: regs.r14,
500                r15: regs.r15,
501                rip: regs.rip,
502                rflags: regs.rflags,
503            };
504
505            self.vcpu_fd.set_regs(&kvm_regs).map_err(|e| {
506                HypervisorError::VcpuRunError(format!("Failed to set registers: {}", e))
507            })?;
508        }
509
510        #[cfg(target_arch = "aarch64")]
511        {
512            // ARM64 registers would need to be set individually
513            // using KVM_SET_ONE_REG
514            let _ = regs; // Suppress unused warning
515            return Err(HypervisorError::VcpuRunError(
516                "ARM64 register setting not fully implemented".to_string(),
517            ));
518        }
519
520        Ok(())
521    }
522
523    fn id(&self) -> u32 {
524        self.id
525    }
526
527    fn set_io_result(&mut self, value: u64) -> Result<(), HypervisorError> {
528        // For I/O IN operations, write the result back to the data area
529        let bytes = value.to_le_bytes();
530        unsafe {
531            let kvm_run = self.vcpu_fd.kvm_run_mut();
532            let io = kvm_run.exit_data.io;
533            let size = io.size as usize;
534            let data_ptr = (kvm_run as *mut _ as *mut u8).add(io.data_offset as usize);
535            std::ptr::copy_nonoverlapping(bytes.as_ptr(), data_ptr, size.min(8));
536        }
537        Ok(())
538    }
539
540    fn set_mmio_result(&mut self, value: u64) -> Result<(), HypervisorError> {
541        // For MMIO read operations, write the result back to the mmio data area
542        let bytes = value.to_le_bytes();
543        unsafe {
544            let kvm_run = self.vcpu_fd.kvm_run_mut();
545            let mmio = &mut kvm_run.exit_data.mmio;
546            let len = (mmio.len as usize).min(8);
547            mmio.data[..len].copy_from_slice(&bytes[..len]);
548        }
549        Ok(())
550    }
551
552    fn snapshot(&self) -> Result<VcpuSnapshot, HypervisorError> {
553        #[cfg(target_arch = "x86_64")]
554        {
555            let regs = self.get_regs()?;
556            Ok(VcpuSnapshot::new_x86(self.id, regs))
557        }
558
559        #[cfg(target_arch = "aarch64")]
560        {
561            use super::ffi::arm64_regs;
562
563            // Read ARM64 registers
564            let mut arm_regs = crate::types::Arm64Registers::default();
565
566            // Read X0-X30
567            for i in 0..31 {
568                let reg_id = arm64_regs::X0 + (i as u64) * 2; // Each reg is 2 u64s apart in encoding
569                if let Ok(val) = self.vcpu_fd.get_one_reg(reg_id) {
570                    arm_regs.x[i] = val;
571                }
572            }
573
574            // Read SP, PC, PSTATE
575            arm_regs.sp = self.vcpu_fd.get_one_reg(arm64_regs::SP).unwrap_or(0);
576            arm_regs.pc = self.vcpu_fd.get_one_reg(arm64_regs::PC).unwrap_or(0);
577            arm_regs.pstate = self.vcpu_fd.get_one_reg(arm64_regs::PSTATE).unwrap_or(0);
578
579            // Note: FPCR, FPSR, and vector registers would need additional KVM_GET_ONE_REG calls
580
581            Ok(VcpuSnapshot::new_arm64(self.id, arm_regs))
582        }
583    }
584
585    fn restore(&mut self, snapshot: &VcpuSnapshot) -> Result<(), HypervisorError> {
586        if snapshot.id != self.id {
587            return Err(HypervisorError::SnapshotError(format!(
588                "vCPU ID mismatch: expected {}, got {}",
589                self.id, snapshot.id
590            )));
591        }
592
593        #[cfg(target_arch = "x86_64")]
594        {
595            if let Some(regs) = &snapshot.x86_regs {
596                self.set_regs(regs)?;
597            }
598        }
599
600        #[cfg(target_arch = "aarch64")]
601        {
602            use super::ffi::arm64_regs;
603
604            if let Some(arm_regs) = &snapshot.arm64_regs {
605                // Restore X0-X30
606                for i in 0..31 {
607                    let reg_id = arm64_regs::X0 + (i as u64) * 2;
608                    let _ = self.vcpu_fd.set_one_reg(reg_id, arm_regs.x[i]);
609                }
610
611                // Restore SP, PC, PSTATE
612                let _ = self.vcpu_fd.set_one_reg(arm64_regs::SP, arm_regs.sp);
613                let _ = self.vcpu_fd.set_one_reg(arm64_regs::PC, arm_regs.pc);
614                let _ = self
615                    .vcpu_fd
616                    .set_one_reg(arm64_regs::PSTATE, arm_regs.pstate);
617            }
618        }
619
620        Ok(())
621    }
622}
623
624/// Extended vCPU state for x86_64.
625#[cfg(target_arch = "x86_64")]
626#[derive(Debug, Clone, Default)]
627pub struct SpecialRegisters {
628    /// Code segment.
629    pub cs: SegmentRegister,
630    /// Data segment.
631    pub ds: SegmentRegister,
632    /// Stack segment.
633    pub ss: SegmentRegister,
634    /// Extra segment.
635    pub es: SegmentRegister,
636    /// FS segment.
637    pub fs: SegmentRegister,
638    /// GS segment.
639    pub gs: SegmentRegister,
640    /// Global descriptor table.
641    pub gdt: DescriptorTable,
642    /// Interrupt descriptor table.
643    pub idt: DescriptorTable,
644    /// Control register 0.
645    pub cr0: u64,
646    /// Control register 3 (page table base).
647    pub cr3: u64,
648    /// Control register 4.
649    pub cr4: u64,
650    /// Extended feature enable register.
651    pub efer: u64,
652}
653
654/// Segment register.
655#[cfg(target_arch = "x86_64")]
656#[derive(Debug, Clone, Default)]
657pub struct SegmentRegister {
658    /// Base address.
659    pub base: u64,
660    /// Limit.
661    pub limit: u32,
662    /// Selector.
663    pub selector: u16,
664    /// Type.
665    pub type_: u8,
666    /// Present.
667    pub present: u8,
668    /// Descriptor privilege level.
669    pub dpl: u8,
670    /// Default operation size.
671    pub db: u8,
672    /// Granularity.
673    pub granularity: u8,
674    /// Long mode.
675    pub long_mode: u8,
676}
677
678/// Descriptor table (GDT/IDT).
679#[cfg(target_arch = "x86_64")]
680#[derive(Debug, Clone, Default)]
681pub struct DescriptorTable {
682    /// Base address.
683    pub base: u64,
684    /// Limit.
685    pub limit: u16,
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691
692    #[test]
693    fn test_vcpu_running_flag() {
694        // Just test that we can create and check the running flag
695        let running = Arc::new(AtomicBool::new(false));
696        assert!(!running.load(Ordering::SeqCst));
697        running.store(true, Ordering::SeqCst);
698        assert!(running.load(Ordering::SeqCst));
699    }
700}