use core::{ffi::c_void, mem::MaybeUninit, ptr::null_mut};
use bitfield_struct::bitfield;
use paste::paste;
use crate::ffi::{*, winhvplatform::*, winhvplatformdefs::*};
#[bitfield(u64)] struct PresentFields
{
x64_msr_exit_bitmap:bool,
gpa_range_population_flags:bool,
scheduler_features:bool,
processor_xsave_features:bool,
processor_clock_frequency:bool,
interrupt_clock_frequency:bool,
processor_feature_banks:bool,
processor_frequency_cap:bool,
processor_perfmon_features:bool,
physical_address_width:bool,
#[bits(54)] rsvd:u64
}
#[derive(Debug, Default)]
pub struct HypervisorCapabilities
{
present_fields:PresentFields,
pub features:WHvCapabilityFeatures,
pub extended_vm_exits:WHvExtendedVmExits,
#[cfg(target_arch="x86_64")]
pub exception_bitmap:WHvExceptionBitmap,
#[cfg(target_arch="x86_64")]
x64_msr_exit_bitmap:WHvX64MsrExitBitmap,
gpa_range_population_flags:WHvAdviseGpaRangePopulateFlag,
scheduler_features:WHvSchedulerFeatures,
pub vendor_id:WHvProcessorVendor,
#[cfg(target_arch="x86_64")]
pub processor_features:WHvX64ProcessorFeatures,
pub clflush_size:u8,
#[cfg(target_arch="x86_64")]
processor_xsave_features:WHvProcessorXsaveFeatures,
processor_feature_banks:WHvProcessorFeatureBanks,
processor_clock_frequency:u64,
#[cfg(target_arch="x86_64")]
interrupt_clock_frequency:u64,
processor_frequency_cap:WHvCapabilityProcessorFrequencyCap,
#[cfg(target_arch="x86_64")]
processor_perfmon_features:WHvProcessorPerfmonFeatures,
physical_address_width:u32
}
macro_rules! define_cap_fn
{
($name:tt,$type:ty)=>
{
paste!
{
pub fn [<get_ $name:lower>](&self)->Option<$type>
{
if self.present_fields.$name()
{
Some(self.$name)
}
else
{
None
}
}
}
};
}
impl HypervisorCapabilities
{
pub fn get()->Option<Self>
{
let mut cap:MaybeUninit<WHvCapability>=MaybeUninit::uninit();
const WHV_CAP_SIZE:u32=size_of::<WHvCapability>() as u32;
macro_rules! get_cap
{
($name:tt,$field:tt)=>
{
match get_capability(WHvCapabilityCode::$name,cap.as_mut_ptr(),WHV_CAP_SIZE,null_mut())
{
Ok(_)=>Some(cap.assume_init_ref().$field),
Err(_)=>None
}
};
}
match unsafe{get_cap!(HYPERVISOR_PRESENT,hypervisor_present)}
{
Some(true)=>
{
let mut ret:MaybeUninit<HypervisorCapabilities>=MaybeUninit::uninit();
unsafe
{
let r=ret.assume_init_mut();
r.present_fields=PresentFields::new();
r.features=get_cap!(FEATURES,features).unwrap();
r.extended_vm_exits=get_cap!(EXTENDED_VM_EXITS,extended_vm_exits).unwrap();
macro_rules! set_cap
{
($name:tt)=>
{
paste!
{
if let Some(f)=get_cap!([<$name:upper>],$name)
{
r.$name=f;
r.present_fields.[<set_ $name>](true);
}
}
};
}
#[cfg(target_arch="x86_64")]
{
r.exception_bitmap=get_cap!(EXCEPTION_EXIT_BITMAP,exception_exit_bitmap).unwrap();
set_cap!(x64_msr_exit_bitmap);
set_cap!(processor_xsave_features);
set_cap!(interrupt_clock_frequency);
set_cap!(processor_perfmon_features);
}
set_cap!(gpa_range_population_flags);
set_cap!(scheduler_features);
set_cap!(processor_clock_frequency);
set_cap!(processor_feature_banks);
set_cap!(processor_frequency_cap);
set_cap!(physical_address_width);
}
Some(unsafe{ret.assume_init()})
}
Some(false)|None=>None
}
}
define_cap_fn!(gpa_range_population_flags,WHvAdviseGpaRangePopulateFlag);
define_cap_fn!(scheduler_features,WHvSchedulerFeatures);
define_cap_fn!(processor_clock_frequency,u64);
define_cap_fn!(processor_feature_banks,WHvProcessorFeatureBanks);
define_cap_fn!(processor_frequency_cap,WHvCapabilityProcessorFrequencyCap);
define_cap_fn!(physical_address_width,u32);
#[cfg(target_arch="x86_64")]
define_cap_fn!(processor_perfmon_features,WHvProcessorPerfmonFeatures);
#[cfg(target_arch="x86_64")]
define_cap_fn!(x64_msr_exit_bitmap,WHvX64MsrExitBitmap);
#[cfg(target_arch="x86_64")]
define_cap_fn!(processor_xsave_features,WHvProcessorXsaveFeatures);
#[cfg(target_arch="x86_64")]
define_cap_fn!(interrupt_clock_frequency,u64);
}
pub struct Partition(pub(crate) WHvPartitionHandle);
impl Partition
{
const PARTITION_SIZE:u32=size_of::<WHVPartitionProperty>() as u32;
pub fn create()->Result<Self,HResult>
{
let mut handle:MaybeUninit<WHvPartitionHandle>=MaybeUninit::uninit();
unsafe
{
match create_partition(handle.as_mut_ptr())
{
Ok(_)=>Ok(Self(handle.assume_init())),
Err(e)=>Err(e)
}
}
}
pub fn set_vcpu_count(&mut self,vcpu_count:u32)->Result<(),HResult>
{
unsafe
{
let mut prop:MaybeUninit<WHVPartitionProperty>=MaybeUninit::uninit();
prop.assume_init_mut().processor_count=vcpu_count;
set_partition_property(self.0,WHvPartitionPropertyCode::PROCESSOR_COUNT,prop.as_ptr(),Self::PARTITION_SIZE)
}
}
pub fn setup(&mut self)->Result<(),HResult>
{
unsafe
{
setup_partition(self.0)
}
}
pub unsafe fn map_gpa_range(&mut self,gpa_start:u64,source:*mut c_void,size:usize,flags:WHvMapGpaRangeFlags)->Result<(),HResult>
{
unsafe
{
map_gpa_range(self.0,source,gpa_start,size as u64,flags)
}
}
pub fn unmap_gpa_range(&mut self,gpa_start:u64,size:usize)->Result<(),HResult>
{
unsafe
{
unmap_gpa_range(self.0,gpa_start,size as u64)
}
}
pub fn create_vcpu(&mut self,vp_index:u32)->Result<(),HResult>
{
unsafe
{
create_virtual_processor(self.0,vp_index,0)
}
}
pub fn get_vcpu_registers(&self,vp_index:u32,reg_names:&[WHvRegisterName],reg_values:&mut [WHvRegisterValue])->Result<(),HResult>
{
assert_eq!(reg_names.len(),reg_values.len(),"There must be same number of register names and values!");
unsafe
{
get_virtual_processor_registers(self.0,vp_index,reg_names.as_ptr(),reg_names.len() as u32,reg_values.as_mut_ptr())
}
}
pub fn set_vcpu_registers(&self,vp_index:u32,reg_names:&[WHvRegisterName],reg_values:&[WHvRegisterValue])->Result<(),HResult>
{
assert_eq!(reg_names.len(),reg_values.len(),"There must be same number of register names and values!");
unsafe
{
set_virtual_processor_registers(self.0,vp_index,reg_names.as_ptr(),reg_names.len() as u32,reg_values.as_ptr())
}
}
pub fn translate_gva(&self,vp_index:u32,gva:u64,flags:WHvTranslateGvaFlags)->Result<u64,Result<WHvTranslateGvaResult,HResult>>
{
unsafe
{
let mut gpa:MaybeUninit<u64>=MaybeUninit::uninit();
let mut result:MaybeUninit<WHvTranslateGvaResult>=MaybeUninit::uninit();
match translate_gva(self.0,vp_index,gva,flags,result.as_mut_ptr(),gpa.as_mut_ptr())
{
Ok(_)=>if result.assume_init_ref().result_code==WHvTranslateGvaResultCode::SUCCESS
{
Ok(gpa.assume_init())
}
else
{
Err(Ok(result.assume_init()))
}
Err(e)=>Err(Err(e))
}
}
}
pub fn run_vcpu(&self,vp_index:u32,exit_context:&mut WHvRunVpExitContext)->Result<(),HResult>
{
unsafe
{
run_virtual_processor(self.0,vp_index,exit_context,size_of::<WHvRunVpExitContext>() as u32)
}
}
}
impl Drop for Partition
{
fn drop(&mut self)
{
unsafe
{
let _=delete_partition(self.0);
}
}
}
pub struct MemoryRegion(pub *mut c_void);
impl MemoryRegion
{
pub fn new(region_size:usize)->Result<Self,Win32Error>
{
let p=unsafe{VirtualAlloc(null_mut(),region_size,MEM_COMMIT,PAGE_READWRITE)};
if p.is_null()
{
Err(unsafe{GetLastError()})
}
else
{
Ok(Self(p))
}
}
}
impl Drop for MemoryRegion
{
fn drop(&mut self)
{
unsafe
{
VirtualFree(self.0,0,MEM_RELEASE);
}
}
}
#[cfg(test)] mod tests
{
use crate::{ffi::{HResult, winhvplatformdefs::*}, hvemulator::Emulator, hvplatform::{MemoryRegion, Partition}, init_bindings};
#[test] fn halt_exit()
{
init_bindings();
let mut vm=match Partition::create()
{
Ok(x)=>x,
Err(e)=>panic!("WHvCreatePartition failed! Reason: {e}")
};
if let Err(e)=vm.set_vcpu_count(1)
{
panic!("WHvSetPartitionProperty ProcessorCount failed! Reason: {e}");
}
if let Err(e)=vm.setup()
{
panic!("WHvSetupPartition failed! Reason: {e}");
}
const VMEM_SIZE:usize=0x1000;
let region=match MemoryRegion::new(VMEM_SIZE)
{
Ok(r)=>
{
let s=unsafe{core::slice::from_raw_parts_mut(r.0.cast::<u8>(),VMEM_SIZE)};
s.fill(0xf4);
r
}
Err(e)=>panic!("VirtualAlloc failed! Reason: {e}")
};
if let Err(e)=unsafe{vm.map_gpa_range(0xFF000,region.0,VMEM_SIZE,WHvMapGpaRangeFlags::READ|WHvMapGpaRangeFlags::WRITE|WHvMapGpaRangeFlags::EXECUTE)}
{
panic!("WHvMapGpaRange failed! Reason: {e}");
}
if let Err(e)=vm.create_vcpu(0)
{
panic!("WHvCreateVirtualProcessor failed! Reason: {e}");
}
let mut exit_ctxt=WHvRunVpExitContext::default();
if let Err(e)=vm.run_vcpu(0,&mut exit_ctxt)
{
panic!("WHvRunVirtualProcessor failed! Reason: {e}");
}
assert_eq!(exit_ctxt.exit_reason,WHvRunVpExitReason::X64_HALT);
}
#[test] fn io_exit()
{
init_bindings();
let mut vm=match Partition::create()
{
Ok(x)=>x,
Err(e)=>panic!("WHvCreatePartition failed! Reason: {e}")
};
fn pout(port:u16,buffer:&[u8])->HResult
{
assert_eq!(port,0xE9);
assert_eq!(buffer,&[0xA5]);
HResult::S_OK
}
let emu=match Emulator::new(None,Some(pout),None,None)
{
Ok(x)=>x,
Err(e)=>panic!("WHvEmulatorCreateEmulator failed! Reason: {e}")
};
if let Err(e)=vm.set_vcpu_count(1)
{
panic!("WHvSetPartitionProperty ProcessorCount failed! Reason: {e}");
}
if let Err(e)=vm.setup()
{
panic!("WHvSetupPartition failed! Reason: {e}");
}
const VMEM_SIZE:usize=0x1000;
let region=match MemoryRegion::new(VMEM_SIZE)
{
Ok(r)=>
{
let s=unsafe{core::slice::from_raw_parts_mut(r.0.cast::<u8>(),VMEM_SIZE)};
s[0xFF0]=0xE6;
s[0xFF1]=0xE9;
r
}
Err(e)=>panic!("VirtualAlloc failed! Reason: {e}")
};
if let Err(e)=unsafe{vm.map_gpa_range(0xFF000,region.0,VMEM_SIZE,WHvMapGpaRangeFlags::READ|WHvMapGpaRangeFlags::WRITE|WHvMapGpaRangeFlags::EXECUTE)}
{
panic!("WHvMapGpaRange failed! Reason: {e}");
}
if let Err(e)=vm.create_vcpu(0)
{
panic!("WHvCreateVirtualProcessor failed! Reason: {e}");
}
let names=[WHvRegisterName::X64_RAX];
let values=[WHvRegisterValue{reg64:0xA5}];
if let Err(e)=vm.set_vcpu_registers(0,&names,&values)
{
panic!("WHvSetVirtualProcessorRegisters failed! Reason: {e}");
}
let mut exit_ctxt=WHvRunVpExitContext::default();
if let Err(e)=vm.run_vcpu(0,&mut exit_ctxt)
{
panic!("WHvRunVirtualProcessor failed! Reason: {e}");
}
assert_eq!(exit_ctxt.exit_reason,WHvRunVpExitReason::X64_IO_PORT_ACCESS);
match emu.try_io_emulation(&vm,0,&exit_ctxt)
{
Ok(st)=>assert_eq!(st.into_bits(),1),
Err(e)=>panic!("WHvEmulatorTryIoEmulation failed! Reason: {e}")
}
}
}