From e81c51c49bcceb3cb7de3ef2c3ec3d819de3fbcd Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 20 Mar 2026 17:47:23 +0100 Subject: [PATCH] arch: CpuidAdjustments type Introduce a type for adjusting CPUID entries. Signed-off-by: Oliver Anderson On-behalf-of: SAP oliver.anderson@sap.com --- .../x86_64/cpu_profile/cpuid_adjustments.rs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/arch/src/x86_64/cpu_profile/cpuid_adjustments.rs b/arch/src/x86_64/cpu_profile/cpuid_adjustments.rs index 10ae1bb61..ac3f58dfc 100644 --- a/arch/src/x86_64/cpu_profile/cpuid_adjustments.rs +++ b/arch/src/x86_64/cpu_profile/cpuid_adjustments.rs @@ -8,7 +8,10 @@ use std::ops::RangeInclusive; +use hypervisor::arch::x86::CpuIdEntry; +use log::error; use serde::{Deserialize, Serialize}; +use thiserror::Error; use crate::x86_64::{CpuidReg, deserialize_u32_hex, serialize_u32_hex}; @@ -64,3 +67,117 @@ fn deserialize_range_hex<'de, D: serde::Deserializer<'de>>( ProvisionalRangeInclusive::deserialize(deserializer)?; Ok(start..=end) } + +/// Used for adjusting an entire cpuid output register (EAX, EBX, ECX or EDX). +/// +/// Instances of this struct typically adjust CPUID according to the following +/// formula: `cpuid_reg_value = (self.mask & cpuid_reg_value) | self.replacements`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct CpuidOutputRegisterAdjustments { + /// Packs values to be placed into the given CPUID output register. + #[serde( + serialize_with = "serialize_u32_hex", + deserialize_with = "deserialize_u32_hex" + )] + pub replacements: u32, + /// Used to zero out the area `replacements` occupy. This mask is not necessarily !replacements, as replacements + /// may pack values of different types that occupy varying ranges of bits. + /// + /// Bit ranges within a CPUID output register that are **not** supposed to be replaced/overwritten should be set in + /// this mask. + #[serde( + serialize_with = "serialize_u32_hex", + deserialize_with = "deserialize_u32_hex" + )] + pub mask: u32, +} + +/// Error type indicating that expected CPUID entries could not be found. +/// +/// This type does not record which entries could not be found as we do not +/// expect this to be actionable at runtime. Instead we encourage logging such +/// violations when and where they are detected. +#[derive(Debug, Error)] +#[error("Required CPUID entries not found")] +pub struct MissingCpuidEntriesError; + +impl CpuidOutputRegisterAdjustments { + /// Adjust the given `cpuid_output_register` by retaining and replacing values according to `self`. + fn adjust(self, cpuid_output_register: &mut u32) { + *cpuid_output_register &= self.mask; + *cpuid_output_register |= self.replacements; + } + + /// Adjust `cpuid` according to the given `adjustments`. + /// + /// The returned vector of cpuid entries covers the same CPUID (sub-) leaves as the given `cpuid` input, + /// but values without matching [`CpuidParameters`] are zeroed out. + /// + /// # Errors + /// + /// An error is returned if an entry cannot be found for an adjustment describing non-zero replacements. + pub(super) fn adjust_cpuid_entries( + mut cpuid: Vec, + adjustments: &[(CpuidParameters, Self)], + ) -> Result, MissingCpuidEntriesError> { + for entry in &mut cpuid { + for (reg, reg_value) in [ + (CpuidReg::EAX, &mut entry.eax), + (CpuidReg::EBX, &mut entry.ebx), + (CpuidReg::ECX, &mut entry.ecx), + (CpuidReg::EDX, &mut entry.edx), + ] { + // Lookup the adjustment corresponding to the entry's function/leaf and index/sub-leaf for each of the register. + let register_adjustments: Option = + adjustments.iter().find_map(|(param, adjustment)| { + ((param.leaf == entry.function) + && param.sub_leaf.contains(&entry.index) + && (param.register == reg)) + .then_some(*adjustment) + }); + + match register_adjustments { + Some(adjustment) => adjustment.adjust(reg_value), + None => { + // No matching cpuid parameters were found. We thus set the value of the register to 0. + *reg_value = 0; + } + } + } + } + + Self::expected_entries_found(&cpuid, adjustments)?; + Ok(cpuid) + } + + /// Check that we found every value that was supposed to be replaced with something else than 0 + /// + /// IMPORTANT: This function assumes that the given `cpuid` has already been adjusted with the + /// provided `adjustments`. + fn expected_entries_found( + cpuid: &[CpuIdEntry], + adjustments: &[(CpuidParameters, Self)], + ) -> Result<(), MissingCpuidEntriesError> { + let mut missing_entry = false; + + for (param, adjustment) in adjustments { + if adjustment.replacements == 0 { + continue; + } + + if !cpuid.iter().any(|entry| { + (entry.function == param.leaf) && (param.sub_leaf.contains(&entry.index)) + }) { + error!( + "cannot adjust CPU profile. No entry found matching the required parameters: {param:?}" + ); + missing_entry = true; + } + } + if missing_entry { + Err(MissingCpuidEntriesError) + } else { + Ok(()) + } + } +}