vmcs, controls, err, exit_reason

This commit is contained in:
Masato Imai
2025-08-04 10:30:34 +00:00
parent 7e32503bcb
commit a0a49ab92c
8 changed files with 414 additions and 50 deletions

View File

@ -141,12 +141,19 @@ dependencies = [
"linked_list_allocator",
"modular-bitfield",
"nel_os_common",
"numeric-enum-macro",
"raw-cpuid 11.5.0",
"spin 0.10.0",
"uart_16550",
"x86_64",
]
[[package]]
name = "numeric-enum-macro"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e4bdb6b46b592948e700ea1ef24a4296491f6a0ee722b258040abd15a3714"
[[package]]
name = "owned_ttf_parser"
version = "0.25.0"

View File

@ -14,3 +14,4 @@ ab_glyph = { version = "0.2", features = ["libm"], default-features = false }
raw-cpuid = "11.5.0"
acpi = "5.2.0"
modular-bitfield = "0.12.0"
numeric-enum-macro = "0.2.0"

View File

@ -1,6 +1,8 @@
mod vmcs;
mod vmxon;
use core::arch::asm;
use raw_cpuid::cpuid;
use x86_64::{
registers::rflags::{self, RFlags},
@ -72,3 +74,31 @@ pub fn vmx_capture_status() -> Result<(), &'static str> {
Ok(())
}
}
pub fn vmread(field: u32) -> Result<u64, &'static str> {
let field: u64 = field.into();
let value: u64;
unsafe {
asm!(
"vmread {0}, {1}",
in(reg) field,
out(reg) value,
options(att_syntax)
)
};
vmx_capture_status()?;
Ok(value)
}
pub fn vmwrite(field: u32, value: u64) -> Result<(), &'static str> {
let field: u64 = field.into();
unsafe {
asm!(
"vmwrite {1}, {0}",
in(reg) field,
in(reg) value,
options(att_syntax)
)
};
vmx_capture_status()
}

View File

@ -1,50 +0,0 @@
use core::arch::asm;
use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB};
use crate::vmm::x86_64::intel::vmx_capture_status;
pub struct Vmcs {
pub frame: PhysFrame,
}
impl Vmcs {
pub fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Result<Self, &'static str> {
let frame = frame_allocator
.allocate_frame()
.ok_or("Failed to allocate VMCS frame")?;
Ok(Vmcs { frame })
}
pub fn reset(&mut self) -> Result<(), &'static str> {
let vmcs_addr = self.get_vmcs_addr();
unsafe {
asm!(
"vmclear ({})",
in(reg) &vmcs_addr,
options(att_syntax)
);
vmx_capture_status()?;
asm!(
"vmptrld ({})",
in(reg) &vmcs_addr,
options(att_syntax)
);
vmx_capture_status()
}
}
pub fn write_revision_id(&mut self, revision_id: u32) {
let vmcs_addr = self.get_vmcs_addr();
unsafe {
core::ptr::write_volatile(vmcs_addr as *mut u32, revision_id);
}
}
#[inline]
fn get_vmcs_addr(&self) -> u64 {
self.frame.start_address().as_u64()
}
}

View File

@ -0,0 +1,31 @@
use modular_bitfield::{bitfield, prelude::*};
use crate::vmm::x86_64::intel::vmcs;
#[bitfield]
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub struct PinBasedVmExecutionControls {
pub external_interrupt_exiting: bool,
reserved1: B1,
pub interrupt_window_exiting: bool,
pub nmi_exiting: bool,
reserved2: B1,
pub virtual_nmi: bool,
pub activate_vmx_preemption_timer: bool,
pub process_posted_interrupts: bool,
reserved3: B24,
}
impl PinBasedVmExecutionControls {
pub fn read() -> Result<Self, &'static str> {
vmcs::VmcsControl32::PIN_BASED_VM_EXECUTION_CONTROLS
.read()
.map(|value| PinBasedVmExecutionControls::from(value))
.map_err(|_| "Failed to read Pin-Based VM Execution Controls")
}
pub fn write(&self) -> Result<(), &'static str> {
vmcs::VmcsControl32::PIN_BASED_VM_EXECUTION_CONTROLS.write(u32::from(*self))
}
}

View File

