Skip to main content

virt/x86/
topology.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Provides processor topology related cpuid leaves.
5
6use crate::CpuidLeaf;
7use std::cmp::min;
8use thiserror::Error;
9use vm_topology::processor::ProcessorTopology;
10use x86defs::cpuid::CacheParametersEax;
11use x86defs::cpuid::CpuidFunction;
12use x86defs::cpuid::ExtendedAddressSpaceSizesEcx;
13use x86defs::cpuid::ExtendedTopologyEax;
14use x86defs::cpuid::ExtendedTopologyEbx;
15use x86defs::cpuid::ExtendedTopologyEcx;
16use x86defs::cpuid::ProcessorTopologyDefinitionEbx;
17use x86defs::cpuid::ProcessorTopologyDefinitionEcx;
18use x86defs::cpuid::TopologyLevelType;
19use x86defs::cpuid::Vendor;
20use x86defs::cpuid::VendorAndMaxFunctionEax;
21use x86defs::cpuid::VersionAndFeaturesEbx;
22
23/// A function used to query the cpuid result for a given input value (`eax`,
24/// `ecx`).
25pub type CpuidFn<'a> = &'a dyn Fn(u32, u32) -> [u32; 4];
26
27#[derive(Debug, Error)]
28#[error("unknown processor vendor {0}")]
29pub struct UnknownVendor(Vendor);
30
31/// Adds appropriately masked leaves for reporting processor topology.
32///
33/// This includes some bits of leaves 01h and 04h, plus all of leaves 0Bh and
34/// 1Fh
35pub fn topology_cpuid<'a>(
36    topology: &'a ProcessorTopology,
37    cpuid: CpuidFn<'a>,
38    leaves: &mut Vec<CpuidLeaf>,
39) -> Result<(), UnknownVendor> {
40    let result = cpuid(CpuidFunction::VendorAndMaxFunction.0, 0);
41    let max = VendorAndMaxFunctionEax::from(result[0]).max_function();
42    let vendor = Vendor::from_ebx_ecx_edx(result[1], result[2], result[3]);
43    if !vendor.is_intel_compatible() && !vendor.is_amd_compatible() {
44        return Err(UnknownVendor(vendor));
45    };
46
47    // Set the number of VPs per socket in leaf 01h.
48    leaves.push(
49        CpuidLeaf::new(
50            CpuidFunction::VersionAndFeatures.0,
51            [
52                0,
53                VersionAndFeaturesEbx::new()
54                    .with_lps_per_package(topology.reserved_vps_per_socket() as u8)
55                    .into(),
56                0,
57                0,
58            ],
59        )
60        .masked([
61            0,
62            VersionAndFeaturesEbx::new()
63                .with_lps_per_package(0xff)
64                .into(),
65            0,
66            0,
67        ]),
68    );
69
70    // Set leaf 04h for Intel processors.
71    if vendor.is_intel_compatible() {
72        cache_parameters_cpuid(topology, cpuid, leaves);
73    }
74
75    // Set leaf 0bh.
76    extended_topology_cpuid(topology, CpuidFunction::ExtendedTopologyEnumeration, leaves);
77
78    // Set leaf 1fh if requested.
79    if max >= CpuidFunction::V2ExtendedTopologyEnumeration.0 {
80        extended_topology_cpuid(
81            topology,
82            CpuidFunction::V2ExtendedTopologyEnumeration,
83            leaves,
84        );
85    }
86
87    if vendor.is_amd_compatible() {
88        // Add AMD-specific topology leaves here.
89        amd_extended_address_space_sizes_cpuid(topology, leaves);
90        amd_processor_topology_definition_cpuid(topology, leaves);
91    }
92
93    Ok(())
94}
95
96/// Adds subleaves for leaf 04h.
97///
98/// Only valid for Intel processors.
99fn cache_parameters_cpuid(
100    topology: &ProcessorTopology,
101    cpuid: CpuidFn<'_>,
102    leaves: &mut Vec<CpuidLeaf>,
103) {
104    for i in 0..=255 {
105        let result = cpuid(CpuidFunction::CacheParameters.0, i);
106        if result == [0; 4] {
107            break;
108        }
109        let mut eax = CacheParametersEax::new();
110        // Only 6 bits are available in the cache parameters CPUID leaf (04H)
111        // so use a saturated value here as the maximum to avoid a panic later.
112        const MAX_CORES_PER_SOCKET_MINUS_ONE: u32 = 0b111111;
113        if topology.smt_enabled() {
114            eax.set_cores_per_socket_minus_one(min(
115                MAX_CORES_PER_SOCKET_MINUS_ONE,
116                topology.reserved_vps_per_socket() / 2 - 1,
117            ));
118            eax.set_threads_sharing_cache_minus_one(1);
119        } else {
120            eax.set_cores_per_socket_minus_one(min(
121                MAX_CORES_PER_SOCKET_MINUS_ONE,
122                topology.reserved_vps_per_socket() - 1,
123            ));
124            eax.set_threads_sharing_cache_minus_one(0);
125        }
126
127        // The level 3 cache is not per-VP; indicate that it is per-socket.
128        if eax.cache_level() == 3 {
129            eax.set_threads_sharing_cache_minus_one(topology.reserved_vps_per_socket() - 1);
130        }
131
132        let eax_mask = CacheParametersEax::new()
133            .with_cores_per_socket_minus_one(0x3f)
134            .with_threads_sharing_cache_minus_one(0xfff);
135
136        leaves.push(
137            CpuidLeaf::new(CpuidFunction::CacheParameters.0, [eax.into(), 0, 0, 0]).masked([
138                eax_mask.into(),
139                0,
140                0,
141                0,
142            ]),
143        )
144    }
145}
146
147/// Returns topology information in cpuid format (0Bh and 1Fh leaves).
148///
149/// The x2APIC values in edx will be zero. The caller will need to ensure
150/// these are set correctly for each VP.
151fn extended_topology_cpuid(
152    topology: &ProcessorTopology,
153    function: CpuidFunction,
154    leaves: &mut Vec<CpuidLeaf>,
155) {
156    assert!(
157        function == CpuidFunction::ExtendedTopologyEnumeration
158            || function == CpuidFunction::V2ExtendedTopologyEnumeration
159    );
160    for (index, (level_type, num_lps)) in [
161        (
162            TopologyLevelType::SMT,
163            if topology.smt_enabled() { 2 } else { 1 },
164        ),
165        (TopologyLevelType::CORE, topology.reserved_vps_per_socket()),
166    ]
167    .into_iter()
168    .enumerate()
169    {
170        if level_type <= TopologyLevelType::CORE
171            || function == CpuidFunction::V2ExtendedTopologyEnumeration
172        {
173            let eax = ExtendedTopologyEax::new().with_x2_apic_shift(num_lps.trailing_zeros());
174            let ebx = ExtendedTopologyEbx::new().with_num_lps(num_lps as u16);
175            let ecx = ExtendedTopologyEcx::new()
176                .with_level_number(index as u8)
177                .with_level_type(level_type.0);
178
179            // Don't include edx in the mask: it is the x2APIC ID, which
180            // must be filled in by the caller separately for each VP.
181            leaves.push(
182                CpuidLeaf::new(function.0, [eax.into(), ebx.into(), ecx.into(), 0])
183                    .indexed(index as u32)
184                    .masked([!0, !0, !0, 0]),
185            );
186        }
187    }
188}
189
190/// Adds leaf 80000008h (Extended Address Space Sizes) for AMD processors.
191///
192/// This leaf contains core count and APIC ID size information.
193fn amd_extended_address_space_sizes_cpuid(
194    topology: &ProcessorTopology,
195    leaves: &mut Vec<CpuidLeaf>,
196) {
197    let nc = (topology.reserved_vps_per_socket() - 1) as u8;
198    let apic_core_id_size = topology.reserved_vps_per_socket().trailing_zeros() as u8;
199    let ecx = ExtendedAddressSpaceSizesEcx::new()
200        .with_nc(nc)
201        .with_apic_core_id_size(apic_core_id_size);
202
203    let ecx_mask = ExtendedAddressSpaceSizesEcx::new()
204        .with_nc(0xff)
205        .with_apic_core_id_size(0xf);
206
207    leaves.push(
208        CpuidLeaf::new(
209            CpuidFunction::ExtendedAddressSpaceSizes.0,
210            [0, 0, ecx.into(), 0],
211        )
212        .masked([0, 0, ecx_mask.into(), 0]),
213    );
214}
215
216/// Adds leaf 8000001Eh (Processor Topology Definition) for AMD processors.
217fn amd_processor_topology_definition_cpuid(
218    topology: &ProcessorTopology,
219    leaves: &mut Vec<CpuidLeaf>,
220) {
221    // threads_per_compute_unit is (threads per core - 1).
222    let threads_per_compute_unit = if topology.smt_enabled() { 1 } else { 0 };
223    let ebx = ProcessorTopologyDefinitionEbx::new()
224        .with_threads_per_compute_unit(threads_per_compute_unit);
225
226    let ebx_mask = ProcessorTopologyDefinitionEbx::new().with_threads_per_compute_unit(!0);
227
228    // TODO: support AMD's nodes per socket concept.
229    let ecx = ProcessorTopologyDefinitionEcx::new().with_nodes_per_processor(0);
230    let ecx_mask = ProcessorTopologyDefinitionEcx::new().with_nodes_per_processor(0x7);
231
232    leaves.push(
233        CpuidLeaf::new(
234            CpuidFunction::ProcessorTopologyDefinition.0,
235            [0, ebx.into(), ecx.into(), 0],
236        )
237        .masked([0, ebx_mask.into(), ecx_mask.into(), 0]),
238    );
239}