44//
55use std:: fs:: File ;
66use std:: io:: Write ;
7- use std:: ops:: RangeInclusive ;
7+ use std:: ops:: { BitOr , RangeInclusive , Shl } ;
8+ use std:: path:: PathBuf ;
89
910use anyhow:: { Context , anyhow} ;
10- use hypervisor:: arch:: x86:: CpuIdEntry ;
11+ use hypervisor:: arch:: x86:: { CpuIdEntry , MsrEntry } ;
1112use 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" ) ]
1516use crate :: x86_64:: cpuid_definitions:: CpuidDefinitions ;
1617use crate :: x86_64:: cpuid_definitions:: intel:: INTEL_CPUID_DEFINITIONS ;
1718use crate :: x86_64:: cpuid_definitions:: kvm:: KVM_CPUID_DEFINITIONS ;
1819use crate :: x86_64:: cpuid_definitions:: { Parameters , ProfilePolicy } ;
20+ use crate :: x86_64:: msr_definitions:: { self , MsrDefinitions } ;
1921use 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
6480struct 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
225330SPDX-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.
239343fn 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.
312421fn extract_parameter_matches (
0 commit comments