@ -0,0 +1,44 @@
use numeric_enum_macro::numeric_enum;
use crate::vmm::x86_64::intel::vmcs::VmcsReadOnlyData32;
numeric_enum! {
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum InstructionError {
NOT_AVAILABLE = 0,
VMCALL_IN_VMXROOT = 1,
VMCLEAR_INVALID_PHYS = 2,
VMCLEAR_VMXONPTR = 3,
VMLAUNCH_NONCLEAR_VMCS = 4,
VMRESUME_NONLAUNCHED_VMCS = 5,
VMRESUME_AFTER_VMXOFF = 6,
VMENTRY_INVALID_CTRL = 7,
VMENTRY_INVALID_HOST_STATE = 8,
VMPTRLD_INVALID_PHYS = 9,
VMPTRLD_VMXONP = 10,
VMPTRLD_INCORRECT_REV = 11,
VMRW_UNSUPPORTED_COMPONENT = 12,
VMW_RO_COMPONENT = 13,
VMXON_IN_VMXROOT = 15,
VMENTRY_INVALID_EXEC_CTRL = 16,
VMENTRY_NONLAUNCHED_EXEC_CTRL = 17,
VMENTRY_EXEC_VMCSPTR = 18,
VMCALL_NONCLEAR_VMCS = 19,
VMCALL_INVALID_EXITCTL = 20,
VMCALL_INCORRECT_MSGREV = 22,
VMXOFF_DUALMONITOR = 23,
VMCALL_INVALID_SMM = 24,
VMENTRY_INVALID_EXECCTRL = 25,
VMENTRY_EVENTS_BLOCKED = 26,
INVALID_INVEPT = 28,
}
}
impl InstructionError {
pub fn read() -> Result<Self, &'static str> {
let err = VmcsReadOnlyData32::VM_INSTRUCTION_ERROR.read()?;
InstructionError::try_from(err).map_err(|_| "Unknown instruction error")
}
}

View File

