arch: CpuidAdjustments type

Introduce a type for adjusting CPUID entries.

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-03-20 17:47:23 +01:00
committed by Rob Bradford
parent 9f38cec485
commit e81c51c49b

View File

@@ -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<CpuIdEntry>,
adjustments: &[(CpuidParameters, Self)],
) -> Result<Vec<CpuIdEntry>, 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<CpuidOutputRegisterAdjustments> =
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(())
}
}
}