arch: Apply CPU profiles

We refactor `generate_common_cpuid` to take CPU profiles into account.

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-04-13 14:25:15 +02:00
committed by Rob Bradford
parent ed3a42bc9d
commit dbf489c1e5

View File

@@ -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<CpuIdEntry>,
config: &CpuidConfig,
#[cfg(feature = "kvm")] hypervisor_type: hypervisor::HypervisorType,
) -> Vec<CpuIdEntry> {
#[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)]