From 6ea8e8e20ea46525e17d4eedcf628e8d92a89fc5 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 22 Jul 2026 18:06:11 +0200 Subject: [PATCH] hypervisor: Optional vCPU MSR configuration update in vcpu constructor When applying a CPU profile we need a way to change the configuration of each vCPU to respect the requirements of the CPU profile. This means that we need to set the feature MSRs in accordance with the CPU profile upon configuring the vCPU and also ensuring that we do not attempt to restore any MSRs that are not compatible with the profile upon snapshot/restore. The first step is to update `Vm::create_vcpu` to take an extra parameter describing the necessary update. In the case of KVM we modify the internal MSR state buffer when constructing the vCPU whenever a VcpuMsrConfigUpdate is present. The feature MSRs contained in the configuration will be treated in follow up commits. The changes to the vmm crate that are part of this commit are just the minimum necessary to make the crate compile. We will update the vmm crate to take CPU profiles into account in a follow up commit. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- hypervisor/src/arch/x86/mod.rs | 6 ++++++ hypervisor/src/kvm/mod.rs | 30 +++++++++++++++++++++++++----- hypervisor/src/mshv/mod.rs | 5 +++-- hypervisor/src/vm.rs | 9 ++++++++- vmm/src/cpu.rs | 25 +++++++++++++++++-------- vmm/src/vm.rs | 8 ++++++-- 6 files changed, 65 insertions(+), 18 deletions(-) diff --git a/hypervisor/src/arch/x86/mod.rs b/hypervisor/src/arch/x86/mod.rs index 8ff396121..dc7244c44 100644 --- a/hypervisor/src/arch/x86/mod.rs +++ b/hypervisor/src/arch/x86/mod.rs @@ -340,6 +340,12 @@ pub struct MsrEntry { pub data: u64, } +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct VcpuMsrConfigUpdate { + pub feature_msrs: Vec, + pub snapshottable_msr_indices: Vec, +} + #[serde_with::serde_as] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct XsaveState { diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index fe393d5d8..70b4423c2 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -95,7 +95,7 @@ use crate::ClockData; #[cfg(target_arch = "x86_64")] use crate::arch::x86::{ CpuIdEntry, FpuState, LapicState, MTRR_MSR_INDICES, MsrEntry, NUM_IOAPIC_PINS, - SpecialRegisters, XsaveState, + SpecialRegisters, VcpuMsrConfigUpdate, XsaveState, }; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] use crate::{ClockRestoreMode, ClockState}; @@ -886,6 +886,7 @@ impl vm::Vm for KvmVm { &self, id: u32, vm_ops: Option>, + #[cfg(target_arch = "x86_64")] msr_config_update: Option, ) -> vm::Result> { let fd = self .fd @@ -911,6 +912,21 @@ impl vm::Vm for KvmVm { })?; } + #[cfg(target_arch = "x86_64")] + let (feature_msrs, msrs) = msr_config_update.map_or_else( + || (Vec::new(), self.msrs.clone()), + |update| { + ( + update.feature_msrs, + update + .snapshottable_msr_indices + .into_iter() + .map(|index| MsrEntry { index, data: 0 }) + .collect(), + ) + }, + ); + #[cfg(target_arch = "x86_64")] // Safety: `xsave_size` will not change after vcpu creation because: // 1. `xsave_size` depends on cpuid @@ -924,7 +940,9 @@ impl vm::Vm for KvmVm { let vcpu = KvmVcpu { fd, #[cfg(target_arch = "x86_64")] - msrs: self.msrs.clone(), + msrs, + #[cfg(target_arch = "x86_64")] + feature_msrs, vm_ops, #[cfg(target_arch = "x86_64")] hyperv_synic: AtomicBool::new(false), @@ -1921,6 +1939,8 @@ pub struct KvmVcpu { fd: VcpuFd, #[cfg(target_arch = "x86_64")] msrs: Vec, + #[cfg(target_arch = "x86_64")] + feature_msrs: Vec, vm_ops: Option>, #[cfg(target_arch = "x86_64")] hyperv_synic: AtomicBool, @@ -2011,7 +2031,7 @@ impl KvmVcpu { /// let kvm = KvmHypervisor::new().unwrap(); /// let hypervisor = Arc::new(kvm); /// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); -/// let vcpu = vm.create_vcpu(0, None).unwrap(); +/// let vcpu = vm.create_vcpu(0, None, #[cfg(target_arch = "x86_64")] Default::default()).unwrap(); /// ``` impl cpu::Vcpu for KvmVcpu { /// @@ -3068,7 +3088,7 @@ impl cpu::Vcpu for KvmVcpu { /// let hv = Arc::new(kvm); /// let vm = hv.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// vm.enable_split_irq().unwrap(); - /// let vcpu = vm.create_vcpu(0, None).unwrap(); + /// let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); /// let state = vcpu.state().unwrap(); /// ``` fn state(&self) -> cpu::Result { @@ -3333,7 +3353,7 @@ impl cpu::Vcpu for KvmVcpu { /// let hv = Arc::new(kvm); /// let vm = hv.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); /// vm.enable_split_irq().unwrap(); - /// let vcpu = vm.create_vcpu(0, None).unwrap(); + /// let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); /// let state = vcpu.state().unwrap(); /// vcpu.set_state(&state).unwrap(); /// ``` diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index d1b8e4c7c..c861af14f 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -42,7 +42,7 @@ use crate::arch::emulator::PlatformEmulator; #[cfg(target_arch = "x86_64")] use crate::arch::x86::emulator::Emulator; #[cfg(target_arch = "x86_64")] -use crate::arch::x86::{LapicState, SpecialRegisters}; +use crate::arch::x86::{LapicState, SpecialRegisters, VcpuMsrConfigUpdate}; #[cfg(target_arch = "aarch64")] use crate::mshv::aarch64::emulator; use crate::mshv::emulator::MshvEmulatorContext; @@ -481,7 +481,7 @@ pub struct MshvVcpu { /// let mshv = MshvHypervisor::new().unwrap(); /// let hypervisor = Arc::new(mshv); /// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed"); -/// let vcpu = vm.create_vcpu(0, None).unwrap(); +/// let vcpu = vm.create_vcpu(0, None, #[cfg(target_arch = "x86_64")] Default::default()).unwrap(); /// ``` impl cpu::Vcpu for MshvVcpu { /// @@ -1896,6 +1896,7 @@ impl vm::Vm for MshvVm { &self, id: u32, vm_ops: Option>, + #[cfg(target_arch = "x86_64")] _msr_config_update: Option, ) -> vm::Result> { let id: u8 = id.try_into().unwrap(); let vcpu_fd = self diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 9c78c3b00..980ceddd5 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -33,6 +33,8 @@ use crate::arch::aarch64::gic::{Vgic, VgicConfig}; use crate::arch::riscv64::aia::{Vaia, VaiaConfig}; #[cfg(feature = "tdx")] use crate::arch::x86::CpuIdEntry; +#[cfg(target_arch = "x86_64")] +use crate::arch::x86::VcpuMsrConfigUpdate; use crate::cpu::Vcpu; use crate::{ClockRestoreMode, ClockState, IoEventAddress, IrqRoutingEntry}; @@ -347,7 +349,12 @@ pub trait Vm: Send + Sync + Any { /// Unregister an event that will, when signaled, trigger the `gsi` IRQ. fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>; /// Creates a new KVM vCPU file descriptor and maps the memory corresponding - fn create_vcpu(&self, id: u32, vm_ops: Option>) -> Result>; + fn create_vcpu( + &self, + id: u32, + vm_ops: Option>, + #[cfg(target_arch = "x86_64")] msr_config_update: Option, + ) -> Result>; #[cfg(target_arch = "aarch64")] fn create_vgic(&self, config: &VgicConfig) -> Result>>; #[cfg(target_arch = "riscv64")] diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 98f4e9325..21301e9fe 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -45,14 +45,14 @@ use hypervisor::StandardRegisters; use hypervisor::arch::aarch64::gic::Vgic; #[cfg(all(target_arch = "aarch64", feature = "guest_debug"))] use hypervisor::arch::aarch64::regs::{ID_AA64MMFR0_EL1, TCR_EL1, TTBR1_EL1}; -#[cfg(target_arch = "x86_64")] -use hypervisor::arch::x86::CpuIdEntry; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use hypervisor::arch::x86::MsrEntry; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use hypervisor::arch::x86::SpecialRegisters; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use hypervisor::arch::x86::msr_index; +#[cfg(target_arch = "x86_64")] +use hypervisor::arch::x86::{CpuIdEntry, VcpuMsrConfigUpdate}; #[cfg(feature = "tdx")] use hypervisor::kvm::{TdxExitDetails, TdxExitStatus}; #[cfg(feature = "mshv")] @@ -504,15 +504,22 @@ impl Vcpu { /// * `vm` - The virtual machine this vcpu will get attached to. /// * `vm_ops` - Optional object for exit handling. /// * `cpu_vendor` - CPU vendor as reported by __cpuid(0x0) + /// * `msr_config_update` - An optional update to MSR configuration. pub fn new( id: u32, apic_id: u32, vm: &dyn hypervisor::Vm, vm_ops: Option>, #[cfg(target_arch = "x86_64")] cpu_vendor: CpuVendor, + #[cfg(target_arch = "x86_64")] msr_config_update: Option, ) -> Result { let vcpu = vm - .create_vcpu(apic_id, vm_ops) + .create_vcpu( + apic_id, + vm_ops, + #[cfg(target_arch = "x86_64")] + msr_config_update, + ) .map_err(|e| Error::VcpuCreate(e.into()))?; // Initially the cpuid per vCPU is the one supported by this VM. Ok(Vcpu { @@ -978,6 +985,8 @@ impl CpuManager { Some(self.vm_ops.clone()), #[cfg(target_arch = "x86_64")] self.hypervisor.get_cpu_vendor(), + #[cfg(target_arch = "x86_64")] + Default::default(), )?; if let Some(snapshot) = snapshot { @@ -3439,7 +3448,7 @@ mod unit_tests { hv.check_required_extensions().unwrap(); // Calling get_lapic will fail if there is no irqchip before hand. vm.create_irq_chip().unwrap(); - let vcpu = vm.create_vcpu(0, None).unwrap(); + let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); let klapic_before: LapicState = vcpu.get_lapic().unwrap(); // Compute the value that is expected to represent LVT0 and LVT1. @@ -3464,7 +3473,7 @@ mod unit_tests { let vm = hv .create_vm(HypervisorVmConfig::default()) .expect("new VM fd creation failed"); - let vcpu = vm.create_vcpu(0, None).unwrap(); + let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); setup_fpu(vcpu.as_ref()).unwrap(); let expected_fpu: FpuState = FpuState { @@ -3490,7 +3499,7 @@ mod unit_tests { let vm = hv .create_vm(HypervisorVmConfig::default()) .expect("new VM fd creation failed"); - let vcpu = vm.create_vcpu(0, None).unwrap(); + let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); setup_msrs(vcpu.as_ref()).unwrap(); // This test will check against the last MSR entry configured (the tenth one). @@ -3518,7 +3527,7 @@ mod unit_tests { let vm = hv .create_vm(HypervisorVmConfig::default()) .expect("new VM fd creation failed"); - let vcpu = vm.create_vcpu(0, None).unwrap(); + let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); let mut expected_regs: StandardRegisters = vcpu.create_standard_regs(); expected_regs.set_rflags(0x0000000000000002u64); @@ -3544,7 +3553,7 @@ mod unit_tests { let vm = hv .create_vm(HypervisorVmConfig::default()) .expect("new VM fd creation failed"); - let vcpu = vm.create_vcpu(0, None).unwrap(); + let vcpu = vm.create_vcpu(0, None, Default::default()).unwrap(); let mut expected_regs: StandardRegisters = vcpu.create_standard_regs(); expected_regs.set_rflags(0x0000000000000002u64); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 63a62d8a1..12a9fe0ad 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -3935,7 +3935,9 @@ mod unit_tests { mem.write_slice(&code, load_addr) .expect("Writing code to memory failed"); - let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); + let mut vcpu = vm + .create_vcpu(0, None, Default::default()) + .expect("new Vcpu failed"); let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed"); vcpu_sregs.cs.base = 0; @@ -4073,7 +4075,9 @@ pub fn test_vm() { mem.write_slice(&code, load_addr) .expect("Writing code to memory failed"); - let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed"); + let mut vcpu = vm + .create_vcpu(0, None, Default::default()) + .expect("new Vcpu failed"); let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed"); vcpu_sregs.cs.base = 0;