Skip to content

Commit 65a665a

Browse files
zulinx86Patrick Roy
authored andcommitted
Add support for KVM_GET_XSAVE2
Since Linux 5.17, the `kvm_xsave` struct has a flexiblre array member (FAM) at the end, which can be retrieved using the `KVM_GET_XSAVE2` ioctl [1]. What makes this FAM special is that the length is not stored in itself, but has to be retrieved via `KVM_CHECK_CAPABILITY(KVM_CAP_XSAVE2)` which returns the total size of the `kvm_xsave` struct (e.g. the traditional 4096 byte region + extra region with the size of the FAM). To support it in rust-vmm, `Xsave` has been introduced as its `FamStructWrapper`. The size required to hold the whole `kvm_xsave` structure can vary depending on features that have been dynamically enabled by `arch_prctl()` [2]. Any features must not be enabled after the size has been confirmed; otherwise, `KVM_GET_XSAVE2` writes beyond the allocated area for `Xsave`, potentially causing undefined behavior. It is unable to put `KVM_CHECK_CAPABILITY` call for the size check and `KVM_GET_XSAVE2` call together into `get_xsave2()`, because there is a chance of race condition where another thread enables additional features between them. That's why `get_xsave2()` is marked `unsafe`. Although `KVM_SET_XSAVE` was extended and `KVM_SET_XSAVE2` was not added to support the `kvm_xsave` with FAM, `set_xsave2()` is also implemented to enable users to pass `Xsave` to it for convenience. That is also marked `unsafe` for the same reason. [1]: https://www.kernel.org/doc/html/latest/virt/kvm/api.html#kvm-get-xsave2 [2]: https://docs.kernel.org/arch/x86/xstate.html Co-authored-by: Patrick Roy <[email protected]> Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 7564f91 commit 65a665a

File tree

4 files changed

+127
-2
lines changed

4 files changed

+127
-2
lines changed

kvm-ioctls/CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
## Upcoming Release
44

