hypervisor: kvm/x86_64: get and set nested guest state

Since the functionality of [0] was backported to
kvm-bindings@v0.12.1 and kvm-ioctls@v0.22.1 [1, 2], we
can now save nested KVM state. This way, nesting works across
state save/resume and live-migration.

[0] https://github.com/rust-vmm/kvm/pull/322
[1] https://github.com/rust-vmm/kvm/pull/349
[2] https://github.com/rust-vmm/kvm/pull/350

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
This commit is contained in:
Philipp Schuster 2025-06-24 10:31:37 +02:00 committed by Rob Bradford
parent 4c16285dde
commit 690741de13
6 changed files with 57 additions and 6 deletions

8
Cargo.lock generated
View file

@ -1073,9 +1073,9 @@ dependencies = [
[[package]]
name = "kvm-bindings"
version = "0.12.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4b153a59bb3ca930ff8148655b2ef68c34259a623ae08cf2fb9b570b2e45363"
checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9"
dependencies = [
"serde",
"vmm-sys-util",
@ -1084,9 +1084,9 @@ dependencies = [
[[package]]
name = "kvm-ioctls"
version = "0.22.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b702df98508cb63ad89dd9beb9f6409761b30edca10d48e57941d3f11513a006"
checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d"
dependencies = [
"bitflags 2.9.4",
"kvm-bindings",

View file

@ -107,8 +107,8 @@ package.edition = "2024"
[workspace.dependencies]
# rust-vmm crates
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
kvm-bindings = "0.12.0"
kvm-ioctls = "0.22.0"
kvm-bindings = "0.12.1"
kvm-ioctls = "0.22.1"
# TODO: update to 0.13.1+
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" }
mshv-bindings = "0.6.0"

View file

@ -334,6 +334,10 @@ pub enum HypervisorCpuError {
///
#[error("Failed to inject NMI")]
Nmi(#[source] anyhow::Error),
#[error("Failed to get nested guest state")]
GetNestedState(#[source] anyhow::Error),
#[error("Failed to set nested guest state")]
SetNestedState(#[source] anyhow::Error),
}
#[derive(Debug)]

View file

@ -86,6 +86,8 @@ use std::mem;
///
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub use kvm_bindings::kvm_vcpu_events as VcpuEvents;
#[cfg(target_arch = "x86_64")]
use kvm_bindings::nested::KvmNestedStateBuffer;
pub use kvm_bindings::{
KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI,
KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, kvm_clock_data,
@ -2442,6 +2444,7 @@ impl cpu::Vcpu for KvmVcpu {
let xcrs = self.get_xcrs()?;
let lapic_state = self.get_lapic()?;
let fpu = self.get_fpu()?;
let nested_state = self.nested_state()?;
// Try to get all MSRs based on the list previously retrieved from KVM.
// If the number of MSRs obtained from GET_MSRS is different from the
@ -2516,6 +2519,7 @@ impl cpu::Vcpu for KvmVcpu {
xcrs,
mp_state,
tsc_khz,
nested_state,
}
.into())
}
@ -2683,6 +2687,9 @@ impl cpu::Vcpu for KvmVcpu {
self.set_xcrs(&state.xcrs)?;
self.set_lapic(&state.lapic_state)?;
self.set_fpu(&state.fpu)?;
if let Some(nested_state) = state.nested_state {
self.set_nested_state(&nested_state)?;
}
if let Some(freq) = state.tsc_khz {
self.set_tsc_khz(freq)?;
@ -3036,6 +3043,36 @@ impl KvmVcpu {
.set_vcpu_events(events)
.map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into()))
}
/// Get the state of the nested guest from the current vCPU,
/// if there is any.
#[cfg(target_arch = "x86_64")]
fn nested_state(&self) -> cpu::Result<Option<KvmNestedStateBuffer>> {
let mut buffer = KvmNestedStateBuffer::empty();
let maybe_size = self
.fd
.lock()
.unwrap()
.get_nested_state(&mut buffer)
.map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into()))?;
if let Some(_size) = maybe_size {
Ok(Some(buffer))
} else {
Ok(None)
}
}
/// Sets the state of the nested guest for the current vCPU.
#[cfg(target_arch = "x86_64")]
fn set_nested_state(&self, state: &KvmNestedStateBuffer) -> cpu::Result<()> {
self.fd
.lock()
.unwrap()
.set_nested_state(state)
.map_err(|e| cpu::HypervisorCpuError::GetNestedState(e.into()))
}
}
#[cfg(test)]

View file

@ -19,6 +19,7 @@ pub use {
kvm_bindings::kvm_msr_entry, kvm_bindings::kvm_regs, kvm_bindings::kvm_segment,
kvm_bindings::kvm_sregs, kvm_bindings::kvm_vcpu_events as VcpuEvents,
kvm_bindings::kvm_xcrs as ExtendedControlRegisters, kvm_bindings::kvm_xsave,
kvm_bindings::nested::KvmNestedStateBuffer,
};
use crate::arch::x86::{
@ -75,6 +76,9 @@ pub struct VcpuKvmState {
pub xcrs: ExtendedControlRegisters,
pub mp_state: MpState,
pub tsc_khz: Option<u32>,
// Option to prevent useless 8K (de)serialization when no nested
// state exists.
pub nested_state: Option<KvmNestedStateBuffer>,
}
impl From<SegmentRegister> for kvm_segment {

View file

@ -103,6 +103,8 @@ mod kvm {
pub const KVM_GET_REG_LIST: u64 = 0xc008_aeb0;
pub const KVM_MEMORY_ENCRYPT_OP: u64 = 0xc008_aeba;
pub const KVM_NMI: u64 = 0xae9a;
pub const KVM_GET_NESTED_STATE: u64 = 3229658814;
pub const KVM_SET_NESTED_STATE: u64 = 1082175167;
}
// MSHV IOCTL code. This is unstable until the kernel code has been declared stable.
@ -232,6 +234,8 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result<Vec<SeccompRule>, Backen
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_VCPU_EVENTS,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?],
])
}
@ -697,6 +701,8 @@ fn create_vcpu_ioctl_seccomp_rule_kvm() -> Result<Vec<SeccompRule>, BackendError
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_RUN,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?],
])
}