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 <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
This commit is contained in:
Oliver Anderson
2026-07-22 18:06:11 +02:00
committed by Rob Bradford
parent 2aff169533
commit 6ea8e8e20e
6 changed files with 65 additions and 18 deletions

View File

@@ -340,6 +340,12 @@ pub struct MsrEntry {
pub data: u64,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct VcpuMsrConfigUpdate {
pub feature_msrs: Vec<MsrEntry>,
pub snapshottable_msr_indices: Vec<u32>,
}
#[serde_with::serde_as]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct XsaveState {

View File

@@ -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<Arc<dyn VmOps>>,
#[cfg(target_arch = "x86_64")] msr_config_update: Option<VcpuMsrConfigUpdate>,
) -> vm::Result<Box<dyn cpu::Vcpu>> {
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<MsrEntry>,
#[cfg(target_arch = "x86_64")]
feature_msrs: Vec<MsrEntry>,
vm_ops: Option<Arc<dyn vm::VmOps>>,
#[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<CpuState> {
@@ -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();
/// ```

View File

@@ -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<Arc<dyn VmOps>>,
#[cfg(target_arch = "x86_64")] _msr_config_update: Option<VcpuMsrConfigUpdate>,
) -> vm::Result<Box<dyn cpu::Vcpu>> {
let id: u8 = id.try_into().unwrap();
let vcpu_fd = self

View File

@@ -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<Arc<dyn VmOps>>) -> Result<Box<dyn Vcpu>>;
fn create_vcpu(
&self,
id: u32,
vm_ops: Option<Arc<dyn VmOps>>,
#[cfg(target_arch = "x86_64")] msr_config_update: Option<VcpuMsrConfigUpdate>,
) -> Result<Box<dyn Vcpu>>;
#[cfg(target_arch = "aarch64")]
fn create_vgic(&self, config: &VgicConfig) -> Result<Arc<Mutex<dyn Vgic>>>;
#[cfg(target_arch = "riscv64")]

View File

@@ -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<Arc<dyn VmOps>>,
#[cfg(target_arch = "x86_64")] cpu_vendor: CpuVendor,
#[cfg(target_arch = "x86_64")] msr_config_update: Option<VcpuMsrConfigUpdate>,
) -> Result<Self> {
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);

View File

@@ -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;