diff --git a/mshv-bindings/src/hvcall.rs b/mshv-bindings/src/hvcall.rs new file mode 100644 index 00000000..752bac6e --- /dev/null +++ b/mshv-bindings/src/hvcall.rs @@ -0,0 +1,186 @@ +// Copyright © 2024, Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// + +use crate::bindings::*; +use std::mem::size_of; +use std::vec::Vec; + +/// This file contains helper functions for the MSHV_ROOT_HVCALL ioctl. +/// MSHV_ROOT_HVCALL is basically a 'passthrough' hypercall. The kernel makes a +/// hypercall on behalf of the VMM without interpreting the arguments or result +/// or changing any state in the kernel. + +/// RepInput wraps a buffer containing the input for a "rep"[1] hypercall. +/// Rep hypercalls have rep-eated data, i.e. a variable length array as part of +/// the input structure e.g.: +/// ``` +/// use mshv_bindings::bindings::*; +/// #[repr(C, packed)] +/// struct hv_input_foo { +/// some_field: __u64, +/// variable_array_field: __IncompleteArrayField<__u64>, +/// } +/// ``` +/// The struct cannot be used as-is because it can't store anything in the +/// __IncompleteArrayField field. +/// +/// RepInput abstracts a rep hypercall input by wrapping a Vec, where T +/// is the hv_input_* struct type. The buffer backing the Vec has enough +/// space to store both the hv_input_* struct (at index 0), and the rep data +/// immediately following it. +/// +/// Note also that the length of the variable length array field is not stored in +/// this struct. Rather, it is passed to the hypercall in the 'rep count' field +/// of the hypercall args (mshv_root_hvcall.reps). RepInput stores this count, +/// along with the size of the entire input data. +/// +/// RepInput is intended to be created with make_rep_input!() and used with +/// make_rep_args!() below. +/// +/// [1] HyperV TLFS describing the hypercall interface and rep hypercalls: +/// https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercall-interface +/// +pub struct RepInput { + vec: Vec, + size: usize, + rep_count: usize, +} +impl RepInput { + /// Create a RepInput for a rep hypercall + /// + /// # Arguments + /// + /// * `vec` - Vec Created via vec_with_array_field(). T is hv_input_* struct + /// * `size` - Size of the hypercall input, including the rep data + /// * `rep_count` - number of reps + pub fn new(vec: Vec, size: usize, rep_count: usize) -> Self { + Self { + vec, + size, + rep_count, + } + } + pub fn as_mut_struct_ref(&mut self) -> &mut T { + &mut self.vec[0] + } + pub fn as_struct_ptr(&self) -> *const T { + &self.vec[0] + } + pub fn rep_count(&self) -> usize { + self.rep_count + } + pub fn size(&self) -> usize { + self.size + } +} + +/// Make `Vec` with at least enough space for `count` + 1 entries of +/// `entry_size`, where `entry_size` != size_of::() +/// The first element of the Vec will hold the T data, and the rest will hold +/// elements of size `entry_size` (note the Vec cannot be used normally to get +/// at these) +pub fn vec_with_array_field(t: T, entry_size: usize, count: usize) -> Vec { + let element_space = count * entry_size; + let vec_size_bytes = size_of::() + element_space; + let rounded_size = (vec_size_bytes + size_of::() - 1) / size_of::(); + let mut v = Vec::with_capacity(rounded_size); + v.resize_with(rounded_size, T::default); + v[0] = t; + v +} + +/// Utility for casting __IncompleteArrayField as the interior type +pub fn arr_field_as_entry_ptr_mut(ptr: *mut __IncompleteArrayField) -> *mut T { + ptr as *mut T +} + +/// Utility for getting the interior type of a __IncompleteArrayField +/// Note we must use a raw pointer rather than a reference here, because the +/// field itself may be unaligned due to the struct being packed +pub fn arr_field_entry_size(_: *const __IncompleteArrayField) -> usize { + size_of::() +} + +/// Assemble a RepInput from a hypercall input struct and an array of rep data +/// Arguments: +/// 1. The hv_input_* struct with the input data +/// 2. Name of the __IncompleteArrayField in the struct +/// 3. An array or slice containing the rep data +#[macro_export] +macro_rules! make_rep_input { + ($struct_expr:expr, $field_ident:ident, $arr_expr:expr) => {{ + let s = $struct_expr; + let a = $arr_expr; + let el_size = arr_field_entry_size(std::ptr::addr_of!(s.$field_ident)); + let struct_size = std::mem::size_of_val(&s); + let mut vec = vec_with_array_field(s, el_size, a.len()); + // SAFETY: we know the vector is large enough to hold the data + let ptr = arr_field_as_entry_ptr_mut(std::ptr::addr_of_mut!(vec[0].$field_ident)); + for (i, el) in a.iter().enumerate() { + unsafe { + let mut p = ptr.add(i); + p.write_unaligned(*el); + }; + } + RepInput::new(vec, struct_size + el_size * a.len(), a.len()) + }}; +} + +/// Create a mshv_root_hvcall populated with rep hypercall parameters +/// Arguments: +/// 1. hypercall code +/// 2. RepInput structure, where T is hv_input_*. See make_rep_input!() +/// 3. Slice of the correct type for output data (optional) +#[macro_export] +macro_rules! make_rep_args { + ($code_expr:expr, $input_ident:ident, $output_slice_ident:ident) => {{ + mshv_root_hvcall { + code: $code_expr as u16, + reps: $input_ident.rep_count() as u16, + in_sz: $input_ident.size() as u16, + out_sz: (std::mem::size_of_val(&$output_slice_ident[0]) * $output_slice_ident.len()) + as u16, + in_ptr: $input_ident.as_struct_ptr() as u64, + out_ptr: std::ptr::addr_of_mut!($output_slice_ident[0]) as u64, + ..Default::default() + } + }}; + ($code_expr:expr, $input_ident:ident) => {{ + mshv_root_hvcall { + code: $code_expr as u16, + reps: $input_ident.rep_count() as u16, + in_sz: $input_ident.size() as u16, + in_ptr: $input_ident.as_struct_ptr() as u64, + ..Default::default() + } + }}; +} + +/// Create a mshv_root_hvcall populated with hypercall parameters +/// Arguments: +/// 1. hypercall code +/// 2. hv_input_* structure +/// 3. hv_output_* structure (optional) +#[macro_export] +macro_rules! make_args { + ($code_expr:expr, $input_ident:ident, $output_ident:ident) => {{ + mshv_root_hvcall { + code: $code_expr as u16, + in_sz: std::mem::size_of_val(&$input_ident) as u16, + out_sz: std::mem::size_of_val(&$output_ident) as u16, + in_ptr: std::ptr::addr_of!($input_ident) as u64, + out_ptr: std::ptr::addr_of!($output_ident) as u64, + ..Default::default() + } + }}; + ($code_expr:expr, $input_ident:ident) => {{ + mshv_root_hvcall { + code: $code_expr as u16, + in_sz: std::mem::size_of_val(&$input_ident) as u16, + in_ptr: std::ptr::addr_of!($input_ident) as u64, + ..Default::default() + } + }}; +} diff --git a/mshv-bindings/src/lib.rs b/mshv-bindings/src/lib.rs index 59296c13..f20e85cf 100644 --- a/mshv-bindings/src/lib.rs +++ b/mshv-bindings/src/lib.rs @@ -13,10 +13,13 @@ mod x86_64; #[cfg(target_arch = "x86_64")] pub use self::x86_64::*; -pub mod hvdef; -pub use hvdef::*; - #[cfg(target_arch = "aarch64")] mod arm64; #[cfg(target_arch = "aarch64")] pub use self::arm64::*; + +pub mod hvdef; +pub use hvdef::*; + +pub mod hvcall; +pub use hvcall::*; diff --git a/mshv-ioctls/src/ioctls/mod.rs b/mshv-ioctls/src/ioctls/mod.rs index b9761d50..fab01295 100644 --- a/mshv-ioctls/src/ioctls/mod.rs +++ b/mshv-ioctls/src/ioctls/mod.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause // -use mshv_bindings::{mshv_root_hvcall, HvError}; +use mshv_bindings::{mshv_root_hvcall, HvError, HV_STATUS_SUCCESS}; use thiserror::Error; use vmm_sys_util::errno; pub mod device; @@ -47,8 +47,7 @@ impl MshvError { /// * `ret_args` - MSHV_ROOT_HVCALL args struct, after the ioctl completed pub fn from_hvcall(error: errno::Error, ret_args: mshv_root_hvcall) -> Self { use std::convert::TryFrom; - // EIO signals that the hypercall itself may have failed - if error.errno() == libc::EIO && ret_args.status != 0 { + if ret_args.status != HV_STATUS_SUCCESS as u16 { let hv_err = HvError::try_from(ret_args.status); return MshvError::Hypercall { code: ret_args.code, @@ -138,15 +137,9 @@ mod tests { status: HV_STATUS_SUCCESS as u16, ..Default::default() }; - { - let ioctl_err = errno::Error::new(libc::EFAULT); - let mshv_err = MshvError::from_hvcall(ioctl_err, args); - assert!(mshv_err == MshvError::Errno(ioctl_err)); - } { - // special case: EIO with successful hypercall - let ioctl_err = errno::Error::new(libc::EIO); + let ioctl_err = errno::Error::new(libc::EINVAL); let mshv_err = MshvError::from_hvcall(ioctl_err, args); assert!(mshv_err == MshvError::Errno(ioctl_err)); diff --git a/mshv-ioctls/src/ioctls/vcpu.rs b/mshv-ioctls/src/ioctls/vcpu.rs index 9e9de4d6..fb750634 100644 --- a/mshv-ioctls/src/ioctls/vcpu.rs +++ b/mshv-ioctls/src/ioctls/vcpu.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause // -use crate::ioctls::Result; +use crate::ioctls::{MshvError, Result}; use crate::mshv_ioctls::*; use mshv_bindings::*; use std::convert::TryFrom; @@ -43,6 +43,7 @@ macro_rules! set_registers_64 { #[derive(Debug)] /// Wrapper over Mshv vCPU ioctls. pub struct VcpuFd { + index: u32, vcpu: File, } @@ -51,8 +52,8 @@ pub struct VcpuFd { /// This should not be exported as a public function because the preferred way is to use /// `create_vcpu` from `VmFd`. The function cannot be part of the `VcpuFd` implementation because /// then it would be exported with the public `VcpuFd` interface. -pub fn new_vcpu(vcpu: File) -> VcpuFd { - VcpuFd { vcpu } +pub fn new_vcpu(index: u32, vcpu: File) -> VcpuFd { + VcpuFd { index, vcpu } } impl AsRawFd for VcpuFd { @@ -80,12 +81,44 @@ impl VcpuFd { } Ok(()) } - /// Sets a vCPU register to input value. - /// - /// # Arguments - /// - /// * `reg_name` - general purpose register name. - /// * `reg_value` - register value. + /// Generic hvcall version of get_reg + pub fn hvcall_get_reg(&self, reg_assocs: &mut [hv_register_assoc]) -> Result<()> { + if reg_assocs.is_empty() { + return Err(libc::EINVAL.into()); + } + let reg_names: Vec = reg_assocs.iter().map(|assoc| assoc.name).collect(); + let input = make_rep_input!( + hv_input_get_vp_registers { + vp_index: self.index, + ..Default::default() + }, + names, + reg_names.as_slice() + ); + let mut output: Vec = reg_names + .iter() + .map(|_| hv_register_value { + reg128: hv_u128 { + ..Default::default() + }, + }) + .collect(); + let output_slice = output.as_mut_slice(); + + let mut args = make_rep_args!(HVCALL_GET_VP_REGISTERS, input, output_slice); + self.hvcall(&mut args)?; + + if args.reps as usize != reg_assocs.len() { + return Err(libc::EINTR.into()); + } + + for (assoc, value) in reg_assocs.iter_mut().zip(output.iter()) { + assoc.value = *value; + } + + Ok(()) + } + /// Set vcpu register values by providing an array of register assocs #[cfg(not(target_arch = "aarch64"))] pub fn set_reg(&self, regs: &[hv_register_assoc]) -> Result<()> { let hv_vp_register_args = mshv_vp_registers { @@ -97,6 +130,26 @@ impl VcpuFd { if ret != 0 { return Err(errno::Error::last().into()); } + + Ok(()) + } + /// Generic hypercall version of set_reg + pub fn hvcall_set_reg(&self, reg_assocs: &[hv_register_assoc]) -> Result<()> { + let input = make_rep_input!( + hv_input_set_vp_registers { + vp_index: self.index, + ..Default::default() + }, + elements, + reg_assocs + ); + let mut args = make_rep_args!(HVCALL_SET_VP_REGISTERS, input); + self.hvcall(&mut args)?; + + if args.reps as usize != reg_assocs.len() { + return Err(libc::EINTR.into()); + } + Ok(()) } /// Sets the vCPU general purpose registers @@ -908,6 +961,29 @@ impl VcpuFd { Ok((gpa, result)) } + /// Generic hvcall version of translate guest virtual address + pub fn hvcall_translate_gva( + &self, + gva: u64, + flags: u64, + ) -> Result<(u64, hv_translate_gva_result)> { + let input = hv_input_translate_virtual_address { + vp_index: self.index, + control_flags: flags, + gva_page: gva >> HV_HYP_PAGE_SHIFT, + ..Default::default() // NOTE: kernel will populate partition_id field + }; + let output = hv_output_translate_virtual_address { + ..Default::default() + }; + let mut args = make_args!(HVCALL_TRANSLATE_VIRTUAL_ADDRESS, input, output); + self.hvcall(&mut args)?; + + let gpa = (output.gpa_page << HV_HYP_PAGE_SHIFT) | (gva & !(1 << HV_HYP_PAGE_SHIFT)); + + Ok((gpa, output.translation_result)) + } + /// X86 specific call that returns the vcpu's current "suspend registers". #[cfg(not(target_arch = "aarch64"))] pub fn get_suspend_regs(&self) -> Result { @@ -1044,6 +1120,48 @@ impl VcpuFd { } Ok([parms.eax, parms.ebx, parms.ecx, parms.edx]) } + /// Generic hvcall version of get cpuid values + #[cfg(not(target_arch = "aarch64"))] + pub fn hvcall_get_cpuid_values( + &self, + eax: u32, + ecx: u32, + xfem: u64, + xss: u64, + ) -> Result<[u32; 4]> { + let mut input = make_rep_input!( + hv_input_get_vp_cpuid_values { + vp_index: self.index, + ..Default::default() // NOTE: Kernel will populate partition_id field + }, + cpuid_leaf_info, + [hv_cpuid_leaf_info { + eax, + ecx, + xfem, + xss, + }] + ); + unsafe { + input + .as_mut_struct_ref() + .flags + .__bindgen_anon_1 + .set_use_vp_xfem_xss(1); + input + .as_mut_struct_ref() + .flags + .__bindgen_anon_1 + .set_apply_registered_values(1); + } + let mut output_arr: [hv_output_get_vp_cpuid_values; 1] = [Default::default()]; + let mut args = make_rep_args!(HVCALL_GET_VP_CPUID_VALUES, input, output_arr); + self.hvcall(&mut args)?; + + // SAFETY: The hvcall succeeded, and both fields of the union are + // equivalent. Just return the array instead of taking eax, ebx, etc... + Ok(unsafe { output_arr[0].as_uint32 }) + } /// Read GPA pub fn gpa_read(&self, input: &mut mshv_read_write_gpa) -> Result { // SAFETY: we know that our file is a vCPU fd, we know the kernel honours its ABI. @@ -1115,6 +1233,17 @@ impl VcpuFd { } Ok(()) } + + /// Execute a hypercall for this vp + pub fn hvcall(&self, args: &mut mshv_root_hvcall) -> Result<()> { + // SAFETY: IOCTL with correct types + let ret = unsafe { ioctl_with_ref(self, MSHV_ROOT_HVCALL(), args) }; + if ret == 0 { + Ok(()) + } else { + Err(MshvError::from_hvcall(errno::Error::last(), *args)) + } + } } #[allow(dead_code)] @@ -1126,11 +1255,7 @@ mod tests { #[test] fn test_set_get_regs() { - let hv = Mshv::new().unwrap(); - let vm = hv.create_vm().unwrap(); - let vcpu = vm.create_vcpu(0).unwrap(); - - vcpu.set_reg(&[ + let set_reg_assocs: [hv_register_assoc; 2] = [ hv_register_assoc { name: hv_register_name_HV_X64_REGISTER_RIP, value: hv_register_value { reg64: 0x1000 }, @@ -1141,10 +1266,8 @@ mod tests { value: hv_register_value { reg64: 0x2 }, ..Default::default() }, - ]) - .unwrap(); - - let mut get_regs: [hv_register_assoc; 2] = [ + ]; + let get_reg_assocs: [hv_register_assoc; 2] = [ hv_register_assoc { name: hv_register_name_HV_X64_REGISTER_RIP, ..Default::default() @@ -1155,12 +1278,30 @@ mod tests { }, ]; - vcpu.get_reg(&mut get_regs).unwrap(); + for i in [0, 1] { + let hv = Mshv::new().unwrap(); + let vm = hv.create_vm().unwrap(); + let vcpu = vm.create_vcpu(0).unwrap(); - // SAFETY: access union fields - unsafe { - assert!(get_regs[0].value.reg64 == 0x1000); - assert!(get_regs[1].value.reg64 == 0x2); + if i == 0 { + vcpu.set_reg(&set_reg_assocs).unwrap(); + } else { + vcpu.hvcall_set_reg(&set_reg_assocs).unwrap(); + } + + let mut get_regs: [hv_register_assoc; 2] = get_reg_assocs; + + if i == 0 { + vcpu.get_reg(&mut get_regs).unwrap(); + } else { + vcpu.hvcall_get_reg(&mut get_regs).unwrap(); + } + + // SAFETY: access union fields + unsafe { + assert!(get_regs[0].value.reg64 == 0x1000); + assert!(get_regs[1].value.reg64 == 0x2); + } } } @@ -1530,9 +1671,11 @@ mod tests { let hv = Mshv::new().unwrap(); let vm = hv.create_vm().unwrap(); let vcpu = vm.create_vcpu(0).unwrap(); - let res = vcpu.get_cpuid_values(0, 0, 0, 0).unwrap(); - let max_function = res[0]; + let res_0 = vcpu.get_cpuid_values(0, 0, 0, 0).unwrap(); + let max_function = res_0[0]; assert!(max_function >= 1); + let res_1 = vcpu.hvcall_get_cpuid_values(0, 0, 0, 0).unwrap(); + assert!(res_1[0] >= 1); } #[test] diff --git a/mshv-ioctls/src/ioctls/vm.rs b/mshv-ioctls/src/ioctls/vm.rs index 71922cef..e87117c3 100644 --- a/mshv-ioctls/src/ioctls/vm.rs +++ b/mshv-ioctls/src/ioctls/vm.rs @@ -4,7 +4,7 @@ // use crate::ioctls::device::{new_device, DeviceFd}; use crate::ioctls::vcpu::{new_vcpu, VcpuFd}; -use crate::ioctls::Result; +use crate::ioctls::{MshvError, Result}; use crate::mshv_ioctls::*; use mshv_bindings::*; @@ -202,7 +202,7 @@ impl VmFd { // SAFETY: we're sure vcpu_fd is valid. let vcpu = unsafe { File::from_raw_fd(vcpu_fd) }; - Ok(new_vcpu(vcpu)) + Ok(new_vcpu(id as u32, vcpu)) } /// Inject an interrupt into the guest.. pub fn request_virtual_interrupt(&self, request: &InterruptRequest) -> Result<()> { @@ -572,6 +572,16 @@ impl VmFd { Err(errno::Error::last().into()) } } + /// Generic hvcall version of set_partition_property + pub fn hvcall_set_partition_property(&self, code: u32, value: u64) -> Result<()> { + let input = hv_input_set_partition_property { + property_code: code, + property_value: value, + ..Default::default() // NOTE: kernel will populate partition_id field + }; + let mut args = make_args!(HVCALL_SET_PARTITION_PROPERTY, input); + self.hvcall(&mut args) + } /// Enable dirty page tracking by hypervisor /// Flags: /// bit 1: Enabled @@ -705,6 +715,17 @@ impl VmFd { Err(errno::Error::last().into()) } } + + /// Execute a hypercall for this partition + pub fn hvcall(&self, args: &mut mshv_root_hvcall) -> Result<()> { + // SAFETY: IOCTL with correct types + let ret = unsafe { ioctl_with_ref(self, MSHV_ROOT_HVCALL(), args) }; + if ret == 0 { + Ok(()) + } else { + Err(MshvError::from_hvcall(errno::Error::last(), *args)) + } + } } /// Helper function to create a new `VmFd`. /// @@ -784,23 +805,10 @@ mod tests { intercept_type: hv_intercept_type_HV_INTERCEPT_TYPE_X64_CPUID, intercept_parameter: hv_intercept_parameters { cpuid_index: 0x100 }, }; - vm.install_intercept(intercept_args).unwrap(); + assert!(vm.install_intercept(intercept_args).is_ok()); } #[test] - fn test_setting_immutable_partition_property() { - let hv = Mshv::new().unwrap(); - let vm = hv.create_vm().unwrap(); - let res = vm.set_partition_property( - hv_partition_property_code_HV_PARTITION_PROPERTY_PRIVILEGE_FLAGS, - 0, - ); - - // We should get an error, because we are trying to change an immutable - // partition property. - assert!(res.is_err()) - } - #[test] - fn test_get_set_property() { + fn test_get_property() { let hv = Mshv::new().unwrap(); let vm = hv.create_vm().unwrap(); @@ -838,6 +846,52 @@ mod tests { ); } #[test] + fn test_set_property() { + let hv = Mshv::new().unwrap(); + let vm = hv.create_vm().unwrap(); + + let code = hv_partition_property_code_HV_PARTITION_PROPERTY_UNIMPLEMENTED_MSR_ACTION; + let ignore = + hv_unimplemented_msr_action_HV_UNIMPLEMENTED_MSR_ACTION_IGNORE_WRITE_READ_ZERO as u64; + let fault = hv_unimplemented_msr_action_HV_UNIMPLEMENTED_MSR_ACTION_FAULT as u64; + + vm.set_partition_property(code, ignore).unwrap(); + let ignore_ret = vm.get_partition_property(code).unwrap(); + assert!(ignore_ret == ignore); + + vm.set_partition_property(code, fault).unwrap(); + let fault_ret = vm.get_partition_property(code).unwrap(); + assert!(fault_ret == fault); + + // Test the same with hvcall_ equivalent + vm.hvcall_set_partition_property(code, ignore).unwrap(); + let ignore_ret = vm.get_partition_property(code).unwrap(); + assert!(ignore_ret == ignore); + + vm.hvcall_set_partition_property(code, fault).unwrap(); + let fault_ret = vm.get_partition_property(code).unwrap(); + assert!(fault_ret == fault); + } + #[test] + fn test_set_partition_property_invalid() { + let hv = Mshv::new().unwrap(); + let vm = hv.create_vm().unwrap(); + let code = hv_partition_property_code_HV_PARTITION_PROPERTY_PRIVILEGE_FLAGS; + + // old IOCTL + let res_0 = vm.set_partition_property(code, 0); + assert!(res_0.is_err()); + + // generic hvcall + let res_1 = vm.hvcall_set_partition_property(code, 0); + let mshv_err_check = MshvError::Hypercall { + code: HVCALL_SET_PARTITION_PROPERTY as u16, + status_raw: HV_STATUS_INVALID_PARTITION_STATE as u16, + status: Some(HvError::InvalidPartitionState), + }; + assert!(res_1.err().unwrap() == mshv_err_check); + } + #[test] fn test_irqfd() { use libc::EFD_NONBLOCK; let hv = Mshv::new().unwrap(); diff --git a/mshv-ioctls/src/mshv_ioctls.rs b/mshv-ioctls/src/mshv_ioctls.rs index a6bb0164..1daffc75 100644 --- a/mshv-ioctls/src/mshv_ioctls.rs +++ b/mshv-ioctls/src/mshv_ioctls.rs @@ -134,3 +134,5 @@ ioctl_iow_nr!( 0x34, mshv_sev_snp_ap_create ); + +ioctl_iowr_nr!(MSHV_ROOT_HVCALL, MSHV_IOCTL, 0x35, mshv_root_hvcall);