Skip to main content

ostd/arch/x86/vm/
exit.rs

1// SPDX-License-Identifier: MPL-2.0
2
3use int_to_c_enum::TryFromInt;
4use x86::vmx::vmcs::ro;
5
6use super::vmx::vmcs::CurrentVmcs;
7use crate::{Error, prelude::*, vm::Gpaddr};
8
9/// A VM exit that needs handling by the kernel client.
10#[derive(Clone, Copy, Debug)]
11pub struct GuestExitInfo {
12    /// The basic VMX exit reason.
13    pub exit_reason: u32,
14    /// The length of the exiting instruction, or unspecified for non-instruction exits.
15    pub instruction_len: u32,
16    /// Architecture-specific exit qualification.
17    pub exit_qualification: u64,
18    /// The guest physical address for an EPT exit, or zero otherwise.
19    pub guest_phys_addr: Gpaddr,
20    /// VM-exit interruption information for an exception or NMI, or zero otherwise.
21    pub interruption_info: u32,
22    /// The exception error code, if indicated by `interruption_info`.
23    pub interruption_error_code: u32,
24    /// IDT-vectoring information for an interrupted event delivery.
25    pub idt_vectoring_info: u32,
26    /// The exception error code, if indicated by `idt_vectoring_info`.
27    pub idt_vectoring_error_code: u32,
28}
29
30/// VMX basic exit reasons.
31///
32/// See Intel SDM, Vol. 3D, Appendix C, Table C-1
33#[expect(non_camel_case_types)]
34#[repr(u32)]
35#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromInt)]
36pub enum VmxExitReason {
37    /// An exception selected by the exception bitmap, or an intercepted NMI.
38    EXCEPTION_NMI = 0,
39    /// An external interrupt with external-interrupt exiting enabled.
40    EXTERNAL_INTERRUPT = 1,
41    /// An exception during double-fault delivery that was not intercepted.
42    TRIPLE_FAULT = 2,
43    /// Receipt of an INIT signal.
44    INIT = 3,
45    /// Receipt of a startup IPI while waiting for SIPI.
46    SIPI = 4,
47    /// An SMM VM exit caused by an SMI immediately after an I/O instruction retires.
48    SMI = 5,
49    /// An SMM VM exit caused by an SMI not immediately following I/O instruction retirement.
50    OTHER_SMI = 6,
51    /// An interrupt window with `RFLAGS.IF` set, no `STI` or `MOV SS` blocking,
52    /// and interrupt-window exiting enabled.
53    INTERRUPT_WINDOW = 7,
54    /// An NMI window with no virtual-NMI or `MOV SS` blocking and NMI-window exiting enabled.
55    NMI_WINDOW = 8,
56    /// A task switch attempted by the guest.
57    TASK_SWITCH = 9,
58    /// An attempt to execute `CPUID`.
59    CPUID = 10,
60    /// An attempt to execute `GETSEC`.
61    GETSEC = 11,
62    /// An intercepted `HLT` instruction.
63    HLT = 12,
64    /// An attempt to execute `INVD`.
65    INVD = 13,
66    /// An intercepted `INVLPG` instruction.
67    INVLPG = 14,
68    /// An intercepted `RDPMC` instruction.
69    RDPMC = 15,
70    /// An intercepted `RDTSC` instruction.
71    RDTSC = 16,
72    /// An attempt to execute `RSM` in SMM.
73    RSM = 17,
74    /// A `VMCALL` from the guest or the executive monitor.
75    VMCALL = 18,
76    /// An attempt to execute `VMCLEAR`.
77    VMCLEAR = 19,
78    /// An attempt to execute `VMLAUNCH`.
79    VMLAUNCH = 20,
80    /// An attempt to execute `VMPTRLD`.
81    VMPTRLD = 21,
82    /// An attempt to execute `VMPTRST`.
83    VMPTRST = 22,
84    /// An attempt to execute `VMREAD`.
85    VMREAD = 23,
86    /// An attempt to execute `VMRESUME`.
87    VMRESUME = 24,
88    /// An attempt to execute `VMWRITE`.
89    VMWRITE = 25,
90    /// An attempt to execute `VMXOFF`.
91    VMOFF = 26,
92    /// An attempt to execute `VMXON`.
93    VMON = 27,
94    /// An intercepted access to `CR0`, `CR3`, `CR4`, or `CR8` via `CLTS`, `LMSW`, or `MOV CR`.
95    CR_ACCESS = 28,
96    /// An intercepted `MOV` to or from a debug register.
97    DR_ACCESS = 29,
98    /// An I/O instruction intercepted by I/O exiting controls or the I/O bitmaps.
99    IO_INSTRUCTION = 30,
100    /// An intercepted `RDMSR` instruction.
101    MSR_READ = 31,
102    /// An intercepted `WRMSR` or `WRMSRNS` instruction.
103    MSR_WRITE = 32,
104    /// A VM-entry failure caused by invalid guest state.
105    INVALID_GUEST_STATE = 33,
106    /// A VM-entry failure while loading guest MSRs.
107    MSR_LOAD_FAIL = 34,
108    /// An intercepted `MWAIT` instruction.
109    MWAIT_INSTRUCTION = 36,
110    /// A monitor-trap-flag exit enabled by VM controls or injected on VM entry.
111    MONITOR_TRAP_FLAG = 37,
112    /// An intercepted `MONITOR` instruction.
113    MONITOR_INSTRUCTION = 39,
114    /// An intercepted `PAUSE` instruction or a pause loop exceeding `PLE_Window`.
115    PAUSE_INSTRUCTION = 40,
116    /// A machine-check event during VM entry.
117    MCE_DURING_VMENTRY = 41,
118    /// A virtual TPR priority below the configured threshold while TPR shadowing is enabled.
119    TPR_BELOW_THRESHOLD = 43,
120    /// An intercepted memory access to the APIC-access page.
121    APIC_ACCESS = 44,
122    /// A virtual EOI for an interrupt selected by the EOI-exit bitmap.
123    VIRTUALIZED_EOI = 45,
124    /// An intercepted `LGDT`, `LIDT`, `SGDT`, or `SIDT` instruction.
125    GDTR_IDTR = 46,
126    /// An intercepted `LLDT`, `LTR`, `SLDT`, or `STR` instruction.
127    LDTR_TR = 47,
128    /// A guest physical memory access denied by the EPT paging structures.
129    EPT_VIOLATION = 48,
130    /// A guest physical memory access encountering an invalid EPT entry.
131    EPT_MISCONFIG = 49,
132    /// An attempt to execute `INVEPT`.
133    INVEPT = 50,
134    /// An intercepted `RDTSCP` instruction.
135    RDTSCP = 51,
136    /// Expiration of the VMX preemption timer.
137    PREEMPTION_TIMER = 52,
138    /// An attempt to execute `INVVPID`.
139    INVVPID = 53,
140    /// An intercepted `WBINVD` or `WBNOINVD` instruction.
141    WBINVD = 54,
142    /// An attempt to execute `XSETBV`.
143    XSETBV = 55,
144    /// A completed write to the virtual-APIC page requiring VMM emulation.
145    APIC_WRITE = 56,
146    /// An intercepted `RDRAND` instruction.
147    RDRAND = 57,
148    /// An intercepted `INVPCID` instruction.
149    INVPCID = 58,
150    /// A `VMFUNC` for a disabled function or a function-specific exit condition.
151    VMFUNC = 59,
152    /// An `ENCLS` leaf intercepted by the ENCLS-exiting bitmap.
153    ENCLS = 60,
154    /// An intercepted `RDSEED` instruction.
155    RDSEED = 61,
156    /// An attempted page-modification log update with the PML index outside `0..=511`.
157    PML_FULL = 62,
158    /// An `XSAVES` request for state selected by `IA32_XSS` and the XSS-exiting bitmap.
159    XSAVES = 63,
160    /// An `XRSTORS` request for state selected by `IA32_XSS` and the XSS-exiting bitmap.
161    XRSTORS = 64,
162    /// A `PCONFIG` leaf intercepted by the PCONFIG-exiting bitmap.
163    PCONFIG = 65,
164    /// A miss or misconfiguration while looking up sub-page write permissions.
165    SPP_EVENT = 66,
166    /// An intercepted `UMWAIT` instruction.
167    UMWAIT = 67,
168    /// An intercepted `TPAUSE` instruction.
169    TPAUSE = 68,
170    /// An intercepted `LOADIWKEY` instruction.
171    LOADIWKEY = 69,
172}
173
174/// Reads the current VMCS's exit information.
175pub(super) fn exit_info(vmcs: &CurrentVmcs<'_>) -> Result<GuestExitInfo> {
176    let raw_reason = vmcs.read(ro::EXIT_REASON)?;
177    // A late VM-entry failure reaches the VM-exit handler instead of setting
178    // CF/ZF at VMLAUNCH/VMRESUME (Intel SDM, Vol. 3C, Section 26.7).
179    if raw_reason & (1 << 31) != 0 {
180        return Err(Error::InvalidArgs);
181    }
182    let reason = (raw_reason & 0xffff) as u32;
183
184    let is_ept = reason == VmxExitReason::EPT_VIOLATION as u32
185        || reason == VmxExitReason::EPT_MISCONFIG as u32;
186    let guest_phys_addr = if is_ept {
187        vmcs.read(ro::GUEST_PHYSICAL_ADDR_FULL)?
188    } else {
189        0
190    };
191
192    const ERROR_CODE_VALID: u32 = (1 << 31) | (1 << 11);
193
194    let interruption_info = if reason == VmxExitReason::EXCEPTION_NMI as u32 {
195        vmcs.read(ro::VMEXIT_INTERRUPTION_INFO)? as u32
196    } else {
197        0
198    };
199    let interruption_error_code = if interruption_info & ERROR_CODE_VALID == ERROR_CODE_VALID {
200        vmcs.read(ro::VMEXIT_INTERRUPTION_ERR_CODE)? as u32
201    } else {
202        0
203    };
204
205    let idt_vectoring_info = vmcs.read(ro::IDT_VECTORING_INFO)? as u32;
206    let idt_vectoring_error_code = if idt_vectoring_info & ERROR_CODE_VALID == ERROR_CODE_VALID {
207        vmcs.read(ro::IDT_VECTORING_ERR_CODE)? as u32
208    } else {
209        0
210    };
211
212    Ok(GuestExitInfo {
213        exit_reason: reason,
214        instruction_len: vmcs.read(ro::VMEXIT_INSTRUCTION_LEN)? as u32,
215        exit_qualification: vmcs.read(ro::EXIT_QUALIFICATION)? as u64,
216        guest_phys_addr,
217        interruption_info,
218        interruption_error_code,
219        idt_vectoring_info,
220        idt_vectoring_error_code,
221    })
222}