@ -0,0 +1,175 @@
use numeric_enum_macro::numeric_enum;
use crate::vmm::x86_64::intel::vmcs;
numeric_enum! {
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VmxExitReason {
EXCEPTION = 0,
EXTERNAL_INTERRUPT = 1,
TRIPLE_FAULT = 2,
INIT = 3,
SIPI = 4,
IO_SMI = 5,
OTHER_SMI = 6,
INTERRUPT_WINDOW = 7,
NMI_WINDOW = 8,
TASK_SWITCH = 9,
CPUID = 10,
GETSEC = 11,
HLT = 12,
INVD = 13,
INVLPG = 14,
RDPMC = 15,
RDTSC = 16,
RSM = 17,
VMCALL = 18,
VMCLEAR = 19,
VMLAUNCH = 20,
VMPTRLD = 21,
VMPTRST = 22,
VMREAD = 23,
VMRESUME = 24,
VMWRITE = 25,
VMXOFF = 26,
VMXON = 27,
CONTROL_REGISTER_ACCESSES = 28,
MOV_DR = 29,
IO_INSTRUCTION = 30,
RDMSR = 31,
WRMSR = 32,
VM_ENTRY_FAILURE_INVALID_GUEST_STATE = 33,
VM_ENTRY_FAILURE_MSR_LOADING = 34,
MWAIT = 36,
MONITOR_TRAP_FLAG = 37,
MONITOR = 39,
PAUSE = 40,
VM_ENTRY_FAILURE_MACHINE_CHECK_EVENT = 41,
TPR_BELOW_THRESHOLD = 43,
APIC_ACCESS = 44,
VIRTUALIZED_EOI = 45,
ACCESS_TO_GDTR_OR_IDTR = 46,
ACCESS_TO_LDTR_OR_TR = 47,
EPT_VIOLATION = 48,
EPT_MISCONFIGURATION = 49,
INVEPT = 50,
RDTSCP = 51,
VMX_PREEMPTION_TIMER_EXPIRED = 52,
INVVPID = 53,
WBINVD = 54,
XSETBV = 55,
APIC_WRITE = 56,
RDRAND = 57,
INVPCID = 58,
VMFUNC = 59,
ENCLS = 60,
RDSEED = 61,
PAGE_MODIFICATION_LOG_FULL = 62,
XSAVES = 63,
XRSTORS = 64,
PCONFIG = 65,
SPP_RELATED_EVENT = 66,
UMWAIT = 67,
TPAUSE = 68,
LOADIWKEY = 69,
ENCLV = 70,
ENQCMD_PASID_TRANSLATION_FAILURE = 72,
ENQCMDS_PASID_TRANSLATION_FAILURE = 73,
BUS_LOCK = 74,
INSTRUCTION_TIMEOUT = 75,
SEAMCALL = 76,
TDCALL = 77,
RDMSRLIST = 78,
WRMSRLIST = 79,
}
}
impl VmxExitReason {
pub fn read() -> Result<Self, &'static str> {
let reason = vmcs::VmcsReadOnlyData32::VM_EXIT_REASON.read()? as u16;
VmxExitReason::try_from(reason).map_err(|_| "Unknown VMX exit reason")
}
pub fn as_str(&self) -> &'static str {
use VmxExitReason::*;
match self {
EXCEPTION => "Exception or non-maskable interrupt (NMI)",
EXTERNAL_INTERRUPT => "External interrupt",
TRIPLE_FAULT => "Triple fault",
INIT => "INIT signal",
SIPI => "Start-up IPI (SIPI)",
IO_SMI => "I/O system-management interrupt (SMI)",
OTHER_SMI => "Other SMI",
INTERRUPT_WINDOW => "Interrupt window",
NMI_WINDOW => "NMI window",
TASK_SWITCH => "Task switch",
CPUID => "CPUID instruction execution",
GETSEC => "GETSEC instruction execution",
HLT => "HLT instruction execution",
INVD => "INVD instruction execution",
INVLPG => "INVLPG instruction execution",
RDPMC => "RDPMC instruction execution",
RDTSC => "RDTSC instruction execution",
RSM => "RSM instruction execution in SMM",
VMCALL => "VMCALL instruction execution",
VMCLEAR => "VMCLEAR instruction execution",
VMLAUNCH => "VMLAUNCH instruction execution",
VMPTRLD => "VMPTRLD instruction execution",
VMPTRST => "VMPTRST instruction execution",
VMREAD => "VMREAD instruction execution",
VMRESUME => "VMRESUME instruction execution",
VMWRITE => "VMWRITE instruction execution",
VMXOFF => "VMXOFF instruction execution",
VMXON => "VMXON instruction execution",
CONTROL_REGISTER_ACCESSES => "Control-register accesses",
MOV_DR => "MOV to or from debug registers",
IO_INSTRUCTION => "I/O instruction execution",
RDMSR => "RDMSR instruction execution",
WRMSR => "WRMSR or WRMSRNS instruction execution",
VM_ENTRY_FAILURE_INVALID_GUEST_STATE => "VM-entry failure due to invalid guest state",
VM_ENTRY_FAILURE_MSR_LOADING => "VM-entry failure due to MSR loading",
MWAIT => "MWAIT instruction execution",
MONITOR_TRAP_FLAG => "Monitor trap flag",
MONITOR => "MONITOR instruction execution",
PAUSE => "PAUSE instruction execution",
VM_ENTRY_FAILURE_MACHINE_CHECK_EVENT => "VM-entry failure due to machine-check event",
TPR_BELOW_THRESHOLD => "TPR below threshold",
APIC_ACCESS => "APIC access",
VIRTUALIZED_EOI => "Virtualized EOI",
ACCESS_TO_GDTR_OR_IDTR => "Access to GDTR or IDTR",
ACCESS_TO_LDTR_OR_TR => "Access to LDTR or TR",
EPT_VIOLATION => "EPT violation",
EPT_MISCONFIGURATION => "EPT misconfiguration",
INVEPT => "INVEPT instruction execution",
RDTSCP => "RDTSCP instruction execution",
VMX_PREEMPTION_TIMER_EXPIRED => "VMX-preemption timer expired",
INVVPID => "INVVPID instruction execution",
WBINVD => "WBINVD or WBNOINVD instruction execution",
XSETBV => "XSETBV instruction execution",
APIC_WRITE => "APIC write",
RDRAND => "RDRAND instruction execution",
INVPCID => "INVPCID instruction execution",
VMFUNC => "VMFUNC instruction execution",
ENCLS => "ENCLS instruction execution",
RDSEED => "RDSEED instruction execution",
PAGE_MODIFICATION_LOG_FULL => "Page-modification log full",
XSAVES => "XSAVES instruction execution",
XRSTORS => "XRSTORS instruction execution",
PCONFIG => "PCONFIG instruction execution",
SPP_RELATED_EVENT => "SPP-related event",
UMWAIT => "UMWAIT instruction execution",
TPAUSE => "TPAUSE instruction execution",
LOADIWKEY => "LOADIWKEY instruction execution",
ENCLV => "ENCLV instruction execution",
ENQCMD_PASID_TRANSLATION_FAILURE => "ENQCMD PASID translation failure",
ENQCMDS_PASID_TRANSLATION_FAILURE => "ENQCMDS PASID translation failure",
BUS_LOCK => "Bus lock",
INSTRUCTION_TIMEOUT => "Instruction timeout",
SEAMCALL => "SEAMCALL instruction execution",
TDCALL => "TDCALL instruction execution",
RDMSRLIST => "RDMSRLIST instruction execution",
WRMSRLIST => "WRMSRLIST instruction execution",
}
}
}

