#![allow(dead_code)]
#![deny(missing_docs)]
#![allow(non_camel_case_types)]
#![allow(clippy::upper_case_acronyms)]
use bit_field::BitField;
use x86::bits64::vmx;
use axaddrspace::{GuestPhysAddr, HostPhysAddr, NestedPageFaultInfo};
use ax_errno::{AxResult, ax_err};
use ax_page_table_entry::MappingFlags;
use super::as_axerr;
use super::definitions::{VmxExitReason, VmxInstructionError, VmxInterruptionType};
use crate::msr::Msr;
macro_rules! vmcs_read {
($field_enum: ident, u64) => {
impl $field_enum {
pub fn read(self) -> AxResult<u64> {
#[cfg(target_pointer_width = "64")]
unsafe {
vmx::vmread(self as u32).map_err(as_axerr)
}
#[cfg(target_pointer_width = "32")]
unsafe {
let field = self as u32;
Ok(vmx::vmread(field).map_err(as_axerr)?
+ (vmx::vmread(field + 1).map_err(as_axerr)? << 32))
}
}
}
};
($field_enum: ident, $ux: ty) => {
impl $field_enum {
pub fn read(self) -> AxResult<$ux> {
unsafe { vmx::vmread(self as u32).map(|v| v as $ux).map_err(as_axerr) }
}
}
};
}
macro_rules! vmcs_write {
($field_enum: ident, u64) => {
impl $field_enum {
pub fn write(self, value: u64) -> AxResult {
#[cfg(target_pointer_width = "64")]
unsafe {
vmx::vmwrite(self as u32, value).map_err(as_axerr)
}
#[cfg(target_pointer_width = "32")]
unsafe {
let field = self as u32;
vmx::vmwrite(field, value & 0xffff_ffff).map_err(as_axerr)?;
vmx::vmwrite(field + 1, value >> 32).map_err(as_axerr)?;
Ok(())
}
}
}
};
($field_enum: ident, $ux: ty) => {
impl $field_enum {
pub fn write(self, value: $ux) -> AxResult {
unsafe { vmx::vmwrite(self as u32, value as u64).map_err(as_axerr) }
}
}
};
}
macro_rules! define_vmcs_fields_ro {
($field_enum:ident, $ty:ty) => {
vmcs_read!($field_enum, $ty);
};
}
macro_rules! define_vmcs_fields_rw {
($field_enum:ident, $ty:ty) => {
vmcs_read!($field_enum, $ty);
vmcs_write!($field_enum, $ty);
};
}
#[derive(Clone, Copy, Debug)]
pub enum VmcsControl16 {
VPID = 0x0,
POSTED_INTERRUPT_NOTIFICATION_VECTOR = 0x2,
EPTP_INDEX = 0x4,
}
define_vmcs_fields_rw!(VmcsControl16, u16);
#[derive(Clone, Copy, Debug)]
pub enum VmcsControl64 {
IO_BITMAP_A_ADDR = 0x2000,
IO_BITMAP_B_ADDR = 0x2002,
MSR_BITMAPS_ADDR = 0x2004,
VMEXIT_MSR_STORE_ADDR = 0x2006,
VMEXIT_MSR_LOAD_ADDR = 0x2008,
VMENTRY_MSR_LOAD_ADDR = 0x200A,
EXECUTIVE_VMCS_PTR = 0x200C,
PML_ADDR = 0x200E,
TSC_OFFSET = 0x2010,
VIRT_APIC_ADDR = 0x2012,
APIC_ACCESS_ADDR = 0x2014,
POSTED_INTERRUPT_DESC_ADDR = 0x2016,
VM_FUNCTION_CONTROLS = 0x2018,
EPTP = 0x201A,
EOI_EXIT0 = 0x201C,
EOI_EXIT1 = 0x201E,
EOI_EXIT2 = 0x2020,
EOI_EXIT3 = 0x2022,
EPTP_LIST_ADDR = 0x2024,
VMREAD_BITMAP_ADDR = 0x2026,
VMWRITE_BITMAP_ADDR = 0x2028,
VIRT_EXCEPTION_INFO_ADDR = 0x202A,
XSS_EXITING_BITMAP = 0x202C,
ENCLS_EXITING_BITMAP = 0x202E,
SUBPAGE_PERM_TABLE_PTR = 0x2030,
TSC_MULTIPLIER = 0x2032,
}
define_vmcs_fields_rw!(VmcsControl64, u64);
#[derive(Clone, Copy, Debug)]
pub enum VmcsControl32 {
PINBASED_EXEC_CONTROLS = 0x4000,
PRIMARY_PROCBASED_EXEC_CONTROLS = 0x4002,
EXCEPTION_BITMAP = 0x4004,
PAGE_FAULT_ERR_CODE_MASK = 0x4006,
PAGE_FAULT_ERR_CODE_MATCH = 0x4008,
CR3_TARGET_COUNT = 0x400A,
VMEXIT_CONTROLS = 0x400C,
VMEXIT_MSR_STORE_COUNT = 0x400E,
VMEXIT_MSR_LOAD_COUNT = 0x4010,
VMENTRY_CONTROLS = 0x4012,
VMENTRY_MSR_LOAD_COUNT = 0x4014,
VMENTRY_INTERRUPTION_INFO_FIELD = 0x4016,
VMENTRY_EXCEPTION_ERR_CODE = 0x4018,
VMENTRY_INSTRUCTION_LEN = 0x401A,
TPR_THRESHOLD = 0x401C,
SECONDARY_PROCBASED_EXEC_CONTROLS = 0x401E,
PLE_GAP = 0x4020,
PLE_WINDOW = 0x4022,
}
define_vmcs_fields_rw!(VmcsControl32, u32);
#[derive(Clone, Copy, Debug)]
pub enum VmcsControlNW {
CR0_GUEST_HOST_MASK = 0x6000,
CR4_GUEST_HOST_MASK = 0x6002,
CR0_READ_SHADOW = 0x6004,
CR4_READ_SHADOW = 0x6006,
CR3_TARGET_VALUE0 = 0x6008,
CR3_TARGET_VALUE1 = 0x600A,
CR3_TARGET_VALUE2 = 0x600C,
CR3_TARGET_VALUE3 = 0x600E,
}
define_vmcs_fields_rw!(VmcsControlNW, usize);
pub enum VmcsGuest16 {
ES_SELECTOR = 0x800,
CS_SELECTOR = 0x802,
SS_SELECTOR = 0x804,
DS_SELECTOR = 0x806,
FS_SELECTOR = 0x808,
GS_SELECTOR = 0x80a,
LDTR_SELECTOR = 0x80c,
TR_SELECTOR = 0x80e,
INTERRUPT_STATUS = 0x810,
PML_INDEX = 0x812,
}
define_vmcs_fields_rw!(VmcsGuest16, u16);
#[derive(Clone, Copy, Debug)]
pub enum VmcsGuest64 {
LINK_PTR = 0x2800,
IA32_DEBUGCTL = 0x2802,
IA32_PAT = 0x2804,
IA32_EFER = 0x2806,
IA32_PERF_GLOBAL_CTRL = 0x2808,
PDPTE0 = 0x280A,
PDPTE1 = 0x280C,
PDPTE2 = 0x280E,
PDPTE3 = 0x2810,
IA32_BNDCFGS = 0x2812,
IA32_RTIT_CTL = 0x2814,
}
define_vmcs_fields_rw!(VmcsGuest64, u64);
#[derive(Clone, Copy, Debug)]
pub enum VmcsGuest32 {
ES_LIMIT = 0x4800,
CS_LIMIT = 0x4802,
SS_LIMIT = 0x4804,
DS_LIMIT = 0x4806,
FS_LIMIT = 0x4808,
GS_LIMIT = 0x480A,
LDTR_LIMIT = 0x480C,
TR_LIMIT = 0x480E,
GDTR_LIMIT = 0x4810,
IDTR_LIMIT = 0x4812,
ES_ACCESS_RIGHTS = 0x4814,
CS_ACCESS_RIGHTS = 0x4816,
SS_ACCESS_RIGHTS = 0x4818,
DS_ACCESS_RIGHTS = 0x481A,
FS_ACCESS_RIGHTS = 0x481C,
GS_ACCESS_RIGHTS = 0x481E,
LDTR_ACCESS_RIGHTS = 0x4820,
TR_ACCESS_RIGHTS = 0x4822,
INTERRUPTIBILITY_STATE = 0x4824,
ACTIVITY_STATE = 0x4826,
SMBASE = 0x4828,
IA32_SYSENTER_CS = 0x482A,
VMX_PREEMPTION_TIMER_VALUE = 0x482E,
}
define_vmcs_fields_rw!(VmcsGuest32, u32);
#[derive(Clone, Copy, Debug)]
pub enum VmcsGuestNW {
CR0 = 0x6800,
CR3 = 0x6802,
CR4 = 0x6804,
ES_BASE = 0x6806,
CS_BASE = 0x6808,
SS_BASE = 0x680A,
DS_BASE = 0x680C,
FS_BASE = 0x680E,
GS_BASE = 0x6810,
LDTR_BASE = 0x6812,
TR_BASE = 0x6814,
GDTR_BASE = 0x6816,
IDTR_BASE = 0x6818,
DR7 = 0x681A,
RSP = 0x681C,
RIP = 0x681E,
RFLAGS = 0x6820,
PENDING_DBG_EXCEPTIONS = 0x6822,
IA32_SYSENTER_ESP = 0x6824,
IA32_SYSENTER_EIP = 0x6826,
}
define_vmcs_fields_rw!(VmcsGuestNW, usize);
#[derive(Clone, Copy, Debug)]
pub enum VmcsHost16 {
ES_SELECTOR = 0xC00,
CS_SELECTOR = 0xC02,
SS_SELECTOR = 0xC04,
DS_SELECTOR = 0xC06,
FS_SELECTOR = 0xC08,
GS_SELECTOR = 0xC0A,
TR_SELECTOR = 0xC0C,
}
define_vmcs_fields_rw!(VmcsHost16, u16);
#[derive(Clone, Copy, Debug)]
pub enum VmcsHost64 {
IA32_PAT = 0x2C00,
IA32_EFER = 0x2C02,
IA32_PERF_GLOBAL_CTRL = 0x2C04,
}
define_vmcs_fields_rw!(VmcsHost64, u64);
#[derive(Clone, Copy, Debug)]
pub enum VmcsHost32 {
IA32_SYSENTER_CS = 0x4C00,
}
define_vmcs_fields_rw!(VmcsHost32, u32);
#[derive(Clone, Copy, Debug)]
pub enum VmcsHostNW {
CR0 = 0x6C00,
CR3 = 0x6C02,
CR4 = 0x6C04,
FS_BASE = 0x6C06,
GS_BASE = 0x6C08,
TR_BASE = 0x6C0A,
GDTR_BASE = 0x6C0C,
IDTR_BASE = 0x6C0E,
IA32_SYSENTER_ESP = 0x6C10,
IA32_SYSENTER_EIP = 0x6C12,
RSP = 0x6C14,
RIP = 0x6C16,
}
define_vmcs_fields_rw!(VmcsHostNW, usize);
#[derive(Clone, Copy, Debug)]
pub enum VmcsReadOnly64 {
GUEST_PHYSICAL_ADDR = 0x2400,
}
define_vmcs_fields_ro!(VmcsReadOnly64, u64);
#[derive(Clone, Copy, Debug)]
pub enum VmcsReadOnly32 {
VM_INSTRUCTION_ERROR = 0x4400,
EXIT_REASON = 0x4402,
VMEXIT_INTERRUPTION_INFO = 0x4404,
VMEXIT_INTERRUPTION_ERR_CODE = 0x4406,
IDT_VECTORING_INFO = 0x4408,
IDT_VECTORING_ERR_CODE = 0x440A,
VMEXIT_INSTRUCTION_LEN = 0x440C,
VMEXIT_INSTRUCTION_INFO = 0x440E,
}
define_vmcs_fields_ro!(VmcsReadOnly32, u32);
#[derive(Clone, Copy, Debug)]
pub enum VmcsReadOnlyNW {
EXIT_QUALIFICATION = 0x6400,
IO_RCX = 0x6402,
IO_RSI = 0x6404,
IO_RDI = 0x6406,
IO_RIP = 0x6408,
GUEST_LINEAR_ADDR = 0x640A,
}
define_vmcs_fields_ro!(VmcsReadOnlyNW, usize);
#[derive(Debug)]
pub struct VmxExitInfo {
pub entry_failure: bool,
pub exit_reason: VmxExitReason,
pub exit_instruction_length: u32,
pub guest_rip: usize,
}
#[derive(Debug)]
pub struct VmxInterruptInfo {
pub vector: u8,
pub int_type: VmxInterruptionType,
pub err_code: Option<u32>,
pub valid: bool,
}
impl VmxInterruptInfo {
pub fn from(vector: u8, err_code: Option<u32>) -> Self {
Self {
vector,
int_type: VmxInterruptionType::from_vector(vector),
err_code,
valid: true,
}
}
pub fn bits(&self) -> u32 {
let mut bits = self.vector as u32;
bits |= (self.int_type as u32) << 8;
bits.set_bit(11, self.err_code.is_some());
bits.set_bit(31, self.valid);
bits
}
}
#[derive(Debug)]
pub struct VmxIoExitInfo {
pub access_size: u8,
pub is_in: bool,
pub is_string: bool,
pub is_repeat: bool,
pub port: u16,
}
#[derive(Debug)]
pub struct CrAccessInfo {
pub cr_number: u8,
pub access_type: u8,
pub lmsw_op_type: u8,
pub gpr: u8,
pub lmsw_source_data: u8,
}
#[derive(Debug)]
pub enum ApicAccessExitType {
LinearDataRead = 0,
LinearDataWrite = 1,
LinearInstructionFetch = 2,
LinearEventDelivery = 3,
LinearMonitoring = 4,
GuestPhysicalEventDelivery = 10,
GuestPhysicalMonitoring = 11,
GuestPhysicalInstructionFetchReadWrite = 15,
}
impl TryFrom<u8> for ApicAccessExitType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::LinearDataRead),
1 => Ok(Self::LinearDataWrite),
2 => Ok(Self::LinearInstructionFetch),
3 => Ok(Self::LinearEventDelivery),
4 => Ok(Self::LinearMonitoring),
10 => Ok(Self::GuestPhysicalEventDelivery),
11 => Ok(Self::GuestPhysicalMonitoring),
15 => Ok(Self::GuestPhysicalInstructionFetchReadWrite),
_ => Err(()),
}
}
}
#[derive(Debug)]
pub struct ApicAccessExitInfo {
pub offset: u16,
pub access_type: ApicAccessExitType,
pub non_event_delivery_asynchronous: bool,
}
pub mod controls {
pub use x86::vmx::vmcs::control::{EntryControls, ExitControls};
pub use x86::vmx::vmcs::control::{PinbasedControls, PrimaryControls, SecondaryControls};
}
pub fn set_control(
control: VmcsControl32,
capability_msr: Msr,
old_value: u32,
set: u32,
clear: u32,
) -> AxResult {
let cap = capability_msr.read();
let allowed0 = cap as u32;
let allowed1 = (cap >> 32) as u32;
assert_eq!(allowed0 & allowed1, allowed0);
debug!("set {control:?}: {old_value:#x} (+{set:#x}, -{clear:#x})");
if (set & clear) != 0 {
return ax_err!(
InvalidInput,
format_args!("can not set and clear the same bit in {:?}", control)
);
}
if (allowed1 & set) != set {
return ax_err!(
Unsupported,
format_args!("can not set bits {:#x} in {:?}", set, control)
);
}
if (allowed0 & clear) != 0 {
return ax_err!(
Unsupported,
format_args!("can not clear bits {:#x} in {:?}", clear, control)
);
}
let flexible = !allowed0 & allowed1; let unknown = flexible & !(set | clear); let default = unknown & old_value; let fixed1 = allowed0; control.write(fixed1 | default | set)?;
Ok(())
}
pub fn set_ept_pointer(pml4_paddr: HostPhysAddr) -> AxResult {
use super::instructions::{InvEptType, invept};
let eptp = super::structs::EPTPointer::from_table_phys(pml4_paddr).bits();
VmcsControl64::EPTP.write(eptp)?;
unsafe { invept(InvEptType::SingleContext, eptp).map_err(as_axerr)? };
Ok(())
}
pub fn instruction_error() -> VmxInstructionError {
VmcsReadOnly32::VM_INSTRUCTION_ERROR.read().unwrap().into()
}
pub fn exit_info() -> AxResult<VmxExitInfo> {
let full_reason = VmcsReadOnly32::EXIT_REASON.read()?;
Ok(VmxExitInfo {
exit_reason: full_reason
.get_bits(0..16)
.try_into()
.expect("Unknown VM-exit reason"),
entry_failure: full_reason.get_bit(31),
exit_instruction_length: VmcsReadOnly32::VMEXIT_INSTRUCTION_LEN.read()?,
guest_rip: VmcsGuestNW::RIP.read()?,
})
}
pub fn raw_interrupt_exit_info() -> AxResult<u32> {
VmcsReadOnly32::VMEXIT_INTERRUPTION_INFO.read()
}
pub fn interrupt_exit_info() -> AxResult<VmxInterruptInfo> {
let info = VmcsReadOnly32::VMEXIT_INTERRUPTION_INFO.read()?;
Ok(VmxInterruptInfo {
vector: info.get_bits(0..8) as u8,
int_type: VmxInterruptionType::try_from(info.get_bits(8..11) as u8).unwrap(),
err_code: if info.get_bit(11) {
Some(VmcsReadOnly32::VMEXIT_INTERRUPTION_ERR_CODE.read()?)
} else {
None
},
valid: info.get_bit(31),
})
}
pub fn inject_event(vector: u8, err_code: Option<u32>) -> AxResult {
let err_code = if VmxInterruptionType::vector_has_error_code(vector) {
err_code.or_else(|| Some(VmcsReadOnly32::VMEXIT_INTERRUPTION_ERR_CODE.read().unwrap()))
} else {
None
};
let int_info = VmxInterruptInfo::from(vector, err_code);
if let Some(err_code) = int_info.err_code {
VmcsControl32::VMENTRY_EXCEPTION_ERR_CODE.write(err_code)?;
}
if int_info.int_type.is_soft() {
VmcsControl32::VMENTRY_INSTRUCTION_LEN
.write(VmcsReadOnly32::VMEXIT_INSTRUCTION_LEN.read()?)?;
}
VmcsControl32::VMENTRY_INTERRUPTION_INFO_FIELD.write(int_info.bits())?;
Ok(())
}
pub fn io_exit_info() -> AxResult<VmxIoExitInfo> {
let qualification = VmcsReadOnlyNW::EXIT_QUALIFICATION.read()?;
Ok(VmxIoExitInfo {
access_size: qualification.get_bits(0..3) as u8 + 1,
is_in: qualification.get_bit(3),
is_string: qualification.get_bit(4),
is_repeat: qualification.get_bit(5),
port: qualification.get_bits(16..32) as u16,
})
}
pub fn ept_violation_info() -> AxResult<NestedPageFaultInfo> {
let qualification = VmcsReadOnlyNW::EXIT_QUALIFICATION.read()?;
let fault_guest_paddr = VmcsReadOnly64::GUEST_PHYSICAL_ADDR.read()? as usize;
let mut access_flags = MappingFlags::empty();
if qualification.get_bit(0) {
access_flags |= MappingFlags::READ;
}
if qualification.get_bit(1) {
access_flags |= MappingFlags::WRITE;
}
if qualification.get_bit(2) {
access_flags |= MappingFlags::EXECUTE;
}
Ok(NestedPageFaultInfo {
access_flags,
fault_guest_paddr: GuestPhysAddr::from(fault_guest_paddr),
})
}
pub fn update_efer() -> AxResult {
use x86_64::registers::control::EferFlags;
let efer = VmcsGuest64::IA32_EFER.read()?;
let mut guest_efer = EferFlags::from_bits_truncate(efer);
if guest_efer.contains(EferFlags::LONG_MODE_ENABLE)
&& guest_efer.contains(EferFlags::LONG_MODE_ACTIVE)
{
return Ok(());
}
guest_efer.set(EferFlags::LONG_MODE_ACTIVE, true);
VmcsGuest64::IA32_EFER.write(guest_efer.bits())?;
use controls::EntryControls as EntryCtrl;
set_control(
VmcsControl32::VMENTRY_CONTROLS,
Msr::IA32_VMX_TRUE_ENTRY_CTLS,
VmcsControl32::VMENTRY_CONTROLS.read()?,
(EntryCtrl::IA32E_MODE_GUEST).bits(),
0,
)?;
Ok(())
}
pub fn cr_access_info() -> AxResult<CrAccessInfo> {
let qualification = VmcsReadOnlyNW::EXIT_QUALIFICATION.read()?;
Ok(CrAccessInfo {
cr_number: qualification.get_bits(0..4) as u8,
access_type: qualification.get_bits(4..6) as u8,
lmsw_op_type: qualification.get_bits(6..7) as u8,
gpr: qualification.get_bits(8..12) as u8,
lmsw_source_data: qualification.get_bits(16..32) as u8,
})
}
pub fn apic_access_exit_info() -> AxResult<ApicAccessExitInfo> {
let qualification = VmcsReadOnlyNW::EXIT_QUALIFICATION.read()?;
Ok(ApicAccessExitInfo {
offset: qualification.get_bits(0..12) as u16,
access_type: ApicAccessExitType::try_from(qualification.get_bits(12..16) as u8).unwrap(),
non_event_delivery_asynchronous: qualification.get_bit(16),
})
}