diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 12a05cdb0..d374b897e 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -37,6 +37,7 @@ use vm_memory::{ GuestMemoryRegion, }; +use crate::x86_64::cpu_profile::cpuid_adjustments::MissingCpuidEntriesError; use crate::{CpuProfile, GuestMemoryMmap, InitramfsConfig, RegionType}; // While modern architectures support more than 255 CPUs via x2APIC, @@ -152,6 +153,22 @@ pub enum Error { #[error("Error checking CPUID compatibility")] CpuidCheckCompatibility, + /// Error checking if CPUID is compatible with profile + #[error( + "The selected CPU profile cannot be utilized because the host's CPUID entries are not compatible with the profile" + )] + CpuProfileCpuidIncompatibility, + + /// Error because TDX cannot be enabled when a custom (non host) CPU profile has been selected + #[error("TDX cannot be enabled when a custom CPU profile has been selected")] + CpuProfileTdxIncompatibility, + + /// Error when trying to apply a CPU profile because a necessary CPUID entry was not found + #[error( + "The selected CPU profile cannot be utilized because a necessary CPUID entry was not found" + )] + MissingExpectedCpuidEntry(#[source] MissingCpuidEntriesError), + // Error writing EBDA address #[error("Error writing EBDA address")] EbdaSetup(#[source] vm_memory::GuestMemoryError), @@ -578,6 +595,15 @@ impl CpuidFeatureEntry { } } +/// Generate the CPUID entries intended for every vCPU. +/// +/// ## CPU profiles +/// +/// This function takes the CPU profile given in `config` into account and returns compatible CPUID entries +/// if possible. +/// +/// An error is returned when the CPUID entries obtained from the hypervisor do not satisfy the requirements +/// to apply the selected CPU profile. pub fn generate_common_cpuid( hypervisor: &dyn hypervisor::Hypervisor, config: &CpuidConfig, @@ -603,6 +629,83 @@ pub fn generate_common_cpuid( "Generating guest CPUID for with physical address size: {}", config.phys_bits ); + + // Supported CPUID + let mut cpuid = hypervisor + .get_supported_cpuid() + .map_err(Error::CpuidGetSupported)?; + + let is_non_host_profile = !matches!(config.profile, CpuProfile::Host); + #[cfg(feature = "tdx")] + if config.tdx { + if is_non_host_profile { + // TDX is not supported by CPU profiles other than host for the time being. + return Err(Error::CpuProfileTdxIncompatibility.into()); + } + common_cpuid_tdx_configuration(&mut cpuid, hypervisor)?; + } + + // Copy CPU identification string + for i in 0x8000_0002..=0x8000_0004 { + cpuid.retain(|c| c.function != i); + // SAFETY: call cpuid with valid leaves + #[allow(unused_unsafe)] + let leaf = unsafe { std::arch::x86_64::__cpuid(i) }; + cpuid.push(CpuIdEntry { + function: i, + eax: leaf.eax, + ebx: leaf.ebx, + ecx: leaf.ecx, + edx: leaf.edx, + ..Default::default() + }); + } + + let cpuid_profile = if is_non_host_profile { + let cpuid_profile = config + .profile + .adjust_cpuid(cpuid.clone(), config.amx, hypervisor.get_cpu_vendor()) + .map_err(Error::MissingExpectedCpuidEntry)?; + + required_common_cpuid_updates( + cpuid_profile, + config, + #[cfg(feature = "kvm")] + hypervisor.hypervisor_type(), + ) + } else { + Vec::new() + }; + + let cpuid_host = required_common_cpuid_updates( + cpuid, + config, + #[cfg(feature = "kvm")] + hypervisor.hypervisor_type(), + ); + + // If we want to apply a CPU profile we need to check that it remains compatible with `cpuid_host` + if is_non_host_profile { + CpuidFeatureEntry::check_cpuid_compatibility_with_descriptions( + &cpuid_profile, + "CPU Profile", + &cpuid_host, + "Host VM", + ) + .map_err(|_| Error::CpuProfileCpuidIncompatibility)?; + Ok(cpuid_profile) + } else { + Ok(cpuid_host) + } +} + +/// Apply updates to common CPUID (not vCPU specific) that are necessary regardless of +/// the chosen CPU profile. +fn required_common_cpuid_updates( + mut cpuid: Vec, + config: &CpuidConfig, + #[cfg(feature = "kvm")] hypervisor_type: hypervisor::HypervisorType, +) -> Vec { #[allow(unused_mut)] let mut cpuid_patches = vec![ // Patch hypervisor bit @@ -628,10 +731,7 @@ pub fn generate_common_cpuid( ]; #[cfg(feature = "kvm")] - if matches!( - hypervisor.hypervisor_type(), - hypervisor::HypervisorType::Kvm - ) { + if matches!(hypervisor_type, hypervisor::HypervisorType::Kvm) { // Patch tsc deadline timer bit cpuid_patches.push(CpuidPatch { function: 1, @@ -644,24 +744,8 @@ pub fn generate_common_cpuid( }); } - // Supported CPUID - let mut cpuid = hypervisor - .get_supported_cpuid() - .map_err(Error::CpuidGetSupported)?; - CpuidPatch::patch_cpuid(&mut cpuid, &cpuid_patches); - #[cfg(feature = "tdx")] - let tdx_capabilities = if config.tdx { - let caps = hypervisor - .tdx_capabilities() - .map_err(Error::TdxCapabilities)?; - info!("TDX capabilities {caps:#?}"); - Some(caps) - } else { - None - }; - // Update some existing CPUID for entry in cpuid.as_mut_slice().iter_mut() { #[allow(unused_unsafe)] @@ -676,25 +760,6 @@ pub fn generate_common_cpuid( entry.edx &= !(1 << AMX_COMPLEX); } } - 0xd => - { - #[cfg(feature = "tdx")] - if let Some(caps) = &tdx_capabilities { - let xcr0_mask: u64 = 0x82ff; - let xss_mask: u64 = !xcr0_mask; - if entry.index == 0 { - entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32); - entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32); - entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32; - entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32; - } else if entry.index == 1 { - entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32); - entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32); - entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32; - entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32; - } - } - } // Tile Information (purely AMX related). 0x1d if !config.amx => { entry.eax = 0; @@ -766,22 +831,6 @@ pub fn generate_common_cpuid( } } - // Copy CPU identification string - for i in 0x8000_0002..=0x8000_0004 { - cpuid.retain(|c| c.function != i); - // SAFETY: call cpuid with valid leaves - #[allow(unused_unsafe)] - let leaf = unsafe { std::arch::x86_64::__cpuid(i) }; - cpuid.push(CpuIdEntry { - function: i, - eax: leaf.eax, - ebx: leaf.ebx, - ecx: leaf.ecx, - edx: leaf.edx, - ..Default::default() - }); - } - if config.kvm_hyperv { // Remove conflicting entries cpuid.retain(|c| c.function != 0x4000_0000); @@ -829,7 +878,36 @@ pub fn generate_common_cpuid( } } - Ok(cpuid) + cpuid +} + +#[cfg(feature = "tdx")] +fn common_cpuid_tdx_configuration( + cpuid: &mut [CpuIdEntry], + hypervisor: &dyn hypervisor::Hypervisor, +) -> super::Result<()> { + let caps = hypervisor + .tdx_capabilities() + .map_err(Error::TdxCapabilities)?; + info!("TDX capabilities {caps:#?}"); + + for entry in cpuid.iter_mut().filter(|entry| entry.function == 0xd) { + let xcr0_mask: u64 = 0x82ff; + let xss_mask: u64 = !xcr0_mask; + if entry.index == 0 { + entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32); + entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32); + entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32; + entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32; + } else if entry.index == 1 { + entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32); + entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32); + entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32; + entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32; + } + } + + Ok(()) } #[allow(clippy::too_many_arguments)]