Skip to content

Commit a743da6

Browse files
arch: Make the CPU profile generation tool MSR aware
We adapt the CPU profile generation tool to also take the MSR-based features into account. Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de> On-behalf-of: SAP oliver.anderson@sap.com
1 parent a7cd06e commit a743da6

1 file changed

Lines changed: 153 additions & 44 deletions

File tree

arch/src/x86_64/cpu_profile_generation.rs

Lines changed: 153 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
//
55
use std::fs::File;
66
use std::io::Write;
7-
use std::ops::RangeInclusive;
7+
use std::ops::{BitOr, RangeInclusive, Shl};
8+
use std::path::PathBuf;
89

910
use anyhow::{Context, anyhow};
10-
use hypervisor::arch::x86::CpuIdEntry;
11+
use hypervisor::arch::x86::{CpuIdEntry, MsrEntry};
1112
use hypervisor::{CpuVendor, Hypervisor, HypervisorError, HypervisorType};
1213

13-
use crate::x86_64::cpu_profile::CpuIdProfileData;
14+
use crate::x86_64::cpu_profile::{CpuIdProfileData, FeatureMsrAdjustment, MsrProfileData};
1415
#[cfg(feature = "kvm")]
1516
use crate::x86_64::cpuid_definitions::CpuidDefinitions;
1617
use crate::x86_64::cpuid_definitions::intel::INTEL_CPUID_DEFINITIONS;
1718
use crate::x86_64::cpuid_definitions::kvm::KVM_CPUID_DEFINITIONS;
1819
use crate::x86_64::cpuid_definitions::{Parameters, ProfilePolicy};
20+
use crate::x86_64::msr_definitions::{self, MsrDefinitions};
1921
use crate::x86_64::{CpuidOutputRegisterAdjustments, CpuidReg};
2022

