vmm/cpu_config/x86_64/cpuid/amd/
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::common::{GetCpuidError, get_vendor_id_from_host};
5use crate::cpu_config::x86_64::cpuid::normalize::{
6    CheckedAssignError, get_range, set_bit, set_range,
7};
8use crate::cpu_config::x86_64::cpuid::{
9    BRAND_STRING_LENGTH, CpuidEntry, CpuidKey, CpuidRegisters, CpuidTrait, KvmCpuidFlags,
10    MissingBrandStringLeaves, VENDOR_ID_AMD, cpuid, cpuid_count,
11};
12
13/// Error type for [`super::AmdCpuid::normalize`].
14#[allow(clippy::module_name_repetitions)]
15#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
16pub enum NormalizeCpuidError {
17    /// Provided `cpu_bits` is >=8: {0}.
18    CpuBits(u8),
19    /// Failed to passthrough cache topology: {0}
20    PassthroughCacheTopology(#[from] PassthroughCacheTopologyError),
21    /// Missing leaf 0x7 / subleaf 0.
22    MissingLeaf0x7Subleaf0,
23    /// Missing leaf 0x80000000.
24    MissingLeaf0x80000000,
25    /// Missing leaf 0x80000001.
26    MissingLeaf0x80000001,
27    /// Failed to set feature entry leaf: {0}
28    FeatureEntry(#[from] FeatureEntryError),
29    /// Failed to set extended cache topology leaf: {0}
30    ExtendedCacheTopology(#[from] ExtendedCacheTopologyError),
31    /// Failed to set extended APIC ID leaf: {0}
32    ExtendedApicId(#[from] ExtendedApicIdError),
33    /// Failed to set brand string: {0}
34    BrandString(MissingBrandStringLeaves),
35}
36
37/// Error type for setting cache topology section of [`super::AmdCpuid::normalize`].
38#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
39pub enum PassthroughCacheTopologyError {
40    /// Failed to get the host vendor id: {0}
41    NoVendorId(GetCpuidError),
42    /// The host vendor id does not match AMD.
43    BadVendorId,
44}
45
46/// Error type for setting leaf 0x80000008 section of [`super::AmdCpuid::normalize`].
47#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
48pub enum FeatureEntryError {
49    /// Missing leaf 0x80000008.
50    MissingLeaf0x80000008,
51    /// Failed to set number of physical threads (CPUID.80000008H:ECX[7:0]): {0}
52    NumberOfPhysicalThreads(CheckedAssignError),
53    /// Failed to set number of physical threads (CPUID.80000008H:ECX[7:0]) due to overflow.
54    NumberOfPhysicalThreadsOverflow,
55}
56
57/// Error type for setting leaf 0x8000001d section of [`super::AmdCpuid::normalize`].
58#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
59pub enum ExtendedCacheTopologyError {
60    /// Missing leaf 0x8000001d.
61    MissingLeaf0x8000001d,
62    #[rustfmt::skip]
63    /// Failed to set number of logical processors sharing cache(CPUID.(EAX=8000001DH,ECX={0}):EAX[25:14]): {1}
64    NumSharingCache(u32, CheckedAssignError),
65    #[rustfmt::skip]
66    /// Failed to set number of logical processors sharing cache (CPUID.(EAX=8000001DH,ECX={0}):EAX[25:14]) due to overflow.
67    NumSharingCacheOverflow(u32),
68}
69
70/// Error type for setting leaf 0x8000001e section of [`super::AmdCpuid::normalize`].
71#[derive(Debug, thiserror::Error, displaydoc::Display, Eq, PartialEq)]
72pub enum ExtendedApicIdError {
73    /// Failed to set compute unit ID (CPUID.8000001EH:EBX[7:0]): {0}
74    ComputeUnitId(CheckedAssignError),
75    /// Failed to set extended APIC ID (CPUID.8000001EH:EAX[31:0]): {0}
76    ExtendedApicId(CheckedAssignError),
77    /// Missing leaf 0x8000001e.
78    MissingLeaf0x8000001e,
79    /// Failed to set threads per core unit (CPUID:8000001EH:EBX[15:8]): {0}
80    ThreadPerComputeUnit(CheckedAssignError),
81}
82
83// We use this 2nd implementation so we can conveniently define functions only used within
84// `normalize`.
85#[allow(clippy::multiple_inherent_impl)]
86impl super::AmdCpuid {
87    /// We always use this brand string.
88    const DEFAULT_BRAND_STRING: &'static [u8; BRAND_STRING_LENGTH] =
89        b"AMD EPYC\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
90
91    /// Applies required modifications to CPUID respective of a vCPU.
92    ///
93    /// # Errors
94    ///
95    /// When attempting to access missing leaves or set fields within leaves to values that don't
96    /// fit.
97    #[inline]
98    pub fn normalize(
99        &mut self,
100        // The index of the current logical CPU in the range [0..cpu_count].
101        cpu_index: u8,
102        // The total number of logical CPUs.
103        cpu_count: u8,
104        // The number of logical CPUs per core.
105        cpus_per_core: u8,
106    ) -> Result<(), NormalizeCpuidError> {
107        self.passthrough_cache_topology()?;
108        self.update_structured_extended_entry()?;
109        self.update_extended_feature_fn_entry()?;
110        self.update_amd_feature_entry(cpu_count)?;
111        self.update_extended_cache_topology_entry(cpu_count, cpus_per_core)?;
112        self.update_extended_apic_id_entry(cpu_index, cpus_per_core)?;
113        self.update_brand_string_entry()?;
114
115        Ok(())
116    }
117
118    /// Passthrough cache topology.
119    ///
120    /// # Errors
121    ///
122    /// This function passes through leaves from the host CPUID, if this does not match the AMD
123    /// specification it is possible to enter an indefinite loop. To avoid this, this will return an
124    /// error when the host CPUID vendor id does not match the AMD CPUID vendor id.
125    fn passthrough_cache_topology(&mut self) -> Result<(), PassthroughCacheTopologyError> {
126        if get_vendor_id_from_host().map_err(PassthroughCacheTopologyError::NoVendorId)?
127            != *VENDOR_ID_AMD
128        {
129            return Err(PassthroughCacheTopologyError::BadVendorId);
130        }
131
132        // Pass-through host CPUID for leaves 0x8000001e and 0x8000001d.
133        {
134            // 0x8000001e - Processor Topology Information
135            self.0.insert(
136                CpuidKey::leaf(0x8000001e),
137                CpuidEntry {
138                    flags: KvmCpuidFlags::EMPTY,
139                    result: CpuidRegisters::from(cpuid(0x8000001e)),
140                },
141            );
142
143            // 0x8000001d - Cache Topology Information
144            for subleaf in 0.. {
145                let result = CpuidRegisters::from(cpuid_count(0x8000001d, subleaf));
146                // From 'AMD64 Architecture Programmer’s Manual Volume 3: General-Purpose and System
147                // Instructions':
148                //
149                // > To gather information for all cache levels, software must repeatedly execute
150                // > CPUID with 8000_001Dh in EAX and ECX set to increasing values beginning with 0
151                // > until a value of 00h is returned in the field CacheType (EAX[4:0]) indicating
152                // > no more cache descriptions are available for this processor. If CPUID
153                // > Fn8000_0001_ECX[TopologyExtensions] = 0, then CPUID Fn8000_001Dh is reserved.
154                //
155                // On non-AMD hosts this condition may never be true thus this loop may be
156                // indefinite.
157
158                // CPUID Fn8000_0001D_EAX_x[4:0] (Field Name: CacheType)
159                // Cache type. Identifies the type of cache.
160                // ```text
161                // Bits Description
162                // 00h Null; no more caches.
163                // 01h Data cache
164                // 02h Instruction cache
165                // 03h Unified cache
166                // 1Fh-04h Reserved.
167                // ```
168                let cache_type = result.eax & 15;
169                if cache_type == 0 {
170                    break;
171                }
172                self.0.insert(
173                    CpuidKey::subleaf(0x8000001d, subleaf),
174                    CpuidEntry {
175                        flags: KvmCpuidFlags::SIGNIFICANT_INDEX,
176                        result,
177                    },
178                );
179            }
180        }
181        Ok(())
182    }
183
184    /// Updated extended feature fn entry.
185    fn update_extended_feature_fn_entry(&mut self) -> Result<(), NormalizeCpuidError> {
186        // set the Topology Extension bit since we use the Extended Cache Topology leaf
187        let leaf_80000001 = self
188            .get_mut(&CpuidKey::leaf(0x80000001))
189            .ok_or(NormalizeCpuidError::MissingLeaf0x80000001)?;
190        // CPUID Fn8000_0001_ECX[22] (Field Name: TopologyExtensions)
191        // Topology extensions support. Indicates support for CPUID Fn8000_001D_EAX_x[N:0]-CPUID
192        // Fn8000_001E_EDX.
193        set_bit(&mut leaf_80000001.result.ecx, 22, true);
194        Ok(())
195    }
196
197    // Update structured extended feature entry.
198    fn update_structured_extended_entry(&mut self) -> Result<(), NormalizeCpuidError> {
199        let leaf_7_subleaf_0 = self
200            .get_mut(&CpuidKey::subleaf(0x7, 0x0))
201            .ok_or(NormalizeCpuidError::MissingLeaf0x7Subleaf0)?;
202
203        // According to AMD64 Architecture Programmer’s Manual, IA32_ARCH_CAPABILITIES MSR is not
204        // available on AMD. The availability of IA32_ARCH_CAPABILITIES MSR is controlled via
205        // CPUID.07H(ECX=0):EDX[bit 29]. KVM sets this bit no matter what but this feature is not
206        // supported by hardware.
207        set_bit(&mut leaf_7_subleaf_0.result.edx, 29, false);
208        Ok(())
209    }
210
211    /// Update AMD feature entry.
212    #[allow(clippy::unwrap_used, clippy::unwrap_in_result)]
213    fn update_amd_feature_entry(&mut self, cpu_count: u8) -> Result<(), FeatureEntryError> {
214        /// This value allows at most 64 logical threads within a package.
215        const THREAD_ID_MAX_SIZE: u32 = 7;
216
217        // We don't support more then 128 threads right now.
218        // It's safe to put them all on the same processor.
219        let leaf_80000008 = self
220            .get_mut(&CpuidKey::leaf(0x80000008))
221            .ok_or(FeatureEntryError::MissingLeaf0x80000008)?;
222
223        // CPUID Fn8000_0008_ECX[15:12] (Field Name: ApicIdSize)
224        // APIC ID size. The number of bits in the initial APIC20[ApicId] value that indicate
225        // logical processor ID within a package. The size of this field determines the
226        // maximum number of logical processors (MNLP) that the package could
227        // theoretically support, and not the actual number of logical processors that are
228        // implemented or enabled in the package, as indicated by CPUID
229        // Fn8000_0008_ECX[NC]. A value of zero indicates that legacy methods must be
230        // used to determine the maximum number of logical processors, as indicated by
231        // CPUID Fn8000_0008_ECX[NC].
232        set_range(&mut leaf_80000008.result.ecx, 12..=15, THREAD_ID_MAX_SIZE).unwrap();
233
234        // CPUID Fn8000_0008_ECX[7:0] (Field Name: NC)
235        // Number of physical threads - 1. The number of threads in the processor is NT+1
236        // (e.g., if NT = 0, then there is one thread). See “Legacy Method” on page 633.
237        let sub = cpu_count
238            .checked_sub(1)
239            .ok_or(FeatureEntryError::NumberOfPhysicalThreadsOverflow)?;
240        set_range(&mut leaf_80000008.result.ecx, 0..=7, u32::from(sub))
241            .map_err(FeatureEntryError::NumberOfPhysicalThreads)?;
242
243        Ok(())
244    }
245
246    /// Update extended cache topology entry.
247    #[allow(clippy::unwrap_in_result, clippy::unwrap_used)]
248    fn update_extended_cache_topology_entry(
249        &mut self,
250        cpu_count: u8,
251        cpus_per_core: u8,
252    ) -> Result<(), ExtendedCacheTopologyError> {
253        for i in 0.. {
254            if let Some(subleaf) = self.get_mut(&CpuidKey::subleaf(0x8000001d, i)) {
255                // CPUID Fn8000_001D_EAX_x[7:5] (Field Name: CacheLevel)
256                // Cache level. Identifies the level of this cache. Note that the enumeration value
257                // is not necessarily equal to the cache level.
258                // ```text
259                // Bits Description
260                // 000b Reserved.
261                // 001b Level 1
262                // 010b Level 2
263                // 011b Level 3
264                // 111b-100b Reserved.
265                // ```
266                let cache_level = get_range(subleaf.result.eax, 5..=7);
267
268                // CPUID Fn8000_001D_EAX_x[25:14] (Field Name: NumSharingCache)
269                // Specifies the number of logical processors sharing the cache enumerated by N,
270                // the value passed to the instruction in ECX. The number of logical processors
271                // sharing this cache is the value of this field incremented by 1. To determine
272                // which logical processors are sharing a cache, determine a Share
273                // Id for each processor as follows:
274                //
275                // ShareId = LocalApicId >> log2(NumSharingCache+1)
276                //
277                // Logical processors with the same ShareId then share a cache. If
278                // NumSharingCache+1 is not a power of two, round it up to the next power of two.
279
280                match cache_level {
281                    // L1 & L2 Cache
282                    // The L1 & L2 cache is shared by at most 2 hyper-threads
283                    1 | 2 => {
284                        // SAFETY: We know `cpus_per_core > 0` therefore this is always safe.
285                        let sub = u32::from(cpus_per_core.checked_sub(1).unwrap());
286                        set_range(&mut subleaf.result.eax, 14..=25, sub)
287                            .map_err(|err| ExtendedCacheTopologyError::NumSharingCache(i, err))?;
288                    }
289                    // L3 Cache
290                    // The L3 cache is shared among all the logical threads
291                    3 => {
292                        let sub = cpu_count
293                            .checked_sub(1)
294                            .ok_or(ExtendedCacheTopologyError::NumSharingCacheOverflow(i))?;
295                        set_range(&mut subleaf.result.eax, 14..=25, u32::from(sub))
296                            .map_err(|err| ExtendedCacheTopologyError::NumSharingCache(i, err))?;
297                    }
298                    _ => (),
299                }
300            } else {
301                break;
302            }
303        }
304        Ok(())
305    }
306
307    /// Update extended apic id entry
308    #[allow(clippy::unwrap_used, clippy::unwrap_in_result)]
309    fn update_extended_apic_id_entry(
310        &mut self,
311        cpu_index: u8,
312        cpus_per_core: u8,
313    ) -> Result<(), ExtendedApicIdError> {
314        /// 1 node per processor.
315        const NODES_PER_PROCESSOR: u32 = 0;
316
317        // When hyper-threading is enabled each pair of 2 consecutive logical CPUs
318        // will have the same core id since they represent 2 threads in the same core.
319        // For Example:
320        // logical CPU 0 -> core id: 0
321        // logical CPU 1 -> core id: 0
322        // logical CPU 2 -> core id: 1
323        // logical CPU 3 -> core id: 1
324        //
325        // SAFETY: We know `cpus_per_core != 0` therefore this is always safe.
326        let core_id = u32::from(cpu_index.checked_div(cpus_per_core).unwrap());
327
328        let leaf_8000001e = self
329            .get_mut(&CpuidKey::leaf(0x8000001e))
330            .ok_or(ExtendedApicIdError::MissingLeaf0x8000001e)?;
331
332        // CPUID Fn8000_001E_EAX[31:0] (Field Name: ExtendedApicId)
333        // Extended APIC ID. If MSR0000_001B[ApicEn] = 0, this field is reserved.
334        set_range(&mut leaf_8000001e.result.eax, 0..=31, u32::from(cpu_index))
335            .map_err(ExtendedApicIdError::ExtendedApicId)?;
336
337        // CPUID Fn8000_001E_EBX[7:0] (Field Name: ComputeUnitId)
338        // Compute unit ID. Identifies a Compute Unit, which may be one or more physical cores that
339        // each implement one or more logical processors.
340        set_range(&mut leaf_8000001e.result.ebx, 0..=7, core_id)
341            .map_err(ExtendedApicIdError::ComputeUnitId)?;
342
343        // CPUID Fn8000_001E_EBX[15:8] (Field Name: ThreadsPerComputeUnit)
344        // Threads per compute unit (zero-based count). The actual number of threads
345        // per compute unit is the value of this field + 1. To determine which logical
346        // processors (threads) belong to a given Compute Unit, determine a ShareId
347        // for each processor as follows:
348        //
349        // ShareId = LocalApicId >> log2(ThreadsPerComputeUnit+1)
350        //
351        // Logical processors with the same ShareId then belong to the same Compute
352        // Unit. (If ThreadsPerComputeUnit+1 is not a power of two, round it up to the
353        // next power of two).
354        //
355        // SAFETY: We know `cpus_per_core > 0` therefore this is always safe.
356        let sub = u32::from(cpus_per_core.checked_sub(1).unwrap());
357        set_range(&mut leaf_8000001e.result.ebx, 8..=15, sub)
358            .map_err(ExtendedApicIdError::ThreadPerComputeUnit)?;
359
360        // CPUID Fn8000_001E_ECX[10:8] (Field Name: NodesPerProcessor)
361        // Specifies the number of nodes in the package/socket in which this logical
362        // processor resides. Node in this context corresponds to a processor die.
363        // Encoding is N-1, where N is the number of nodes present in the socket.
364        //
365        // SAFETY: We know the value always fits within the range and thus is always safe.
366        // Set nodes per processor.
367        set_range(&mut leaf_8000001e.result.ecx, 8..=10, NODES_PER_PROCESSOR).unwrap();
368
369        // CPUID Fn8000_001E_ECX[7:0] (Field Name: NodeId)
370        // Specifies the ID of the node containing the current logical processor. NodeId
371        // values are unique across the system.
372        //
373        // Put all the cpus in the same node.
374        set_range(&mut leaf_8000001e.result.ecx, 0..=7, 0).unwrap();
375
376        Ok(())
377    }
378
379    /// Update brand string entry
380    fn update_brand_string_entry(&mut self) -> Result<(), NormalizeCpuidError> {
381        self.apply_brand_string(Self::DEFAULT_BRAND_STRING)
382            .map_err(NormalizeCpuidError::BrandString)?;
383        Ok(())
384    }
385}
386
387#[cfg(test)]
388mod tests {
389
390    use std::collections::BTreeMap;
391
392    use super::*;
393    use crate::cpu_config::x86_64::cpuid::AmdCpuid;
394
395    #[test]
396    fn test_update_structured_extended_entry_invalid() {
397        // `update_structured_extended_entry()` should exit with MissingLeaf0x7Subleaf0 error for
398        // CPUID lacking leaf 0x7 / subleaf 0.
399        let mut cpuid = AmdCpuid(BTreeMap::new());
400        assert_eq!(
401            cpuid.update_structured_extended_entry().unwrap_err(),
402            NormalizeCpuidError::MissingLeaf0x7Subleaf0
403        );
404    }
405
406    #[test]
407    fn test_update_structured_extended_entry_valid() {
408        // `update_structured_extended_entry()` should succeed for CPUID having leaf 0x7 / subleaf
409        // 0, and bit 29 of EDX (IA32_ARCH_CAPABILITIES MSR enumeration) should be disabled.
410        let mut cpuid = AmdCpuid(BTreeMap::from([(
411            CpuidKey {
412                leaf: 0x7,
413                subleaf: 0x0,
414            },
415            CpuidEntry {
416                flags: KvmCpuidFlags::SIGNIFICANT_INDEX,
417                result: CpuidRegisters {
418                    eax: 0,
419                    ebx: 0,
420                    ecx: 0,
421                    edx: u32::MAX,
422                },
423            },
424        )]));
425        cpuid.update_structured_extended_entry().unwrap();
426        assert_eq!(
427            cpuid
428                .get(&CpuidKey {
429                    leaf: 0x7,
430                    subleaf: 0x0
431                })
432                .unwrap()
433                .result
434                .edx
435                & (1 << 29),
436            0
437        );
438    }
439}