From ee8b08176e7a5bc6b7eafe5355e35f05814ebfae Mon Sep 17 00:00:00 2001 From: Nuno Das Neves Date: Tue, 9 Apr 2024 19:00:10 +0000 Subject: [PATCH] ioctls: Implement hvcall_ versions of various IOCTLs While leaving existing implementations untouched. Add: - VmFd::hvcall_set_partition_property - VcpuFd::hvcall_translate_gva - VcpuFd::hvcall_get_cpuid_values - VcpuFd::hvcall_get_reg - VcpuFd::hvcall_set_reg Signed-off-by: Nuno Das Neves --- mshv-ioctls/src/ioctls/vcpu.rs | 131 +++++++++++++++++++++++++++++++-- mshv-ioctls/src/ioctls/vm.rs | 53 +++++++++++-- 2 files changed, 171 insertions(+), 13 deletions(-) diff --git a/mshv-ioctls/src/ioctls/vcpu.rs b/mshv-ioctls/src/ioctls/vcpu.rs index 76d128e6..2385199a 100644 --- a/mshv-ioctls/src/ioctls/vcpu.rs +++ b/mshv-ioctls/src/ioctls/vcpu.rs @@ -81,12 +81,45 @@ 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() { + // TODO better handling? partial success? + 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 { @@ -98,6 +131,27 @@ 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() { + // TODO better handling? partial success? + return Err(libc::EINTR.into()); + } + Ok(()) } /// Sets the vCPU general purpose registers @@ -909,6 +963,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 { @@ -1045,6 +1122,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: we know the hvcall succeeded, and both fields of the union + // are equivalent we 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. diff --git a/mshv-ioctls/src/ioctls/vm.rs b/mshv-ioctls/src/ioctls/vm.rs index 1d28ae68..4e19d9f3 100644 --- a/mshv-ioctls/src/ioctls/vm.rs +++ b/mshv-ioctls/src/ioctls/vm.rs @@ -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_INSTALL_INTERCEPT, input); + self.hvcall(&mut args) + } /// Enable dirty page tracking by hypervisor /// Flags: /// bit 1: Enabled @@ -801,17 +811,20 @@ mod tests { 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, - ); - + let code = hv_partition_property_code_HV_PARTITION_PROPERTY_PRIVILEGE_FLAGS; + // old IOCTL + let res_0 = vm.set_partition_property(code, 0); // We should get an error, because we are trying to change an immutable // partition property. - assert!(res.is_err()) + assert!(res_0.is_err()); + // generic hvcall + let res_1 = vm.hvcall_set_partition_property(code, 0); + // We should get a hypercall error? + assert!(res_1.is_err()); + assert!(matches!(res_1.err().unwrap(), MshvError::Hypercall{ .. })); } #[test] - fn test_get_set_property() { + fn test_get_property() { let hv = Mshv::new().unwrap(); let vm = hv.create_vm().unwrap(); @@ -849,6 +862,32 @@ 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_irqfd() { use libc::EFD_NONBLOCK; let hv = Mshv::new().unwrap();