2123
/// Generate CPU profile data and convert it to a string, embeddable as Rust code, which is
@@ -27,6 +29,8 @@ pub fn generate_profile_data(
2729
hypervisor: &dyn Hypervisor,
2830
profile_name: &str,
2931
) -> anyhow::Result<()> {
32+
use crate::x86_64::msr_definitions::intel::INTEL_MSR_FEATURE_DEFINITIONS;
33+
3034
let cpu_vendor = hypervisor.get_cpu_vendor();
3135
if cpu_vendor != CpuVendor::Intel {
3236
unimplemented!("CPU profiles can only be generated for Intel CPUs at this point in time");
@@ -48,22 +52,36 @@ pub fn generate_profile_data(
4852
let Files {
4953
cpuid_data_file,
5054
cpuid_data_license_file,
55+
msr_data_file,
56+
msr_data_license_file,
5157
} = create_files(profile_name)?;
5258

53-
generate_cpu_profile_data_with(
59+
generate_cpuid_profile_data_with(
5460
hypervisor_type,
5561
cpu_vendor,
5662
&supported_cpuid_sorted,
5763
&INTEL_CPUID_DEFINITIONS,
5864
&KVM_CPUID_DEFINITIONS,
5965
cpuid_data_file,
6066
cpuid_data_license_file,
67+
)?;
68+
69+
let supported_feature_msrs = hypervisor.get_msr_based_features().context("CPU profile generation failed: Could not get the supported MSR-based features from the hypervisor")?;
70+
generate_feature_msr_profile_data_with(
71+
hypervisor_type,
72+
cpu_vendor,
73+
&supported_feature_msrs,
74+
&INTEL_MSR_FEATURE_DEFINITIONS,
75+
msr_data_file,
76+
msr_data_license_file,
6177
)
6278
}
6379

6480
struct Files {
6581
cpuid_data_file: File,
6682
cpuid_data_license_file: File,
83+
msr_data_file: File,
84+
msr_data_license_file: File,
6785
}
6886
/// Create empty files with names derived from the name given to the CPU profile.
6987
/// The name will be lowercase and spaces are replaced with "-".
@@ -79,40 +97,53 @@ fn create_files(profile_name: &str) -> anyhow::Result<Files> {
7997
name
8098
};
8199

100+
let create_file = |path: PathBuf| {
101+
File::create(path.clone()).with_context(|| {
102+
format!(
103+
"CPU profile generation failed: Could not create file:={}",
104+
path.to_string_lossy()
105+
)
106+
})
107+
};
108+
109+
let path_with_license = |mut path: PathBuf| {
110+
path.as_mut_os_string().push(".license");
111+
path
112+
};
113+
114+
let current_dir = std::env::current_dir()
115+
.context("CPU profile generation failed: Unable to get the current working directory")?;
116+
117+
let common_path = format!("arch/src/x86_64/cpu_profiles/{profile_file_name}");
118+
82119
let cpuid_profile_file_name = {
83-
let mut path = std::env::current_dir().context(
84-
"CPU profile generation failed: Unable to get the current working directory",
85-
)?;
86-
path.push(format!(
87-
"arch/src/x86_64/cpu_profiles/{profile_file_name}.cpuid.json"
88-
));
120+
let mut path = current_dir.clone();
121+
path.push(format!("{common_path}.cpuid.json"));
89122
path
90123
};
91124

92-
let cpuid_data_file = File::create(cpuid_profile_file_name.clone()).with_context(|| {
93-
format!(
94-
"CPU profile generation failed: Could not create file:={}",
95-
cpuid_profile_file_name.to_string_lossy()
96-
)
97-
})?;
125+
let cpuid_data_file = create_file(cpuid_profile_file_name.clone())?;
98126

99-
let cpuid_data_license_file_path = {
100-
let mut path = cpuid_profile_file_name.clone();
101-
path.as_mut_os_string().push(".license");
127+
let cpuid_data_license_file_path = path_with_license(cpuid_profile_file_name);
128+
129+
let cpuid_data_license_file = create_file(cpuid_data_license_file_path)?;
130+
131+
let msr_profile_file_name = {
132+
let mut path = current_dir;
133+
path.push(format!("{common_path}.msr.json"));
102134
path
103135
};
104136

105-
let cpuid_data_license_file =
106-
File::create(cpuid_data_license_file_path.clone()).with_context(|| {
107-
format!(
108-
"CPU profile generation failed: Could not create file:={}",
109-
cpuid_data_license_file_path.to_string_lossy()
110-
)
111-
})?;
137+
let msr_data_file = create_file(msr_profile_file_name.clone())?;
138+
139+
let msr_data_license_file_path = path_with_license(msr_profile_file_name);
140+
let msr_data_license_file = create_file(msr_data_license_file_path)?;
112141

113142
Ok(Files {
114143
cpuid_data_file,
115144
cpuid_data_license_file,
145+
msr_data_file,
146+
msr_data_license_file,
116147
})
117148
}
118149

@@ -138,21 +169,21 @@ fn cpu_brand_string_bytes(cpu_vendor: CpuVendor, profile_name: &str) -> anyhow::
138169
}
139170
Ok(brand_string_bytes)
140171
}
141-
/// Computes [`CpuProfileData`] based on the given sorted vector of CPUID entries, hypervisor type, cpu_vendor
172+
/// Computes [`CpuIdProfileData`] based on the given sorted vector of CPUID entries, hypervisor type, cpu_vendor
142173
/// and cpuid_definitions.
143174
///
144-
/// The computed [`CpuProfileData`] is then converted to a string representation, embeddable as Rust code, which is
175+
/// The computed [`CpuIdProfileData`] is then converted to a string representation, embeddable as Rust code, which is
145176
/// then written by the given `writer`.
146177
///
147178
// TODO: Consider making a snapshot test or two for this function.
148-
fn generate_cpu_profile_data_with<const N: usize, const M: usize>(
179+
fn generate_cpuid_profile_data_with<const N: usize, const M: usize>(
149180
hypervisor_type: HypervisorType,
150181
cpu_vendor: CpuVendor,
151182
supported_cpuid_sorted: &[CpuIdEntry],
152183
processor_cpuid_definitions: &CpuidDefinitions<N>,
153184
hypervisor_cpuid_definitions: &CpuidDefinitions<M>,
154185
mut cpuid_data_file: impl Write,
155-
mut cpuid_license_file: impl Write,
186+
cpuid_license_file: impl Write,
156187
) -> anyhow::Result<()> {
157188
let mut adjustments: Vec<(Parameters, CpuidOutputRegisterAdjustments)> = Vec::new();
158189

@@ -180,7 +211,7 @@ fn generate_cpu_profile_data_with<const N: usize, const M: usize>(
180211
// The profile should take whatever we get from the host, hence there is no adjustment, but our
181212
// mask needs to retain all bits in the range of bits corresponding to this value
182213
let (first_bit_pos, last_bit_pos) = value.bits_range;
183-
mask |= bit_range_mask(first_bit_pos, last_bit_pos);
214+
mask |= bit_range_mask::<u32>(first_bit_pos, last_bit_pos);
184215
}
185216
ProfilePolicy::Static(overwrite_value) => {
186217
replacements |= overwrite_value << value.bits_range.0;
@@ -190,7 +221,8 @@ fn generate_cpu_profile_data_with<const N: usize, const M: usize>(
190221
let (first_bit_pos, last_bit_pos) = value.bits_range;
191222
if let Some(matching_register_value) = maybe_matching_register_output_value
192223
{
193-
let extraction_mask = bit_range_mask(first_bit_pos, last_bit_pos);
224+
let extraction_mask =
225+
bit_range_mask::<u32>(first_bit_pos, last_bit_pos);
194226
let value = matching_register_value & extraction_mask;
195227
replacements |= value;
196228
}
@@ -219,22 +251,94 @@ fn generate_cpu_profile_data_with<const N: usize, const M: usize>(
219251
cpuid_data_file
220252
.flush()
221253
.context("CPU profile generation failed: Unable to flush cpuid profile data")?;
254+
write_license_file(cpuid_license_file, "CPUID")
255+
}
256+
257+
fn generate_feature_msr_profile_data_with<const N: usize>(
258+
hypervisor_type: HypervisorType,
259+
cpu_vendor: CpuVendor,
260+
supported_feature_msrs: &[MsrEntry],
261+
processor_feature_msr_definitions: &MsrDefinitions<N>,
262+
mut msr_data_file: impl Write,
263+
msr_license_file: impl Write,
264+
) -> anyhow::Result<()> {
265+
let mut entries_encountered = 0;
266+
let mut adjustments = Vec::new();
267+
'table: for (reg_addr, definitions) in processor_feature_msr_definitions.as_slice() {
268+
let Some(entry) = supported_feature_msrs
269+
.iter()
270+
.find(|e| e.index == reg_addr.0)
271+
else {
272+
continue;
273+
};
274+
entries_encountered += 1;
275+
let value = entry.data;
276+
let mut replacements = 0;
277+
let mut mask = 0;
278+
for msr_definitions::ValueDefinition {
279+
policy,
280+
bits_range: (first_bit_pos, last_bit_pos),
281+
..
282+
} in definitions.as_slice().iter().copied()
283+
{
284+
match policy {
285+
msr_definitions::ProfilePolicy::Deny => {
286+
// This can only be applied to the entire MSR
287+
assert_eq!(first_bit_pos, 0);
288+
assert_eq!(last_bit_pos, 63);
289+
continue 'table;
290+
}
291+
msr_definitions::ProfilePolicy::Inherit => {
292+
replacements |= value & bit_range_mask::<u64>(first_bit_pos, last_bit_pos);
293+
}
294+
msr_definitions::ProfilePolicy::Passthrough => {
295+
mask |= bit_range_mask::<u64>(first_bit_pos, last_bit_pos);
296+
}
297+
msr_definitions::ProfilePolicy::Static(overwrite_value) => {
298+
replacements |= (overwrite_value) << (first_bit_pos);
299+
}
300+
}
301+
}
302+
adjustments.push((*reg_addr, FeatureMsrAdjustment { mask, replacements }));
303+
}
304+
305+
if entries_encountered != supported_feature_msrs.len() {
306+
let unknown_register_address = supported_feature_msrs.iter().find(|entry| !processor_feature_msr_definitions.as_slice().iter().any(|(reg_addr, _)| reg_addr.0 == entry.index )).expect("We have checked that there should be at least one unknown supported MSR-based feature").index;
307+
Err(anyhow!(
308+
"CPU profile generation failed: The hardware and hypervisor supports register address:={unknown_register_address}, but the CPU profile generation tool does not know what to do with this MSR. Please update the appropriate `MsrDefinitions` and try again."
309+
))?;
310+
}
311+
312+
let msr_profile_data = MsrProfileData {
313+
hypervisor_type,
314+
cpu_vendor,
315+
adjustments,
316+
};
317+
318+
serde_json::to_writer_pretty(&mut msr_data_file, &msr_profile_data)
319+
.context("Cpu profile generation failed: Could not serialize the generated MSR profile data to the given writer")?;
320+
msr_data_file
321+
.flush()
322+
.context("CPU profile generation failed: Unable to flush MSR profile data")?;
323+
write_license_file(msr_license_file, "MSR")
324+
}
325+
326+
fn write_license_file(mut license_file: impl Write, data_type: &str) -> anyhow::Result<()> {
222327
let license_text = {
223328
r#"SPDX-FileCopyrightText: 2025 Cyberus Technology GmbH
224329
225330
SPDX-License-Identifier: Apache-2.0
226331
"#
227332
};
228-
cpuid_license_file
333+
license_file
229334
.write_all(license_text.as_bytes())
230-
.context(
231-
"CPU profile generation failed: Unable to write to cpuid profile data license file",
232-
)?;
233-
cpuid_license_file
234-
.flush()
235-
.context("CPU profile generation failed: Unable to flush cpuid profile data license file")
335+
.with_context(|| {
336+
format!("CPU profile generation failed: Unable to write to {data_type} profile data license file")
337+
})?;
338+
license_file.flush().context(format!(
339+
"CPU profile generation failed: Unable to flush {data_type} profile data license file"
340+
))
236341
}
237-
238342
/// Get as many of the supported CPUID entries from the hypervisor as possible.
239343
fn supported_cpuid(hypervisor: &dyn Hypervisor) -> anyhow::Result<Vec<CpuIdEntry>> {
240344
// Check for AMX compatibility. If this is supported we need to call arch_prctl before requesting the supported
@@ -302,11 +406,16 @@ fn sort_entries(mut cpuid: Vec<CpuIdEntry>) -> Vec<CpuIdEntry> {
302406
});
303407
cpuid
304408
}
305-
/// Returns a `u32` where each bit between `first_bit_pos` and `last_bit_pos` is set (including both ends) and all other bits are 0.
306-
fn bit_range_mask(first_bit_pos: u8, last_bit_pos: u8) -> u32 {
307-
(first_bit_pos..=last_bit_pos).fold(0, |acc, next| acc | (1 << next))
308-
}
309409

410+
/// Returns a numeric value where each bit between `first_bit_pos` and `last_bit_pos` is set (including both ends) and all other bits are 0.
411+
fn bit_range_mask<T>(first_bit_pos: u8, last_bit_pos: u8) -> T
412+
where
413+
T: Shl<u8, Output = T>,
414+
T: BitOr<Output = T>,
415+
T: From<u8>,
416+
{
417+
(first_bit_pos..=last_bit_pos).fold(T::from(0_u8), |acc, next| acc | ((T::from(1_u8)) << next))
418+
}
310419
/// Returns a vector of exact parameter matches ((sub_leaf ..= sub_leaf), register_value) interleaved by
311420
/// the sub_leaf ranges specified by `param` that did not match any cpuid entry.
312421
fn extract_parameter_matches(

0 commit comments

Comments
 (0)