From c03ec9c9924f726e2d670bab0c115b66a1a94a45 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 Add tests for the above. Signed-off-by: Nuno Das Neves --- mshv-ioctls/src/ioctls/vcpu.rs | 175 ++++++++++++++++++++++++++++----- mshv-ioctls/src/ioctls/vm.rs | 71 ++++++++++--- 2 files changed, 210 insertions(+), 36 deletions(-) diff --git a/mshv-ioctls/src/ioctls/vcpu.rs b/mshv-ioctls/src/ioctls/vcpu.rs index 5d9bcb84..fb750634 100644 --- a/mshv-ioctls/src/ioctls/vcpu.rs +++ b/mshv-ioctls/src/ioctls/vcpu.rs @@ -81,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 { @@ -98,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 @@ -909,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 { @@ -1045,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. @@ -1138,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 }, @@ -1153,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() @@ -1167,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); + } } } @@ -1542,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 1d28ae68..e87117c3 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_SET_PARTITION_PROPERTY, input); + self.hvcall(&mut args) + } /// Enable dirty page tracking by hypervisor /// Flags: /// bit 1: Enabled @@ -798,20 +808,7 @@ mod tests { 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(); @@ -849,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();