squib_core/exit.rs
1//! The portable [`VmExit`] enum.
2//!
3//! This subsumes the union of KVM's `VcpuExit` variants and the exit shapes returned by
4//! macOS Hypervisor.framework / Virtualization.framework. Each backend decodes its own
5//! native exit struct into one of these variants; the VMM dispatch loop in `squib-vmm`
6//! is therefore backend-agnostic.
7
8use smallvec::SmallVec;
9
10/// A bounded vector of bytes carrying an MMIO/PIO payload — fits in one cache line for the
11/// common 1–8 byte access patterns.
12pub type ExitData = SmallVec<[u8; 8]>;
13
14/// The reason a vCPU returned from its run loop.
15#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub enum VmExit {
18 /// Memory-mapped I/O access. The MMIO bus router in `squib-vmm` dispatches to the
19 /// appropriate device emulation.
20 Mmio {
21 /// The guest-physical address being accessed.
22 addr: u64,
23 /// `true` for a write, `false` for a read.
24 write: bool,
25 /// Payload bytes (read: filled by the device; write: the bytes the guest wrote).
26 data: ExitData,
27 },
28
29 /// Port-mapped I/O access. x86-only; on other architectures this variant never appears.
30 Pio {
31 /// The I/O port number.
32 port: u16,
33 /// `true` for `OUT`, `false` for `IN`.
34 write: bool,
35 /// Payload bytes (1, 2, or 4 wide).
36 data: ExitData,
37 },
38
39 /// The guest issued a hypercall (HVC on aarch64, VMCALL on x86).
40 Hypercall {
41 /// Hypercall function number.
42 func: u64,
43 /// Up to six argument registers from the calling convention.
44 args: [u64; 6],
45 },
46
47 /// The guest executed `WFI` (wait-for-interrupt). The vCPU should sleep until an
48 /// interrupt is injected.
49 Wfi,
50 /// The guest executed `WFE` (wait-for-event). x86's `HLT` maps to [`VmExit::Hlt`] instead.
51 Wfe,
52 /// The guest executed `HLT` (x86). Equivalent to WFI in practice.
53 Hlt,
54 /// The guest issued a system reset.
55 Reset,
56 /// The guest powered down.
57 Shutdown,
58
59 /// A debug exception (single-step, breakpoint). Surfaced when the optional `gdb` feature
60 /// is wired up; otherwise the backend treats this as an internal error.
61 Debug(DebugInfo),
62
63 /// The hypervisor reported an internal failure that does not fit any of the above
64 /// variants. The string is for human consumption only; programmatic consumers should
65 /// treat this as fatal.
66 InternalError(String),
67}
68
69/// Information surfaced on a debug exit.
70#[derive(Debug, Clone)]
71#[non_exhaustive]
72pub struct DebugInfo {
73 /// Program counter at the point of the exception.
74 pub pc: u64,
75 /// Encoded exception syndrome (architecture-specific bit layout).
76 pub syndrome: u64,
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn mmio_payload_inlines_for_small_widths() {
85 let data: ExitData = SmallVec::from_slice(&[0xAB, 0xCD]);
86 assert!(!data.spilled());
87 }
88
89 #[test]
90 fn variants_are_clone() {
91 let exit = VmExit::Wfi;
92 let _again = exit.clone();
93 }
94}