hypervisor, vmm: Fix aarch64 SVE register save/restore

When SVE is enabled, KVM replaces the FPSIMD V-registers with wider
SVE Z-registers. Attempting to access the old FPSIMD offsets returns
EINVAL.

Fix by classifying each register from KVM_GET_REG_LIST as core, system,
or extended. Extended registers (currently SVE only) are saved as
generic `ExtendedReg` entries split into `pre_finalize_regs` (registers
like SVE VLS that must be written before `vcpu_finalize`) and
`extended_regs`. FPSIMD registers are only accessed when SVE is absent.
Unrecognized register families error immediately so future extensions
like SME fail clearly rather than silently losing state.

The snapshot is deserialized before vCPU init to make pre-finalize
register state available for the init -> VLS -> finalize ordering
required by KVM.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
This commit is contained in:
Ruben Hakobyan
2026-05-21 19:04:20 -07:00
committed by Rob Bradford
parent e390f0bdc1
commit 5c25d82f59
6 changed files with 165 additions and 45 deletions

View File

@@ -11,8 +11,9 @@
pub mod gic;
use kvm_bindings::{
KVM_REG_ARM_COPROC_MASK, KVM_REG_ARM_CORE, KVM_REG_SIZE_MASK, KVM_REG_SIZE_U32,
KVM_REG_SIZE_U64, kvm_mp_state, kvm_one_reg, kvm_regs,
KVM_REG_ARM_COPROC_MASK, KVM_REG_ARM_CORE, KVM_REG_ARM64, KVM_REG_ARM64_SVE, KVM_REG_SIZE_MASK,
KVM_REG_SIZE_SHIFT, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64, KVM_REG_SIZE_U128, KVM_REG_SIZE_U256,
KVM_REG_SIZE_U512, KVM_REG_SIZE_U1024, KVM_REG_SIZE_U2048, kvm_mp_state, kvm_one_reg, kvm_regs,
};
pub use kvm_ioctls::{Cap, Kvm};
use serde::{Deserialize, Serialize};
@@ -70,15 +71,26 @@ pub fn is_system_register(regid: u64) -> bool {
}
let size = regid & KVM_REG_SIZE_MASK;
assert!(
!(size != KVM_REG_SIZE_U32 && size != KVM_REG_SIZE_U64),
"Unexpected register size for system register {size}"
);
true
match size {
KVM_REG_SIZE_U32 | KVM_REG_SIZE_U64 => true,
KVM_REG_SIZE_U128 | KVM_REG_SIZE_U256 | KVM_REG_SIZE_U512 | KVM_REG_SIZE_U1024
| KVM_REG_SIZE_U2048 => false,
_ => unreachable!("Unexpected register size {size:#x} for register id {regid:#x}"),
}
}
pub fn is_sve_register(regid: u64) -> bool {
(regid & KVM_REG_ARM_COPROC_MASK as u64) == KVM_REG_ARM64_SVE as u64
}
pub fn reg_size(regid: u64) -> usize {
let shift = ((regid & KVM_REG_SIZE_MASK) >> KVM_REG_SIZE_SHIFT as u64) as u32;
1usize << shift
}
pub const KVM_ARM64_SVE_VLS_REGID: u64 =
KVM_REG_ARM64 | KVM_REG_SIZE_U512 | KVM_REG_ARM64_SVE as u64 | 0xffff;
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
macro_rules! check_extension {
($cap:expr) => {
@@ -100,9 +112,17 @@ pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
Ok(())
}
pub use crate::arch::aarch64::ExtendedReg;
pub const PRE_FINALIZE_IDS: &[u64] = &[KVM_ARM64_SVE_VLS_REGID];
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct VcpuKvmState {
pub mp_state: kvm_mp_state,
pub core_regs: kvm_regs,
pub sys_regs: Vec<kvm_one_reg>,
#[serde(default)]
pub pre_finalize_regs: Vec<ExtendedReg>,
#[serde(default)]
pub extended_regs: Vec<ExtendedReg>,
}