View File

@ -0,0 +1,126 @@
#![allow(non_camel_case_types)]
pub mod controls;
pub mod err;
pub mod exit_reason;
use core::arch::asm;
use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB};
use crate::vmm::x86_64::intel::vmx_capture_status;
macro_rules! vmcs_read {
($field_enum: ident, u64) => {
impl $field_enum {
pub fn read(self) -> Result<u64, &'static str> {
crate::vmm::x86_64::intel::vmread(self as u32)
}
}
};
($field_enum: ident, $ux: ty) => {
impl $field_enum {
pub fn read(self) -> Result<$ux, &'static str> {
crate::vmm::x86_64::intel::vmread(self as u32).map(|v| v as $ux)
}
}
};
}
macro_rules! vmcs_write {
($field_enum: ident, u64) => {
impl $field_enum {
pub fn write(self, value: u64) -> Result<(), &'static str> {
crate::vmm::x86_64::intel::vmwrite(self as u32, value)
}
}
};
($field_enum: ident, $ux: ty) => {
impl $field_enum {
pub fn write(self, value: $ux) -> Result<(), &'static str> {
crate::vmm::x86_64::intel::vmwrite(self as u32, value as u64)
}
}
};
}
pub struct Vmcs {
pub frame: PhysFrame,
}
impl Vmcs {
pub fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Result<Self, &'static str> {
let frame = frame_allocator
.allocate_frame()
.ok_or("Failed to allocate VMCS frame")?;
Ok(Vmcs { frame })
}
pub fn reset(&mut self) -> Result<(), &'static str> {
let vmcs_addr = self.get_vmcs_addr();
unsafe {
asm!(
"vmclear ({})",
in(reg) &vmcs_addr,
options(att_syntax)
);
vmx_capture_status()?;
asm!(
"vmptrld ({})",
in(reg) &vmcs_addr,
options(att_syntax)
);
vmx_capture_status()
}
}
pub fn write_revision_id(&mut self, revision_id: u32) {
let vmcs_addr = self.get_vmcs_addr();
unsafe {
core::ptr::write_volatile(vmcs_addr as *mut u32, revision_id);
}
}
#[inline]
fn get_vmcs_addr(&self) -> u64 {
self.frame.start_address().as_u64()
}
}
pub enum VmcsControl32 {
PIN_BASED_VM_EXECUTION_CONTROLS = 0x00004000,
PRIMARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS = 0x00004002,
EXCEPTION_BITMAP = 0x00004004,
PAGE_FAULT_ERROR_CODE_MASK = 0x00004006,
PAGE_FAULT_ERROR_CODE_MATCH = 0x00004008,
CR3_TARGET_COUNT = 0x0000400A,
PRIMARY_VM_EXIT_CONTROLS = 0x0000400C,
VM_EXIT_MSR_STORE_COUNT = 0x0000400E,
VM_EXIT_MSR_LOAD_COUNT = 0x00004010,
VM_ENTRY_CONTROLS = 0x00004012,
VM_ENTRY_MSR_LOAD_COUNT = 0x00004014,
VM_ENTRY_INTERRUPTION_INFORMATION_FIELD = 0x00004016,
VM_ENTRY_EXCEPTION_ERROR_CODE = 0x00004018,
VM_ENTRY_INSTRUCTION_LENGTH = 0x0000401A,
TPR_THRESHOLD = 0x0000401C,
SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS = 0x0000401E,
PLE_GAP = 0x00004020,
PLE_WINDOW = 0x00004022,
INSTRUCTION_TIMEOUT_CONTROL = 0x00004024,
}
vmcs_read!(VmcsControl32, u32);
vmcs_write!(VmcsControl32, u32);
pub enum VmcsReadOnlyData32 {
VM_INSTRUCTION_ERROR = 0x00004400,
VM_EXIT_REASON = 0x00004402,
VM_EXIT_INTERRUPTION_INFORMATION_FIELD = 0x00004404,
VM_EXIT_INTERRUPTION_ERROR_CODE = 0x00004406,
IDT_VECTORING_INFORMATION_FIELD = 0x00004408,
IDT_VECTORING_ERROR_CODE = 0x0000440A,
VM_EXIT_INSTRUCTION_LENGTH = 0x0000440C,
VM_EXIT_INSTRUCTION_INFO = 0x0000440E,
}
vmcs_read!(VmcsReadOnlyData32, u32);