Skip to content

Commit 02b62c8

Browse files
committed
kvm-ioctls: Add KvmNestedStateBuffer helper
This type is a helper making the use of get_nested_state() and set_nested_state(), which are added in the next commit, much more convenient. Effectively, KVM expects a dynamic buffer with a header reporting the size to either store the nested state in or load it from. As such data structures with a certain alignment are challenging to work with (note that Vec<u8> always have an alignment of 1 but we need 4), this type sacrifices a little memory in some cases overhead for better UX. As the type implements AsRef<[u8]> and AsMut<[u8]>, which will also be used as the parameter for get_nested_state() and set_nested_state(), users will be able to use custom buffers tailored to their use case, e.g., the SVM state is smaller than the VMX state. They then will also be able to pass in types that are compatible with `serde`. Signed-off-by: Philipp Schuster <[email protected]> On-behalf-of: SAP [email protected]
1 parent 7cf1f15 commit 02b62c8

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

kvm-ioctls/src/ioctls/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use vmm_sys_util::errno;
1616

1717
/// Wrappers over KVM device ioctls.
1818
pub mod device;
19+
#[cfg(target_arch = "x86_64")]
20+
pub mod nested_state;
1921
/// Wrappers over KVM system ioctls.
2022
pub mod system;
2123
/// Wrappers over KVM VCPU ioctls.

