From 041764bcd350ed754b0586f833c2735fb58b077f Mon Sep 17 00:00:00 2001 From: Elie Kheirallah Date: Thu, 23 Feb 2023 19:00:33 +0000 Subject: [PATCH] hypervisor: Add get_xsave and set_xsave support to x86. This change adds the function get_xsave to the x86/86_64 platform. get_xsave allows us to get state of the x87 FPU, MMX, XMM and YMM registers. This change is needed for serializing vCPUs. Adds also function to check if XSAVE2 Capability is enabled. get_xsave is not implemented yet for Windows. BUG=b:266515147 Test=Build and run VM Change-Id: I5a9d3b2b86bc11a66db331b4b25d0c348459ab69 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4292298 Reviewed-by: Daniel Verkamp Commit-Queue: Elie Kheirallah --- devices/tests/irqchip/userspace.rs | 7 ++++ hypervisor/src/haxm/vcpu.rs | 10 +++++ hypervisor/src/kvm/x86_64.rs | 67 ++++++++++++++++++++++++++++++ hypervisor/src/whpx/vcpu.rs | 14 +++++++ hypervisor/src/x86_64.rs | 10 +++++ kvm_sys/src/lib.rs | 1 + 6 files changed, 109 insertions(+) diff --git a/devices/tests/irqchip/userspace.rs b/devices/tests/irqchip/userspace.rs index 01e2548b8d..26ad3aaa30 100644 --- a/devices/tests/irqchip/userspace.rs +++ b/devices/tests/irqchip/userspace.rs @@ -54,6 +54,7 @@ use hypervisor::Vcpu; use hypervisor::VcpuExit; use hypervisor::VcpuRunHandle; use hypervisor::VcpuX86_64; +use hypervisor::Xsave; use resources::AddressRange; use resources::SystemAllocator; use resources::SystemAllocatorConfig; @@ -739,6 +740,12 @@ impl VcpuX86_64 for FakeVcpu { fn set_fpu(&self, _fpu: &Fpu) -> Result<()> { unimplemented!() } + fn get_xsave(&self) -> Result { + unimplemented!() + } + fn set_xsave(&self, _xsave: &Xsave) -> Result<()> { + unimplemented!() + } fn get_debugregs(&self) -> Result { unimplemented!() } diff --git a/hypervisor/src/haxm/vcpu.rs b/hypervisor/src/haxm/vcpu.rs index 75b516918a..551043a8ba 100644 --- a/hypervisor/src/haxm/vcpu.rs +++ b/hypervisor/src/haxm/vcpu.rs @@ -26,6 +26,7 @@ use data_model::vec_with_array_field; use libc::EINVAL; use libc::ENOENT; use libc::ENXIO; +use libc::EOPNOTSUPP; use vm_memory::GuestAddress; use super::*; @@ -47,6 +48,7 @@ use crate::Vcpu; use crate::VcpuExit; use crate::VcpuRunHandle; use crate::VcpuX86_64; +use crate::Xsave; // HAXM exit reasons // IO port request @@ -469,6 +471,14 @@ impl VcpuX86_64 for HaxmVcpu { Ok(()) } + fn get_xsave(&self) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn set_xsave(&self, _xsave: &Xsave) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + /// Gets the VCPU debug registers. fn get_debugregs(&self) -> Result { Ok(self.get_vcpu_state()?.get_debugregs()) diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs index 45375f4326..c8378275fb 100644 --- a/hypervisor/src/kvm/x86_64.rs +++ b/hypervisor/src/kvm/x86_64.rs @@ -3,6 +3,7 @@ // found in the LICENSE file. use std::arch::x86_64::CpuidResult; +use std::mem::size_of; use base::errno_result; use base::error; @@ -20,6 +21,7 @@ use base::Result; use data_model::vec_with_array_field; use kvm_sys::*; use libc::E2BIG; +use libc::EIO; use libc::ENXIO; use vm_memory::GuestAddress; @@ -55,10 +57,12 @@ use crate::VcpuExit; use crate::VcpuX86_64; use crate::VmCap; use crate::VmX86_64; +use crate::Xsave; use crate::MAX_IOAPIC_PINS; use crate::NUM_IOAPIC_PINS; type KvmCpuId = kvm::CpuId; +const KVM_XSAVE_MAX_SIZE: i32 = 4096; pub fn get_cpuid_with_initial_capacity( descriptor: &T, @@ -673,6 +677,63 @@ impl VcpuX86_64 for KvmVcpu { } } + /// If the VM reports using XSave2, the function will call XSave2. + fn get_xsave(&self) -> Result { + // Safe because we know that our file is a VM fd, we know that the + // kernel will only read correct amount of memory from our pointer, and + // we verify the return result. + // Get the size of Xsave in bytes. Values are of type u32. + let size = + unsafe { ioctl_with_val(&self.vm, KVM_CHECK_EXTENSION(), KVM_CAP_XSAVE2 as u64) }; + if size < 0 { + return errno_result(); + } + // Size / sizeof(u32) = len of vec. + let mut xsave: Vec = vec![0u32; size as usize / size_of::()]; + let ioctl_nr = if size > KVM_XSAVE_MAX_SIZE { + KVM_GET_XSAVE2() + } else { + KVM_GET_XSAVE() + }; + // Safe because we know that our file is a VCPU fd, we know the kernel will only write the + // correct amount of memory to our pointer, and we verify the return result. + let ret = unsafe { ioctl_with_mut_ptr(self, ioctl_nr, xsave.as_mut_ptr()) }; + if ret == 0 { + Ok(Xsave::from(xsave)) + } else { + errno_result() + } + } + + fn set_xsave(&self, xsave: &Xsave) -> Result<()> { + // Safe because we know that our file is a VM fd, we know that the + // kernel will only read correct amount of memory from our pointer, and + // get size from KVM_CAP_XSAVE2. Will return at least 4096 as a value if XSAVE2 is not + // supported or if no extensions are enabled. Otherwise it will return a value higher than + // 4096. + let size = + unsafe { ioctl_with_val(&self.vm, KVM_CHECK_EXTENSION(), KVM_CAP_XSAVE2 as u64) }; + if size < 0 { + return errno_result(); + } + // Ensure xsave is the same size as used in get_xsave. + // Return err if sizes don't match => not the same extensions are enabled for CPU. + if xsave.0.len() != size as usize / size_of::() { + return Err(Error::new(EIO)); + } + + // Safe because we know that our file is a VCPU fd, we know the kernel will only write the + // correct amount of memory to our pointer, and we verify the return result. + // Because of the len check above, and because the layout of `struct kvm_xsave` is + // compatible with a slice of `u32`, we can pass the pointer to `xsave` directly. + let ret = unsafe { ioctl_with_ptr(self, KVM_SET_XSAVE(), xsave.0.as_ptr()) }; + if ret == 0 { + Ok(()) + } else { + errno_result() + } + } + fn get_debugregs(&self) -> Result { // Safe because we know that our file is a VCPU fd, we know the kernel will only write the // correct amount of memory to our pointer, and we verify the return result. @@ -1316,6 +1377,12 @@ impl From<&Fpu> for kvm_fpu { } } +impl Xsave { + fn from(r: Vec) -> Self { + Xsave(r) + } +} + impl From<&kvm_debugregs> for DebugRegs { fn from(r: &kvm_debugregs) -> Self { DebugRegs { diff --git a/hypervisor/src/whpx/vcpu.rs b/hypervisor/src/whpx/vcpu.rs index 66a0847050..879b6b0980 100644 --- a/hypervisor/src/whpx/vcpu.rs +++ b/hypervisor/src/whpx/vcpu.rs @@ -17,6 +17,7 @@ use libc::EBUSY; use libc::EINVAL; use libc::ENOENT; use libc::ENXIO; +use libc::EOPNOTSUPP; use vm_memory::GuestAddress; use winapi::shared::winerror::E_UNEXPECTED; @@ -38,6 +39,7 @@ use crate::Vcpu; use crate::VcpuExit; use crate::VcpuRunHandle; use crate::VcpuX86_64; +use crate::Xsave; const WHPX_EXIT_DIRECTION_MMIO_READ: u8 = 0; const WHPX_EXIT_DIRECTION_MMIO_WRITE: u8 = 1; @@ -1047,6 +1049,18 @@ impl VcpuX86_64 for WhpxVcpu { }) } + /// Gets the VCPU XSAVE. + // TODO: b/270734340 implement + fn get_xsave(&self) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + /// Sets the VCPU XSAVE. + // TODO: b/270734340 implement + fn set_xsave(&self, _xsave: &Xsave) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + /// Gets the VCPU debug registers. fn get_debugregs(&self) -> Result { let mut whpx_debugregs: WhpxDebugRegs = Default::default(); diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs index 551266c175..ab780e37c4 100644 --- a/hypervisor/src/x86_64.rs +++ b/hypervisor/src/x86_64.rs @@ -95,6 +95,12 @@ pub trait VcpuX86_64: Vcpu { /// Sets the VCPU extended control registers. fn set_xcrs(&self, xcrs: &[Register]) -> Result<()>; + /// Gets the VCPU x87 FPU, MMX, XMM, YMM and MXCSR registers. + fn get_xsave(&self) -> Result; + + /// Sets the VCPU x87 FPU, MMX, XMM, YMM and MXCSR registers. + fn set_xsave(&self, xsave: &Xsave) -> Result<()>; + /// Gets the model-specific registers. `msrs` specifies the MSR indexes to be queried, and /// on success contains their indexes and values. fn get_msrs(&self, msrs: &mut Vec) -> Result<()>; @@ -858,3 +864,7 @@ pub enum CpuHybridType { /// Intel Core. Core, } + +/// State of the VCPU's x87 FPU, MMX, XMM, YMM registers. +/// May contain more state depending on enabled extensions. +pub struct Xsave(pub Vec); diff --git a/kvm_sys/src/lib.rs b/kvm_sys/src/lib.rs index 35a1c6c468..6b3bc7bded 100644 --- a/kvm_sys/src/lib.rs +++ b/kvm_sys/src/lib.rs @@ -59,6 +59,7 @@ pub mod x86 { ioctl_ior_nr!(KVM_GET_XCRS, KVMIO, 0xa6, kvm_xcrs); ioctl_iow_nr!(KVM_SET_XCRS, KVMIO, 0xa7, kvm_xcrs); ioctl_iowr_nr!(KVM_GET_SUPPORTED_HV_CPUID, KVMIO, 0xc1, kvm_cpuid2); + ioctl_ior_nr!(KVM_GET_XSAVE2, KVMIO, 0xcf, kvm_xsave); } #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]