blob: ad9fb3314532f2584f0d0cebecbb607f9cda4d44 [file]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implementation for WHPX hypervisor aka Windows Hyper-V platform.
#![allow(clippy::undocumented_unsafe_blocks)] // FIXME
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::*;
// used by both the vm and vcpu
struct SafePartition {
partition: WHV_PARTITION_HANDLE,
}
// we can send the partition over safely even though it is void*, it can be sent
// in another thread safely.
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();
// safe because we pass in the partition handle for the system to fill in.
check_whpx!(unsafe { WHvCreatePartition(&mut partition_handle) })
.map_err(WhpxError::CreatePartitionError)?;
Ok(SafePartition {
partition: partition_handle,
})
}
}
impl Drop for SafePartition {
fn drop(&mut self) {
// safe because we own this partition
check_whpx!(unsafe { WHvDeletePartition(self.partition) }).unwrap();
}
}
#[derive(Clone)]
pub struct Whpx {
// there is no general whpx object, the vm contains the partition.
}
/// Enum of WHPX Features. Similar to WHV_CAPABILITY_FEATURES but allows us to avoid making the
/// whpx_sys crate pub.
#[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) => {
// safe because we trust the kernel to fill in hypervisor present in the union
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> {
// use LazyLock to cache the results of the get_capability call
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 {
/// Makes a shallow clone of this `Hypervisor`.
fn try_clone(&self) -> Result<Self> {
Ok(self.clone())
}
/// Checks if a particular `HypervisorCap` is available.
fn check_capability(&self, cap: HypervisorCap) -> bool {
// whpx supports immediate exit, user memory, and the xcr0 state
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
})
}
// under whpx, guests rely on this leaf to calibrate their clocksource.
HypervisorCap::CalibratedTscLeafRequired => true,
_ => false,
}
}
}
/// Build a CpuIdEntry for a given `function`/`index` from the host results for that
/// `function`/`index`.
fn cpuid_entry_from_host(function: u32, index: u32) -> CpuIdEntry {
// Safe because arguments are passed by value
let result = unsafe { __cpuid_count(function, index) };
CpuIdEntry {
function,
index,
flags: 0,
cpuid: result,
}
}
impl HypervisorX86_64 for Whpx {
/// Get the system supported CPUID values.
///
/// WHPX does not have an API for getting this information, so we just return the host values
/// instead. This is not technically accurate, since WHPX does modify the contents of various
/// leaves, but in practice this is fine because this function is only used for pre-setting
/// the contents of certain leaves that we can safely base off of the host value.
fn get_supported_cpuid(&self) -> Result<CpuId> {
Ok(CpuId {
cpu_id_entries: vec![
// Leaf 0 just contains information about the max leaf. WHPX seems to set this to
// a value lower than the host value but we want it to be at least 0x15. We set it
// to the host value here assuming that the leaves above 0x15 probably won't hurt
// the guest.
cpuid_entry_from_host(0, 0),
// crosvm overrides the entirety of leaves 2, 0x80000005, and 0x80000006 to the
// host value, so we just return the host value here.
cpuid_entry_from_host(2, 0),
cpuid_entry_from_host(0x80000005, 0),
cpuid_entry_from_host(0x80000006, 0),
],
})
}
/// Gets the list of supported MSRs.
/// TODO: this is only used by the plugin
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));
}
}