From 901aae165cae98d882c958e2a12ce2638962cb89 Mon Sep 17 00:00:00 2001
From: Patrick Roy <roypat@amazon.co.uk>
Date: Thu, 7 Mar 2024 10:55:55 +0000
Subject: [PATCH] Support treating kvm_xsave as a FamStruct

In linux 5.17, kvm_xsave got turned into a FamStruct by adding the
flexible "extra" member to its definition. However, unlike all other
such structs, it does not contain a "length" field. Instead, the length
of the flexible array member has to be determined by querying the
`KVM_CAP_XSAVE2` capability. This requires access to a VM file
descriptor, and thus cannot happen in the `FamStruct::len` trait method.
To work around this, define a wrapper struct that caches the length of a
previous `KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2)` call, and implement
`FamStruct` for this wrapper. Then in kvm-ioctls, we can expose a
function that first query `KVM_CAP_XSAVE2`, then invokes
`KVM_GET_XSAVE2` to retrives the `kvm_xsave` structure, and then combine
them into the below `kvm_xsave2` structure to be managed as a
`FamStruct`.

Signed-off-by: Patrick Roy <roypat@amazon.co.uk>
---
 src/x86_64/fam_wrappers.rs | 53 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/src/x86_64/fam_wrappers.rs b/src/x86_64/fam_wrappers.rs
index 032879d..9307df4 100644
--- a/src/x86_64/fam_wrappers.rs
+++ b/src/x86_64/fam_wrappers.rs
@@ -94,6 +94,59 @@ impl PartialEq for kvm_msr_list {
 /// [FamStructWrapper](../vmm_sys_util/fam/struct.FamStructWrapper.html).
 pub type MsrList = FamStructWrapper<kvm_msr_list>;
 
+// In linux 5.17, kvm_xsave got turned into a FamStruct by adding the flexible "extra" member
+// to its definition. However, unlike all other such structs, it does not contain a "length"
+// field. Instead, the length of the flexible array member has to be determined by querying
+// the `KVM_CAP_XSAVE2` capability. This requires access to a VM file descriptor, and thus
+// cannot happen in the `FamStruct::len` trait method. To work around this, define a wrapper
+// struct that caches the length of a previous `KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2)` call,
+// and implement `FamStruct` for this wrapper. Then in kvm-ioctls, we can expose a function
+// that first query `KVM_CAP_XSAVE2`, then invokes `KVM_GET_XSAVE2` to retrives the `kvm_xsave`
+// structure, and then combine them into the below `kvm_xsave2` structure to be managed as a
+// `FamStruct`.
+#[repr(C)]
+pub struct kvm_xsave2 {
+    pub len: usize,
+    pub xsave: kvm_xsave,
+}
+
+// SAFETY:
+// - `kvm_xsave2` is a POD
+// - `kvm_xsave2` contains a flexible array member as its final field, due to `kvm_xsave` containing
+//    one, and being `repr(C)`
+// - `Entry` is a POD
+unsafe impl FamStruct for kvm_xsave2 {
+    type Entry = __u32;
+
+    fn len(&self) -> usize {
+        self.len
+    }
+
+    unsafe fn set_len(&mut self, len: usize) {
+        self.len = len;
+    }
+
+    fn max_len() -> usize {
+        __u32::MAX as usize
+    }
+
+    fn as_slice(&self) -> &[<Self as FamStruct>::Entry] {
+        let len = self.len();
+        // SAFETY: By the invariants that the caller of `set_len` has to uphold, `len` matches
+        // the actual in-memory length of the FAM
+        unsafe { self.xsave.extra.as_slice(len) }
+    }
+
+    fn as_mut_slice(&mut self) -> &mut [<Self as FamStruct>::Entry] {
+        let len = self.len();
+        // SAFETY: By the invariants that the caller of `set_len` has to uphold, `len` matches
+        // the actual in-memory length of the FAM
+        unsafe { self.xsave.extra.as_mut_slice(len) }
+    }
+}
+
+pub type Xsave = FamStructWrapper<kvm_xsave2>;
+
 #[cfg(test)]
 mod tests {
     use super::{CpuId, MsrList, Msrs};