Skip to main content

squib_core/
vcpu.rs

1//! vCPU trait and the architecture-tagged register file.
2
3use crate::{error::Result, exit::VmExit};
4
5/// Architecture-tagged register file.
6///
7/// Different backends own different register subsets — a KVM x86 vCPU exposes CR0/CR3/EFER/MSRs
8/// that don't exist on aarch64 HVF, and vice versa. We model that with a sum type and let each
9/// backend convert to/from its native struct in its own crate.
10#[derive(Debug, Clone)]
11#[non_exhaustive]
12pub enum Regs {
13    /// `x86_64` register file. Field set is intentionally minimal in `squib-core`; backend
14    /// crates carry richer typed structs for their own use.
15    X86_64 {
16        /// Instruction pointer.
17        rip: u64,
18        /// Stack pointer.
19        rsp: u64,
20        /// 16 general-purpose registers in the canonical order
21        /// (RAX, RBX, RCX, RDX, RSI, RDI, RBP, R8, R9, R10, R11, R12, R13, R14, R15, FLAGS).
22        gprs: [u64; 16],
23    },
24
25    /// `aarch64` register file (EL1). PC + 31 GPRs (X0..X30) + `SP_EL1` + `PSTATE`.
26    Aarch64 {
27        /// Program counter.
28        pc: u64,
29        /// Stack pointer at EL1.
30        sp_el1: u64,
31        /// 31 general-purpose registers (X0..X30).
32        gprs: [u64; 31],
33        /// Processor state register.
34        pstate: u64,
35    },
36}
37
38/// An interrupt to be injected into a vCPU.
39///
40/// Number space and meaning depend on architecture:
41/// - x86: vector 0..=255, with `level=true` for level-triggered.
42/// - aarch64: SPI/PPI/SGI ID, `level` ignored (GIC line is implicit).
43#[derive(Debug, Clone, Copy, Eq, PartialEq)]
44#[non_exhaustive]
45pub struct Irq {
46    /// The architecture-specific interrupt identifier.
47    pub id: u32,
48    /// `true` for level-triggered, `false` for edge-triggered.
49    pub level: bool,
50}
51
52impl Irq {
53    /// Build an edge-triggered interrupt by id.
54    pub const fn edge(id: u32) -> Self {
55        Self { id, level: false }
56    }
57
58    /// Build a level-triggered interrupt by id.
59    pub const fn level(id: u32) -> Self {
60        Self { id, level: true }
61    }
62}
63
64/// A virtual CPU running inside a [`Vm`](crate::backend::Vm).
65///
66/// Each implementation of `Vcpu` is bound to a single OS thread for the duration of its
67/// lifetime, mirroring the `hv_vcpu_create`/`hv_vcpu_run` pthread-affinity rule on macOS
68/// Hypervisor.framework. Callers must not move a `Vcpu` between threads.
69pub trait Vcpu: Send {
70    /// Run the vCPU until the next exit and return the reason.
71    fn run(&mut self) -> Result<VmExit>;
72
73    /// Cancel a concurrent [`Self::run`] call. On HVF this maps to `hv_vcpus_exit`; on
74    /// KVM it sends `SIGRTMIN`. Idempotent and safe to call from any thread.
75    fn cancel(&self);
76
77    /// Read the architecture-tagged register file.
78    fn get_regs(&self) -> Result<Regs>;
79
80    /// Overwrite the architecture-tagged register file.
81    fn set_regs(&mut self, regs: &Regs) -> Result<()>;
82
83    /// Inject an interrupt that will be delivered to the guest at the next entry.
84    fn inject_irq(&mut self, irq: Irq) -> Result<()>;
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn irq_constructors_set_level() {
93        assert!(!Irq::edge(33).level);
94        assert!(Irq::level(33).level);
95    }
96
97    #[test]
98    fn aarch64_regs_have_31_gprs() {
99        let r = Regs::Aarch64 {
100            pc: 0,
101            sp_el1: 0,
102            gprs: [0; 31],
103            pstate: 0,
104        };
105        match r {
106            Regs::Aarch64 { gprs, .. } => assert_eq!(gprs.len(), 31),
107            Regs::X86_64 { .. } => unreachable!(),
108        }
109    }
110}