kvm-ioctls/src/ioctls/nested_state.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright © 2025 Cyberus Technology GmbH
2+
//
3+
// SPDX-License-Identifier: Apache-2.0 OR MIT
4+
5+
//! Module for working with nested KVM state.
6+
//!
7+
//! Getting and setting the nested KVM state is helpful if nested virtualization
8+
//! is used and the state needs to be serialized, e.g., for live-migration or
9+
//! state save/resume. The main export is [`KvmNestedStateBuffer`].
10+
11+
use kvm_bindings::{kvm_nested_state, kvm_svm_nested_state_data, kvm_vmx_nested_state_data};
12+
use std::fmt::{Debug, Formatter};
13+
use std::{cmp, mem};
14+
15+
/// Helper type for [`KvmNestedStateBuffer`] unifying the actual state according
16+
/// to KVM bindings.
17+
///
18+
/// Please note that on SVM, this type wastes one page as the VMX state is
19+
/// larger.
20+
#[cfg(target_arch = "x86_64")]
21+
#[repr(C)]
22+
#[derive(Copy, Clone)]
23+
pub(crate) union KvmNestedStateData {
24+
pub vmx_state: kvm_vmx_nested_state_data,
25+
pub svm_state: kvm_svm_nested_state_data,
26+
}
27+
28+
impl Debug for KvmNestedStateData {
29+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30+
write!(f, "KvmNestedStateData({} bytes)", size_of::<Self>())
31+
}
32+
}
33+
34+
/// A stack-allocated buffer for nested KVM state.
35+
///
36+
/// KVM uses a dynamically sized buffer structure (with a header reporting the
37+
/// size of the buffer/state) making it cumbersome to work with. This helper
38+
/// type makes working with [`get_nested_state`] and [`set_nested_state`]
39+
/// significantly more convenient at the cost of a slightly higher memory
40+
/// footprint in some cases. Unlike the fixed header [`kvm_nested_state`],
41+
/// this type acts as a stack buffer capable of holding all possible state.
42+
///
43+
/// The implementations for [`AsRef<[u8]>`] and [`AsMut<[u8]>`] refer to the
44+
/// entire buffer. To only get the state size as reported by the header, use
45+
/// [`Self::as_raw_state`].
46+
///
47+
/// # Type Size
48+
///
49+
/// On Intel VMX, the actual state requires `128 + 8192 == 8320` bytes, on
50+
/// AMD SVM, the actual state requires `128 + 4096 == 4224` bytes. This type
51+
/// doesn't make a differentiation and unifies the required memory. By
52+
/// sacrificing a few more bytes on VMX, this type is more convenient to use.
53+
///
54+
/// [`get_nested_state`]: crate::VcpuFd::get_nested_state
55+
/// [`set_nested_state`]: crate::VcpuFd::set_nested_state
56+
#[repr(C)]
57+
#[derive(Debug)]
58+
pub struct KvmNestedStateBuffer {
59+
/// The fixed header from the KVM binding.
60+
pub(crate) header: kvm_nested_state,
61+
/// The actual payload.
62+
pub(crate) data: KvmNestedStateData,
63+
}
64+
65+
impl KvmNestedStateBuffer {
66+
/// Creates a new type which acts as empty ready-to-use buffer for
67+
/// [`get_nested_state`].
68+
///
69+
/// [`get_nested_state`]: crate::VcpuFd::get_nested_state
70+
pub fn new_empty() -> Self {
71+
// SAFETY: properly initialized `u8` values to zero
72+
let mut base = unsafe { mem::zeroed::<KvmNestedStateBuffer>() };
73+
// This is a sane default as KVM uses this field to know how many bytes
74+
// are allocated for it.
75+
base.header.size = size_of::<Self>() as _;
76+
base
77+
}
78+
79+
/// Returns the actual complete raw state, suited for serialization.
80+
///
81+
/// This is useful to serialize the actual state without wasting unused
82+
/// buffer capacity on SVM. On VMX, this is as good as [`Self::as_ref::<[u8]>`]
83+
///
84+
/// This is supposed to be called **after** [`Vcpu::get_nested_state`]
85+
/// properly stored the actual state into the buffer. Then, when the
86+
/// header is properly set, this method also respects the length as
87+
/// reported by the header, effectively not returning unused memory
88+
/// for VMX.
89+
pub fn as_actual_raw_state(&self) -> &[u8] {
90+
let len = self.header.size as usize;
91+
92+
// Ensure there is at least the header.
93+
// It is okay if this is empty
94+
// -> When no nested nirtualization was used.
95+
assert!(len >= size_of::<kvm_nested_state>());
96+
// Ensure there is no invalid size
97+
assert!(len <= size_of::<Self>());
98+
99+
unsafe {
100+
let ptr = core::ptr::addr_of!(*self);
101+
core::slice::from_raw_parts(ptr.cast::<u8>(), len)
102+
}
103+
}
104+
}
105+
106+
impl Clone for KvmNestedStateBuffer {
107+
fn clone(&self) -> Self {
108+
let mut header_clone: kvm_nested_state = unsafe { mem::zeroed() };
109+
// SAFETY: header is initialized and sized
110+
unsafe { core::ptr::copy_nonoverlapping(&self.header, &mut header_clone, 1) };
111+
Self {
112+
header: header_clone,
113+
data: self.data.clone(),
114+
}
115+
}
116+
}
117+
118+
#[cfg(target_arch = "x86_64")]
119+
impl Default for KvmNestedStateBuffer {
120+
fn default() -> Self {
121+
Self::new_empty()
122+
}
123+
}
124+
125+
impl AsRef<[u8]> for KvmNestedStateBuffer {
126+
fn as_ref(&self) -> &[u8] {
127+
let ptr = core::ptr::addr_of!(*self);
128+
let len = cmp::min(size_of::<Self>(), self.header.size as usize);
129+
// SAFETY: The reference is initialized and we checked the length.
130+
unsafe { core::slice::from_raw_parts(ptr.cast::<u8>(), len) }
131+
}
132+
}
133+
134+
impl AsMut<[u8]> for KvmNestedStateBuffer {
135+
fn as_mut(&mut self) -> &mut [u8] {
136+
let ptr = core::ptr::addr_of_mut!(*self);
137+
let len = cmp::min(size_of::<Self>(), self.header.size as usize);
138+
// SAFETY: The reference is initialized and we checked the length.
139+
unsafe { core::slice::from_raw_parts_mut(ptr.cast::<u8>(), len) }
140+
}
141+
}

kvm-ioctls/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ pub use ioctls::system::Kvm;
248248
pub use ioctls::vcpu::reg_size;
249249
pub use ioctls::vcpu::{HypercallExit, VcpuExit, VcpuFd};
250250

251+
#[cfg(target_arch = "x86_64")]
252+
pub use ioctls::nested_state::KvmNestedStateBuffer;
253+
251254
#[cfg(target_arch = "x86_64")]
252255
pub use ioctls::vcpu::{MsrExitReason, ReadMsrExit, SyncReg, WriteMsrExit};
253256

0 commit comments

Comments
 (0)