5+
### Added
6+
7+
- [[#310](https://github.com/rust-vmm/kvm/pull/310)]: Added support for
8+
`KVM_CAP_XSAVE2` and the `KVM_GET_XSAVE2` ioctl.
9+
510
## v0.20.0
611

7-
### Added
12+
### Added
813

9-
- [[#288](https://github.com/rust-vmm/kvm-ioctls/pull/288)]: Introduce `Cap::GuestMemfd`, `Cap::MemoryAttributes` and
14+
- [[#288](https://github.com/rust-vmm/kvm-ioctls/pull/288)]: Introduce `Cap::GuestMemfd`, `Cap::MemoryAttributes` and
1015
`Cap::UserMemory2` capabilities enum variants for use with `VmFd::check_extension`.
1116
- [[#288](https://github.com/rust-vmm/kvm-ioctls/pull/288)]: Introduce `VmFd::check_extension_raw` and `VmFd::check_extension_int` to allow `KVM_CHECK_EXTENSION` to return integer.
1217

kvm-ioctls/src/cap.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub enum Cap {
8282
#[cfg(target_arch = "x86_64")]
8383
Xsave = KVM_CAP_XSAVE,
8484
#[cfg(target_arch = "x86_64")]
85+
Xsave2 = KVM_CAP_XSAVE2,
86+
#[cfg(target_arch = "x86_64")]
8587
Xcrs = KVM_CAP_XCRS,
8688
PpcGetPvinfo = KVM_CAP_PPC_GET_PVINFO,
8789
PpcIrqLevel = KVM_CAP_PPC_IRQ_LEVEL,

kvm-ioctls/src/ioctls/vcpu.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,61 @@ impl VcpuFd {
861861
Ok(xsave)
862862
}
863863

864+
/// X86 specific call that gets the current vcpu's "xsave struct" via `KVM_GET_XSAVE2`.
865+
///
866+
/// See the documentation for `KVM_GET_XSAVE2` in the
867+
/// [KVM API doc](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt).
868+
///
869+
/// # Arguments
870+
///
871+
/// * `xsave` - A mutable reference to an [`Xsave`] instance that will be populated with the
872+
/// current vcpu's "xsave struct".
873+
///
874+
/// # Safety
875+
///
876+
/// This function is unsafe because there is no guarantee `xsave` is allocated with enough space
877+
/// to hold the entire xsave state.
878+
///
879+
/// The required size can be retrieved via `KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2)` and can vary
880+
/// depending on features that have been dynamically enabled by `arch_prctl()`. Thus, any
881+
/// features must not be enabled dynamically after the required size has been confirmed.
882+
///
883+
/// If `xsave` is not large enough, `KVM_GET_XSAVE2` copies data beyond the allocated area,
884+
/// possibly causing undefined behavior.
885+
///
886+
/// See the documentation for dynamically enabled XSTATE features in the
887+
/// [kernel doc](https://docs.kernel.org/arch/x86/xstate.html).
888+
///
889+
/// # Example
890+
///
891+
/// ```rust
892+
/// # extern crate kvm_ioctls;
893+
/// # extern crate kvm_bindings;
894+
/// # use kvm_ioctls::{Kvm, Cap};
895+
/// # use kvm_bindings::{Xsave, kvm_xsave};
896+
/// let kvm = Kvm::new().unwrap();
897+
/// let vm = kvm.create_vm().unwrap();
898+
/// let vcpu = vm.create_vcpu(0).unwrap();
899+
/// let xsave_size = vm.check_extension_int(Cap::Xsave2);
900+
/// if xsave_size > 0 {
901+
/// let fam_size = xsave_size as usize - std::mem::size_of::<kvm_xsave>();
902+
/// let mut xsave = Xsave::new(fam_size).unwrap();
903+
/// unsafe { vcpu.get_xsave2(&mut xsave).unwrap() };
904+
/// }
905+
/// ```
906+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
907+
pub unsafe fn get_xsave2(&self, xsave: &mut Xsave) -> Result<()> {
908+
// SAFETY: Safe as long as `xsave` is allocated with enough space to hold the entire "xsave
909+
// struct". That's why this function is unsafe.
910+
let ret = unsafe {
911+
ioctl_with_mut_ref(self, KVM_GET_XSAVE2(), &mut xsave.as_mut_fam_struct().xsave)
912+
};
913+
if ret != 0 {
914+
return Err(errno::Error::last());
915+
}
916+
Ok(())
917+
}
918+
864919
/// X86 specific call that sets the vcpu's current "xsave struct".
865920
///
866921
/// See the documentation for `KVM_SET_XSAVE` in the
@@ -892,6 +947,51 @@ impl VcpuFd {
892947
Ok(())
893948
}
894949

950+
/// Convenience function for doing `KVM_SET_XSAVE` with the FAM-enabled [`Xsave`]
951+
/// instead of the pre-5.17 plain [`kvm_xsave`].
952+
///
953+
/// # Arguments
954+
///
955+
/// * `xsave` - A reference to an [`Xsave`] instance to be set.
956+
///
957+
/// # Safety
958+
///
959+
/// This function is unsafe because there is no guarantee `xsave` is properly allocated with
960+
/// the size that KVM assumes.
961+
///
962+
/// The required size can be retrieved via `KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2)` and can vary
963+
/// depending on features that have been dynamically enabled by `arch_prctl()`. Thus, any
964+
/// features must not be enabled after the required size has been confirmed.
965+
///
966+
/// If `xsave` is not large enough, `KVM_SET_XSAVE` copies data beyond the allocated area to
967+
/// the kernel, possibly causing undefined behavior.
968+
///
969+
/// See the documentation for dynamically enabled XSTATE features in the
970+
/// [kernel doc](https://docs.kernel.org/arch/x86/xstate.html).
971+
///
972+
/// # Example
973+
///
974+
/// ```rust
975+
/// # extern crate kvm_ioctls;
976+
/// # extern crate kvm_bindings;
977+
/// # use kvm_ioctls::{Kvm, Cap};
978+
/// # use kvm_bindings::{Xsave, kvm_xsave};
979+
/// let kvm = Kvm::new().unwrap();
980+
/// let vm = kvm.create_vm().unwrap();
981+
/// let vcpu = vm.create_vcpu(0).unwrap();
982+
/// let xsave_size = vm.check_extension_int(Cap::Xsave2);
983+
/// if xsave_size > 0 {
984+
/// let fam_size = xsave_size as usize - std::mem::size_of::<kvm_xsave>();
985+
/// let xsave = Xsave::new(fam_size).unwrap();
986+
/// // Your `xsave` manipulation here.
987+
/// unsafe { vcpu.set_xsave2(&xsave).unwrap() };
988+
/// }
989+
/// ```
990+
#[cfg(target_arch = "x86_64")]
991+
pub unsafe fn set_xsave2(&self, xsave: &Xsave) -> Result<()> {
992+
self.set_xsave(&xsave.as_fam_struct_ref().xsave)
993+
}
994+
895995
/// X86 specific call that returns the vcpu's current "xcrs".
896996
///
897997
/// See the documentation for `KVM_GET_XCRS` in the
@@ -2228,6 +2328,21 @@ mod tests {
22282328
vcpu.set_xsave(&xsave).unwrap();
22292329
let other_xsave = vcpu.get_xsave().unwrap();
22302330
assert_eq!(&xsave.region[..], &other_xsave.region[..]);
2331+
2332+
let xsave_size = vm.check_extension_int(Cap::Xsave2);
2333+
// only if KVM_CAP_XSAVE2 is supported
2334+
if xsave_size > 0 {
2335+
let fam_size = xsave_size as usize - std::mem::size_of::<kvm_xsave>();
2336+
let mut xsave2 = Xsave::new(fam_size).unwrap();
2337+
// SAFETY: Safe because `xsave2` is allocated with enough space.
2338+
unsafe { vcpu.get_xsave2(&mut xsave2).unwrap() };
2339+
assert_eq!(
2340+
&xsave.region[..],
2341+
&xsave2.as_fam_struct_ref().xsave.region[..]
2342+
);
2343+
// SAFETY: Safe because `xsave2` is allocated with enough space.
2344+
unsafe { vcpu.set_xsave2(&xsave2).unwrap() };
2345+
}
22312346
}
22322347

22332348
#[cfg(target_arch = "x86_64")]

kvm-ioctls/src/kvm_ioctls.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ ioctl_iow_nr!(KVM_SET_DEBUGREGS, KVMIO, 0xa2, kvm_debugregs);
198198
/* Available with KVM_CAP_XSAVE */
199199
#[cfg(target_arch = "x86_64")]
200200
ioctl_ior_nr!(KVM_GET_XSAVE, KVMIO, 0xa4, kvm_xsave);
201+
/* Available with KVM_CAP_XSAVE2 */
202+
#[cfg(target_arch = "x86_64")]
203+
ioctl_ior_nr!(KVM_GET_XSAVE2, KVMIO, 0xcf, kvm_xsave);
201204
/* Available with KVM_CAP_XSAVE */
202205
#[cfg(target_arch = "x86_64")]
203206
ioctl_iow_nr!(KVM_SET_XSAVE, KVMIO, 0xa5, kvm_xsave);

0 commit comments

Comments
 (0)