vmm/cpu_config/x86_64/cpuid/normalize.rs
1// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::cpu_config::x86_64::cpuid::{
5 CpuidEntry, CpuidKey, CpuidRegisters, CpuidTrait, KvmCpuidFlags, cpuid,
6};
7use crate::logger::warn;
8use crate::vmm_config::machine_config::MAX_SUPPORTED_VCPUS;
9
10/// Error type for [`super::Cpuid::normalize`].
11#[allow(clippy::module_name_repetitions)]
12#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
13pub enum NormalizeCpuidError {
14 /// Provided `cpu_bits` is >=8: {0}.
15 CpuBits(u8),
16 /// Failed to apply modifications to Intel CPUID: {0}
17 Intel(#[from] crate::cpu_config::x86_64::cpuid::intel::NormalizeCpuidError),
18 /// Failed to apply modifications to AMD CPUID: {0}
19 Amd(#[from] crate::cpu_config::x86_64::cpuid::amd::NormalizeCpuidError),
20 /// Failed to set feature information leaf: {0}
21 FeatureInformation(#[from] FeatureInformationError),
22 /// Failed to set extended topology leaf: {0}
23 ExtendedTopology(#[from] ExtendedTopologyError),
24 /// Failed to set extended cache features leaf: {0}
25 ExtendedCacheFeatures(#[from] ExtendedCacheFeaturesError),
26 /// Failed to set vendor ID in leaf 0x0: {0}
27 VendorId(#[from] VendorIdError),
28}
29
30/// Error type for setting leaf 0 section.
31#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
32pub enum VendorIdError {
33 /// Leaf 0x0 is missing from CPUID.
34 MissingLeaf0,
35}
36
37/// Error type for setting leaf 1 section of `IntelCpuid::normalize`.
38#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
39pub enum FeatureInformationError {
40 /// Leaf 0x1 is missing from CPUID.
41 MissingLeaf1,
42 /// Failed to set `Initial APIC ID`: {0}
43 InitialApicId(CheckedAssignError),
44 /// Failed to set `CLFLUSH line size`: {0}
45 Clflush(CheckedAssignError),
46 /// Failed to get max CPUs per package: {0}
47 GetMaxCpusPerPackage(GetMaxCpusPerPackageError),
48 /// Failed to set max CPUs per package: {0}
49 SetMaxCpusPerPackage(CheckedAssignError),
50}
51
52/// Error type for `get_max_cpus_per_package`.
53#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
54pub enum GetMaxCpusPerPackageError {
55 /// Failed to get max CPUs per package as `cpu_count == 0`
56 Underflow,
57 /// Failed to get max CPUs per package as `cpu_count > 128`
58 Overflow,
59}
60
61/// Error type for setting leaf b section of `IntelCpuid::normalize`.
62#[rustfmt::skip]
63#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
64pub enum ExtendedTopologyError {
65 /// Failed to set domain type (CPUID.(EAX=0xB,ECX={0}):ECX[15:8]): {1}
66 DomainType(u32, CheckedAssignError),
67 /// Failed to set input ECX (CPUID.(EAX=0xB,ECX={0}):ECX[7:0]): {1}
68 InputEcx(u32, CheckedAssignError),
69 /// Failed to set number of logical processors (CPUID.(EAX=0xB,ECX={0}):EBX[15:0]): {1}
70 NumLogicalProcs(u32, CheckedAssignError),
71 /// Failed to set right-shift bits (CPUID.(EAX=0xB,ECX={0}):EAX[4:0]): {1}
72 RightShiftBits(u32, CheckedAssignError),
73}
74
75/// Error type for setting leaf 0x80000006 of Cpuid::normalize().
76#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
77pub enum ExtendedCacheFeaturesError {
78 /// Leaf 0x80000005 is missing from CPUID.
79 MissingLeaf0x80000005,
80 /// Leaf 0x80000006 is missing from CPUID.
81 MissingLeaf0x80000006,
82}
83
84/// Error type for setting a bit range.
85#[derive(Debug, PartialEq, Eq, thiserror::Error)]
86#[error("Given value is greater than maximum storable value in bit range.")]
87pub struct CheckedAssignError;
88
89/// Sets a given bit to a true or false (1 or 0).
90#[allow(clippy::arithmetic_side_effects)]
91pub fn set_bit(x: &mut u32, bit: u8, y: bool) {
92 debug_assert!(bit < 32);
93 *x = (*x & !(1 << bit)) | ((u32::from(u8::from(y))) << bit);
94}
95
96/// Sets a given range to a given value.
97pub fn set_range(
98 x: &mut u32,
99 range: std::ops::RangeInclusive<u8>,
100 y: u32,
101) -> Result<(), CheckedAssignError> {
102 let start = *range.start();
103 let end = *range.end();
104
105 debug_assert!(end >= start);
106 debug_assert!(end < 32);
107
108 // Ensure `y` fits within the number of bits in the specified range.
109 // Note that
110 // - 1 <= `num_bits` <= 32 from the above assertion
111 // - if `num_bits` equals to 32, `y` always fits within it since `y` is `u32`.
112 let num_bits = end - start + 1;
113 if num_bits < 32 && y >= (1u32 << num_bits) {
114 return Err(CheckedAssignError);
115 }
116
117 let mask = get_mask(range);
118 *x = (*x & !mask) | (y << start);
119
120 Ok(())
121}
122
123/// Gets a given range within a given value.
124pub fn get_range(x: u32, range: std::ops::RangeInclusive<u8>) -> u32 {
125 let start = *range.start();
126 let end = *range.end();
127
128 debug_assert!(end >= start);
129 debug_assert!(end < 32);
130
131 let mask = get_mask(range);
132 (x & mask) >> start
133}
134
135/// Returns a mask where the given range is ones.
136const fn get_mask(range: std::ops::RangeInclusive<u8>) -> u32 {
137 let num_bits = *range.end() - *range.start() + 1;
138 let shift = *range.start();
139
140 if num_bits == 32 {
141 u32::MAX
142 } else {
143 ((1u32 << num_bits) - 1) << shift
144 }
145}
146
147// We use this 2nd implementation so we can conveniently define functions only used within
148// `normalize`.
149#[allow(clippy::multiple_inherent_impl)]
150impl super::Cpuid {
151 /// Applies required modifications to CPUID respective of a vCPU.
152 ///
153 /// # Errors
154 ///
155 /// When:
156 /// - [`super::IntelCpuid::normalize`] errors.
157 /// - [`super::AmdCpuid::normalize`] errors.
158 // As we pass through host frequency, we require CPUID and thus `cfg(cpuid)`.
159 #[inline]
160 pub fn normalize(
161 &mut self,
162 // The index of the current logical CPU in the range [0..cpu_count].
163 cpu_index: u8,
164 // The total number of logical CPUs.
165 cpu_count: u8,
166 // The number of bits needed to enumerate logical CPUs per core.
167 cpu_bits: u8,
168 ) -> Result<(), NormalizeCpuidError> {
169 let cpus_per_core = 1u8
170 .checked_shl(u32::from(cpu_bits))
171 .ok_or(NormalizeCpuidError::CpuBits(cpu_bits))?;
172 self.update_vendor_id()?;
173 self.update_feature_info_entry(cpu_index, cpu_count)?;
174 self.update_extended_topology_entry(cpu_index, cpu_count, cpu_bits, cpus_per_core)?;
175 self.update_extended_cache_features()?;
176
177 // Apply manufacturer specific modifications.
178 match self {
179 // Apply Intel specific modifications.
180 Self::Intel(intel_cpuid) => {
181 intel_cpuid.normalize(cpu_index, cpu_count, cpus_per_core)?;
182 }
183 // Apply AMD specific modifications.
184 Self::Amd(amd_cpuid) => amd_cpuid.normalize(cpu_index, cpu_count, cpus_per_core)?,
185 }
186
187 Ok(())
188 }
189
190 /// Pass-through the vendor ID from the host. This is used to prevent modification of the vendor
191 /// ID via custom CPU templates.
192 fn update_vendor_id(&mut self) -> Result<(), VendorIdError> {
193 let leaf_0 = self
194 .get_mut(&CpuidKey::leaf(0x0))
195 .ok_or(VendorIdError::MissingLeaf0)?;
196
197 let host_leaf_0 = cpuid(0x0);
198
199 leaf_0.result.ebx = host_leaf_0.ebx;
200 leaf_0.result.ecx = host_leaf_0.ecx;
201 leaf_0.result.edx = host_leaf_0.edx;
202
203 Ok(())
204 }
205
206 // Update feature information entry
207 fn update_feature_info_entry(
208 &mut self,
209 cpu_index: u8,
210 cpu_count: u8,
211 ) -> Result<(), FeatureInformationError> {
212 let leaf_1 = self
213 .get_mut(&CpuidKey::leaf(0x1))
214 .ok_or(FeatureInformationError::MissingLeaf1)?;
215
216 // CPUID.01H:EBX[15:08]
217 // CLFLUSH line size (Value * 8 = cache line size in bytes; used also by CLFLUSHOPT).
218 set_range(&mut leaf_1.result.ebx, 8..=15, 8).map_err(FeatureInformationError::Clflush)?;
219
220 // CPUID.01H:EBX[23:16]
221 // Maximum number of addressable IDs for logical processors in this physical package.
222 //
223 // The nearest power-of-2 integer that is not smaller than EBX[23:16] is the number of
224 // unique initial APIC IDs reserved for addressing different logical processors in a
225 // physical package. This field is only valid if CPUID.1.EDX.HTT[bit 28]= 1.
226 let max_cpus_per_package = u32::from(
227 get_max_cpus_per_package(cpu_count)
228 .map_err(FeatureInformationError::GetMaxCpusPerPackage)?,
229 );
230 set_range(&mut leaf_1.result.ebx, 16..=23, max_cpus_per_package)
231 .map_err(FeatureInformationError::SetMaxCpusPerPackage)?;
232
233 // CPUID.01H:EBX[31:24]
234 // Initial APIC ID.
235 //
236 // The 8-bit initial APIC ID in EBX[31:24] is replaced by the 32-bit x2APIC ID, available
237 // in Leaf 0BH and Leaf 1FH.
238 set_range(&mut leaf_1.result.ebx, 24..=31, u32::from(cpu_index))
239 .map_err(FeatureInformationError::InitialApicId)?;
240
241 // CPUID.01H:ECX[15] (Mnemonic: PDCM)
242 // Performance and Debug Capability: A value of 1 indicates the processor supports the
243 // performance and debug feature indication MSR IA32_PERF_CAPABILITIES.
244 set_bit(&mut leaf_1.result.ecx, 15, false);
245
246 // CPUID.01H:ECX[24] (Mnemonic: TSC-Deadline)
247 // A value of 1 indicates that the processor’s local APIC timer supports one-shot operation
248 // using a TSC deadline value.
249 set_bit(&mut leaf_1.result.ecx, 24, true);
250
251 // CPUID.01H:ECX[31] (Mnemonic: Hypervisor)
252 set_bit(&mut leaf_1.result.ecx, 31, true);
253
254 // CPUID.01H:EDX[28] (Mnemonic: HTT)
255 // Max APIC IDs reserved field is Valid. A value of 0 for HTT indicates there is only a
256 // single logical processor in the package and software should assume only a single APIC ID
257 // is reserved. A value of 1 for HTT indicates the value in CPUID.1.EBX[23:16] (the Maximum
258 // number of addressable IDs for logical processors in this package) is valid for the
259 // package.
260 set_bit(&mut leaf_1.result.edx, 28, cpu_count > 1);
261
262 Ok(())
263 }
264
265 /// Update extended topology entry
266 fn update_extended_topology_entry(
267 &mut self,
268 cpu_index: u8,
269 cpu_count: u8,
270 cpu_bits: u8,
271 cpus_per_core: u8,
272 ) -> Result<(), ExtendedTopologyError> {
273 // The following commit changed the behavior of KVM_GET_SUPPORTED_CPUID to no longer
274 // include CPUID.(EAX=0BH,ECX=1).
275 // https://lore.kernel.org/all/20221027092036.2698180-1-pbonzini@redhat.com/
276 self.inner_mut()
277 .entry(CpuidKey::subleaf(0xB, 0x1))
278 .or_insert(CpuidEntry {
279 flags: KvmCpuidFlags::SIGNIFICANT_INDEX,
280 result: CpuidRegisters {
281 eax: 0x0,
282 ebx: 0x0,
283 ecx: 0x0,
284 edx: 0x0,
285 },
286 });
287
288 for index in 0.. {
289 if let Some(subleaf) = self.get_mut(&CpuidKey::subleaf(0xB, index)) {
290 // Reset eax, ebx, ecx
291 subleaf.result.eax = 0;
292 subleaf.result.ebx = 0;
293 subleaf.result.ecx = 0;
294 // CPUID.(EAX=0BH,ECX=N).EDX[31:0]
295 // x2APIC ID of the current logical processor.
296 subleaf.result.edx = u32::from(cpu_index);
297 subleaf.flags = KvmCpuidFlags::SIGNIFICANT_INDEX;
298
299 match index {
300 // CPUID.(EAX=0BH,ECX=N):EAX[4:0]
301 // The number of bits that the x2APIC ID must be shifted to the right to address
302 // instances of the next higher-scoped domain. When logical processor is not
303 // supported by the processor, the value of this field at the Logical Processor
304 // domain sub-leaf may be returned as either 0 (no allocated bits in the x2APIC
305 // ID) or 1 (one allocated bit in the x2APIC ID); software should plan
306 // accordingly.
307
308 // CPUID.(EAX=0BH,ECX=N):EBX[15:0]
309 // The number of logical processors across all instances of this domain within
310 // the next-higher scoped domain. (For example, in a processor socket/package
311 // comprising "M" dies of "N" cores each, where each core has "L" logical
312 // processors, the "die" domain sub-leaf value of this field would be M*N*L.)
313 // This number reflects configuration as shipped by Intel. Note, software must
314 // not use this field to enumerate processor topology.
315
316 // CPUID.(EAX=0BH,ECX=N):ECX[7:0]
317 // The input ECX sub-leaf index.
318
319 // CPUID.(EAX=0BH,ECX=N):ECX[15:8]
320 // Domain Type. This field provides an identification value which indicates the
321 // domain as shown below. Although domains are ordered, their assigned
322 // identification values are not and software should not depend on it.
323 //
324 // Hierarchy Domain Domain Type Identification Value
325 // -----------------------------------------------------------------
326 // Lowest Logical Processor 1
327 // Highest Core 2
328 //
329 // (Note that enumeration values of 0 and 3-255 are reserved.)
330
331 // Logical processor domain
332 0 => {
333 // To get the next level APIC ID, shift right with at most 1 because we have
334 // maximum 2 logical procerssors per core that can be represented by 1 bit.
335 set_range(&mut subleaf.result.eax, 0..=4, u32::from(cpu_bits))
336 .map_err(|err| ExtendedTopologyError::RightShiftBits(index, err))?;
337
338 // When cpu_count == 1 or HT is disabled, there is 1 logical core at this
339 // domain; otherwise there are 2
340 set_range(&mut subleaf.result.ebx, 0..=15, u32::from(cpus_per_core))
341 .map_err(|err| ExtendedTopologyError::NumLogicalProcs(index, err))?;
342
343 // Skip setting 0 to ECX[7:0] since it's already reset to 0.
344
345 // Set the domain type identification value for logical processor,
346 set_range(&mut subleaf.result.ecx, 8..=15, 1)
347 .map_err(|err| ExtendedTopologyError::DomainType(index, err))?;
348 }
349 // Core domain
350 1 => {
351 // Configure such that the next higher-scoped domain (i.e. socket) include
352 // all logical processors.
353 //
354 // The CPUID.(EAX=0BH,ECX=1).EAX[4:0] value must be an integer N such that
355 // 2^N is greater than or equal to the maximum number of vCPUs.
356 set_range(
357 &mut subleaf.result.eax,
358 0..=4,
359 MAX_SUPPORTED_VCPUS.next_power_of_two().ilog2(),
360 )
361 .map_err(|err| ExtendedTopologyError::RightShiftBits(index, err))?;
362 set_range(&mut subleaf.result.ebx, 0..=15, u32::from(cpu_count))
363 .map_err(|err| ExtendedTopologyError::NumLogicalProcs(index, err))?;
364
365 // Setting the input ECX value (i.e. `index`)
366 set_range(&mut subleaf.result.ecx, 0..=7, index)
367 .map_err(|err| ExtendedTopologyError::InputEcx(index, err))?;
368
369 // Set the domain type identification value for core.
370 set_range(&mut subleaf.result.ecx, 8..=15, 2)
371 .map_err(|err| ExtendedTopologyError::DomainType(index, err))?;
372 }
373 _ => {
374 // KVM no longer returns any subleaves greater than 0. The patch was merged
375 // in v6.2 and backported to v5.10. So for all our supported kernels,
376 // subleaves >= 2 should not be included.
377 // https://github.com/torvalds/linux/commit/45e966fcca03ecdcccac7cb236e16eea38cc18af
378 //
379 // However, we intentionally leave Firecracker not fail for unsupported
380 // kernels to keep working. Note that we can detect KVM regression thanks
381 // to the test that compares a fingerprint with its baseline.
382 warn!("Subleaf {index} not expected for CPUID leaf 0xB.");
383 subleaf.result.ecx = index;
384 }
385 }
386 } else {
387 break;
388 }
389 }
390
391 Ok(())
392 }
393
394 // Update extended cache features entry
395 fn update_extended_cache_features(&mut self) -> Result<(), ExtendedCacheFeaturesError> {
396 // Leaf 0x800000005 indicates L1 Cache and TLB Information.
397 let guest_leaf_0x80000005 = self
398 .get_mut(&CpuidKey::leaf(0x80000005))
399 .ok_or(ExtendedCacheFeaturesError::MissingLeaf0x80000005)?;
400 guest_leaf_0x80000005.result = cpuid(0x80000005).into();
401
402 // Leaf 0x80000006 indicates L2 Cache and TLB and L3 Cache Information.
403 let guest_leaf_0x80000006 = self
404 .get_mut(&CpuidKey::leaf(0x80000006))
405 .ok_or(ExtendedCacheFeaturesError::MissingLeaf0x80000006)?;
406 guest_leaf_0x80000006.result = cpuid(0x80000006).into();
407 guest_leaf_0x80000006.result.edx &= !0x00030000; // bits [17:16] are reserved
408 Ok(())
409 }
410}
411
412/// The maximum number of logical processors per package is computed as the closest
413/// power of 2 higher or equal to the CPU count configured by the user.
414const fn get_max_cpus_per_package(cpu_count: u8) -> Result<u8, GetMaxCpusPerPackageError> {
415 // This match is better than but approximately equivalent to
416 // `2.pow((cpu_count as f32).log2().ceil() as u8)` (`2^ceil(log_2(c))`).
417 match cpu_count {
418 0 => Err(GetMaxCpusPerPackageError::Underflow),
419 // `0u8.checked_next_power_of_two()` returns `Some(1)`, this is not the desired behaviour so
420 // we use `next_power_of_two()` instead.
421 1..=128 => Ok(cpu_count.next_power_of_two()),
422 129..=u8::MAX => Err(GetMaxCpusPerPackageError::Overflow),
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use std::collections::BTreeMap;
429
430 use super::*;
431 use crate::cpu_config::x86_64::cpuid::{AmdCpuid, Cpuid, IntelCpuid};
432
433 #[test]
434 fn get_max_cpus_per_package_test() {
435 assert_eq!(
436 get_max_cpus_per_package(0),
437 Err(GetMaxCpusPerPackageError::Underflow)
438 );
439 assert_eq!(get_max_cpus_per_package(1), Ok(1));
440 assert_eq!(get_max_cpus_per_package(2), Ok(2));
441 assert_eq!(get_max_cpus_per_package(3), Ok(4));
442 assert_eq!(get_max_cpus_per_package(4), Ok(4));
443 assert_eq!(get_max_cpus_per_package(5), Ok(8));
444 assert_eq!(get_max_cpus_per_package(8), Ok(8));
445 assert_eq!(get_max_cpus_per_package(9), Ok(16));
446 assert_eq!(get_max_cpus_per_package(16), Ok(16));
447 assert_eq!(get_max_cpus_per_package(17), Ok(32));
448 assert_eq!(get_max_cpus_per_package(32), Ok(32));
449 assert_eq!(get_max_cpus_per_package(33), Ok(64));
450 assert_eq!(get_max_cpus_per_package(64), Ok(64));
451 assert_eq!(get_max_cpus_per_package(65), Ok(128));
452 assert_eq!(get_max_cpus_per_package(128), Ok(128));
453 assert_eq!(
454 get_max_cpus_per_package(129),
455 Err(GetMaxCpusPerPackageError::Overflow)
456 );
457 assert_eq!(
458 get_max_cpus_per_package(u8::MAX),
459 Err(GetMaxCpusPerPackageError::Overflow)
460 );
461 }
462
463 #[test]
464 fn test_update_vendor_id() {
465 // Check `update_vendor_id()` passes through the vendor ID from the host correctly.
466
467 // Pseudo CPUID with invalid vendor ID.
468 let mut guest_cpuid = Cpuid::Intel(IntelCpuid(BTreeMap::from([(
469 CpuidKey {
470 leaf: 0x0,
471 subleaf: 0x0,
472 },
473 CpuidEntry {
474 flags: KvmCpuidFlags::EMPTY,
475 result: CpuidRegisters {
476 eax: 0,
477 ebx: 0x0123_4567,
478 ecx: 0x89ab_cdef,
479 edx: 0x55aa_55aa,
480 },
481 },
482 )])));
483
484 // Pass through vendor ID from host.
485 guest_cpuid.update_vendor_id().unwrap();
486
487 // Check if the guest vendor ID matches the host one.
488 let guest_leaf_0 = guest_cpuid
489 .get(&CpuidKey {
490 leaf: 0x0,
491 subleaf: 0x0,
492 })
493 .unwrap();
494 let host_leaf_0 = cpuid(0x0);
495 assert_eq!(guest_leaf_0.result.ebx, host_leaf_0.ebx);
496 assert_eq!(guest_leaf_0.result.ecx, host_leaf_0.ecx);
497 assert_eq!(guest_leaf_0.result.edx, host_leaf_0.edx);
498 }
499
500 #[test]
501 fn check_leaf_0xb_subleaf_0x1_added() {
502 // Check leaf 0xb / subleaf 0x1 is added in `update_extended_topology_entry()` even when it
503 // isn't included.
504
505 // Pseudo CPU setting
506 let smt = false;
507 let cpu_index = 0;
508 let cpu_count = 2;
509 let cpu_bits = u8::from(cpu_count > 1 && smt);
510 let cpus_per_core = 1u8
511 .checked_shl(u32::from(cpu_bits))
512 .ok_or(NormalizeCpuidError::CpuBits(cpu_bits))
513 .unwrap();
514
515 // Case 1: Intel CPUID
516 let mut intel_cpuid = Cpuid::Intel(IntelCpuid(BTreeMap::from([(
517 CpuidKey {
518 leaf: 0xb,
519 subleaf: 0,
520 },
521 CpuidEntry {
522 flags: KvmCpuidFlags::SIGNIFICANT_INDEX,
523 result: CpuidRegisters {
524 eax: 0,
525 ebx: 0,
526 ecx: 0,
527 edx: 0,
528 },
529 },
530 )])));
531 let result = intel_cpuid.update_extended_topology_entry(
532 cpu_index,
533 cpu_count,
534 cpu_bits,
535 cpus_per_core,
536 );
537 result.unwrap();
538 assert!(intel_cpuid.inner().contains_key(&CpuidKey {
539 leaf: 0xb,
540 subleaf: 0x1
541 }));
542
543 // Case 2: AMD CPUID
544 let mut amd_cpuid = Cpuid::Amd(AmdCpuid(BTreeMap::from([(
545 CpuidKey {
546 leaf: 0xb,
547 subleaf: 0,
548 },
549 CpuidEntry {
550 flags: KvmCpuidFlags::SIGNIFICANT_INDEX,
551 result: CpuidRegisters {
552 eax: 0,
553 ebx: 0,
554 ecx: 0,
555 edx: 0,
556 },
557 },
558 )])));
559 let result =
560 amd_cpuid.update_extended_topology_entry(cpu_index, cpu_count, cpu_bits, cpus_per_core);
561 result.unwrap();
562 assert!(amd_cpuid.inner().contains_key(&CpuidKey {
563 leaf: 0xb,
564 subleaf: 0x1
565 }));
566 }
567}