diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index a53fc4005..45edd336a 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -25,7 +25,7 @@ mod smbios; use std::arch::x86_64; use helpers::{deserialize_u32_hex, serialize_u32_hex}; -use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry}; +use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry, VcpuMsrConfigUpdate}; use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError}; use linux_loader::loader::bootparam::{boot_params, setup_header}; use linux_loader::loader::elf::start_info::{ @@ -185,6 +185,15 @@ pub enum Error { )] CpuProfileMsrIncompatibility, + /// Error when trying to apply a CPU profile because the MSR index list could not be obtained + #[error("The selected CPU profile could not be utilized: failed to obtain MSR index list")] + CpuProfileMsrIndexList(#[source] HypervisorError), + + /// Error when trying to apply a CPU profile because the feature MSRs could not be obtained + /// from the hypervisor. + #[error("The selected CPU profile could not be utilized: failed to obtain feature MSRs")] + CpuProfileFeatureMsrs(#[source] HypervisorError), + // Error writing EBDA address #[error("Error writing EBDA address")] EbdaSetup(#[source] vm_memory::GuestMemoryError), @@ -715,6 +724,61 @@ pub fn generate_common_cpuid( } } +/// Generates the MSR related updates that are required in order to be compatible with the given CPU profile (if any). +/// +/// An error is returned if any MSR required by the given CPU profile is not reported by the hypervisor. If +/// the `kvm_hyperv` parameter is `false` then Hyper-V related MSRs are exempt from this check. +/// +/// If nested is `false` then feature MSRs describing virtualization capabilities will not be considered part of +/// the CPU profile. +/// +/// ## Feature MSRs compatibility +/// +/// The hypervisor is expected to reject setting feature MSRs that are incompatible with the host. This is why this +/// function does **NOT perform such compatibility checks** itself. +/// +/// The one and only exception to this rule is the IA32_ARCH_CAPABILITIES MSR. This function will check that the +/// host is compatible with the CPU profile's value for that MSR when present as KVM permits setting it regardless +/// of what the host actually supports. +pub fn generate_required_msr_updates( + hypervisor: &dyn hypervisor::Hypervisor, + cpu_profile: CpuProfile, + kvm_hyperv: bool, + nested: bool, +) -> super::Result> { + if matches!(cpu_profile, CpuProfile::Host) { + return Ok(None); + } + + let host_supported_msrs = hypervisor + .get_msr_index_list() + .map_err(Error::CpuProfileMsrIndexList)?; + + let host_feature_msrs = hypervisor + .get_feature_msrs() + .map_err(Error::CpuProfileFeatureMsrs)?; + + let required_updates = cpu_profile.required_msr_updates( + &host_feature_msrs, + &host_supported_msrs, + kvm_hyperv, + nested, + )?; + + if let Some(required_updates) = &required_updates { + // If IA32_ARCH_CAPABILITIES is present then we need to check for compatibility. + // This MSR is only available on Intel CPUs as of now, but we do not check for vendor equality here. + // + // TODO: Consider adding a CPU vendor equality requirement to `check_cpuid_compatibility` even though + // it is extremely unlikely that CPUs from different vendors will pass the existing checks. + arch_capabilities_checks::valid_required_arch_capabilities_update( + required_updates, + &host_feature_msrs, + )?; + } + Ok(required_updates) +} + /// Apply updates to common CPUID (not vCPU specific) that are necessary regardless of /// the chosen CPU profile. fn required_common_cpuid_updates(