use core::ffi::c_void;
use std::arch::x86_64::__cpuid_count;
use std::sync::LazyLock;
use base::error;
use base::warn;
use base::Error;
use base::Result;
use thiserror::Error as ThisError;
use winapi::shared::winerror::S_OK;
use crate::CpuId;
use crate::CpuIdEntry;
use crate::Hypervisor;
use crate::HypervisorCap;
use crate::HypervisorX86_64;
#[macro_export]
macro_rules! check_whpx {
($x: expr) => {{
match $x {
S_OK => Ok(()),
_ => Err(Error::new($x)),
}
}};
}
mod types;
mod vcpu;
pub use vcpu::*;
mod vm;
pub use vm::*;
pub mod whpx_sys;
pub use whpx_sys::*;
struct SafePartition {
partition: WHV_PARTITION_HANDLE,
}
unsafe impl Send for SafePartition {}
unsafe impl Sync for SafePartition {}
impl SafePartition {
fn new() -> WhpxResult<SafePartition> {
let mut partition_handle: WHV_PARTITION_HANDLE = std::ptr::null_mut();
check_whpx!(unsafe { WHvCreatePartition(&mut partition_handle) })
.map_err(WhpxError::CreatePartitionError)?;
Ok(SafePartition {
partition: partition_handle,
})
}
}
impl Drop for SafePartition {
fn drop(&mut self) {
check_whpx!(unsafe { WHvDeletePartition(self.partition) }).unwrap();
}
}
#[derive(Clone)]
pub struct Whpx {
}
#[derive(Debug)]
pub enum WhpxFeature {
PartialUnmap = 0x0,
LocalApicEmulation = 0x1,
Xsave = 0x2,
DirtyPageTracking = 0x4,
SpeculationControl = 0x8,
ApicRemoteRead = 0x10,
IdleSuspend = 0x20,
}
#[derive(ThisError, Debug, Clone, Copy)]
pub enum WhpxError {
#[error("failed to create WHPX partition: {0}")]
CreatePartitionError(base::Error),
#[error("failed to get WHPX capability {0}: {1}")]
GetCapability(WHV_CAPABILITY_CODE, base::Error),
#[error("WHPX local apic emulation is not supported on this host")]
LocalApicEmulationNotSupported,
#[error("failed to map guest physical address range: {0}")]
MapGpaRange(base::Error),
#[error("failed to set WHPX partition processor count: {0}")]
SetProcessorCount(base::Error),
#[error("failed to set WHPX partition cpuid result list: {0}")]
SetCpuidResultList(base::Error),
#[error("failed to set WHPX partition cpuid exit list: {0}")]
SetCpuidExitList(base::Error),
#[error("failed to set WHPX partition extended vm exits: {0}")]
SetExtendedVmExits(base::Error),
#[error("failed to set WHPX partition local apic emulation mode: {0}")]
SetLocalApicEmulationMode(base::Error),
#[error("failed to setup WHPX partition: {0}")]
SetupPartition(base::Error),
}
impl From<WhpxError> for Box<dyn std::error::Error + Send> {
fn from(e: WhpxError) -> Self {
Box::new(e)
}
}
type WhpxResult<T> = std::result::Result<T, WhpxError>;
impl Whpx {
pub fn new() -> Result<Whpx> {
Ok(Whpx {})
}
pub fn is_enabled() -> bool {
let res = Whpx::get_capability(WHV_CAPABILITY_CODE_WHvCapabilityCodeHypervisorPresent);
match res {
Ok(cap_code) => {
unsafe { cap_code.HypervisorPresent > 0 }
}
_ => {
warn!("error checking if whpx was enabled. Assuming whpx is disabled");
false
}
}
}
fn get_capability(cap: WHV_CAPABILITY_CODE) -> WhpxResult<WHV_CAPABILITY> {
let mut capability: WHV_CAPABILITY = Default::default();
let mut written_size = 0;
check_whpx!(unsafe {
WHvGetCapability(
cap,
&mut capability as *mut _ as *mut c_void,
std::mem::size_of::<WHV_CAPABILITY>() as u32,
&mut written_size,
)
})
.map_err(|e| WhpxError::GetCapability(cap, e))?;
Ok(capability)
}
pub fn check_whpx_feature(feature: WhpxFeature) -> WhpxResult<bool> {
static FEATURES: LazyLock<WhpxResult<WHV_CAPABILITY>> =
LazyLock::new(|| Whpx::get_capability(WHV_CAPABILITY_CODE_WHvCapabilityCodeFeatures));
Ok((unsafe { (*FEATURES)?.Features.AsUINT64 } & feature as u64) != 0)
}
}
impl Hypervisor for Whpx {
fn try_clone(&self) -> Result<Self> {
Ok(self.clone())
}
fn check_capability(&self, cap: HypervisorCap) -> bool {
match cap {
HypervisorCap::ImmediateExit => true,
HypervisorCap::UserMemory => true,
HypervisorCap::Xcrs => {
Whpx::check_whpx_feature(WhpxFeature::Xsave).unwrap_or_else(|e| {
error!(
"failed to check whpx feature {:?}: {}",
WhpxFeature::Xsave,
e
);
false
})
}
HypervisorCap::CalibratedTscLeafRequired => true,
_ => false,
}
}
}
fn cpuid_entry_from_host(function: u32, index: u32) -> CpuIdEntry {
let result = unsafe { __cpuid_count(function, index) };
CpuIdEntry {
function,
index,
flags: 0,
cpuid: result,
}
}
impl HypervisorX86_64 for Whpx {
fn get_supported_cpuid(&self) -> Result<CpuId> {
Ok(CpuId {
cpu_id_entries: vec![
cpuid_entry_from_host(0, 0),
cpuid_entry_from_host(2, 0),
cpuid_entry_from_host(0x80000005, 0),
cpuid_entry_from_host(0x80000006, 0),
],
})
}
fn get_msr_index_list(&self) -> Result<Vec<u32>> {
Ok(vec![])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_whpx() {
Whpx::new().expect("failed to instantiate whpx");
}
#[test]
fn clone_whpx() {
let whpx = Whpx::new().expect("failed to instantiate whpx");
let _whpx_clone = whpx.try_clone().unwrap();
}
#[test]
fn check_capability() {
let whpx = Whpx::new().expect("failed to instantiate whpx");
assert!(whpx.check_capability(HypervisorCap::UserMemory));
assert!(whpx.check_capability(HypervisorCap::Xcrs));
assert!(whpx.check_capability(HypervisorCap::ImmediateExit));
}
}