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}