x86_64/
cpuid.rs

1// Copyright 2017 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::arch::x86_64::CpuidResult;
6use std::arch::x86_64::__cpuid;
7use std::arch::x86_64::__cpuid_count;
8use std::cmp;
9use std::result;
10
11use devices::Apic;
12use devices::IrqChipCap;
13use devices::IrqChipX86_64;
14use hypervisor::CpuConfigX86_64;
15use hypervisor::CpuHybridType;
16use hypervisor::CpuIdEntry;
17use hypervisor::HypervisorCap;
18use hypervisor::HypervisorX86_64;
19use hypervisor::VcpuX86_64;
20use remain::sorted;
21use thiserror::Error;
22
23use crate::CpuManufacturer;
24
25#[sorted]
26#[derive(Error, Debug, PartialEq, Eq)]
27pub enum Error {
28    #[error("GetSupportedCpus ioctl failed: {0}")]
29    GetSupportedCpusFailed(base::Error),
30    #[error("SetSupportedCpus ioctl failed: {0}")]
31    SetSupportedCpusFailed(base::Error),
32}
33
34pub type Result<T> = result::Result<T, Error>;
35
36// CPUID bits in ebx, ecx, and edx.
37pub const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
38pub const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH.
39pub const EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU.
40pub const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU.
41pub const ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit.
42pub const ECX_X2APIC_SHIFT: u32 = 21; // APIC supports extended xAPIC (x2APIC) standard.
43pub const ECX_TSC_DEADLINE_TIMER_SHIFT: u32 = 24; // TSC deadline mode of APIC timer.
44pub const ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor.
45pub const EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled.
46pub const ECX_TOPO_TYPE_SHIFT: u32 = 8; // Topology Level type.
47pub const ECX_TOPO_SMT_TYPE: u32 = 1; // SMT type.
48pub const ECX_TOPO_CORE_TYPE: u32 = 2; // CORE type.
49pub const ECX_HCFC_PERF_SHIFT: u32 = 0; // Presence of IA32_MPERF and IA32_APERF.
50pub const EAX_CPU_CORES_SHIFT: u32 = 26; // Index of cpu cores in the same physical package.
51pub const EDX_HYBRID_CPU_SHIFT: u32 = 15; // Hybrid. The processor is identified as a hybrid part.
52pub const EAX_HWP_SHIFT: u32 = 7; // Intel Hardware P-states.
53pub const EAX_HWP_NOTIFICATION_SHIFT: u32 = 8; // IA32_HWP_INTERRUPT MSR is supported
54pub const EAX_HWP_EPP_SHIFT: u32 = 10; // HWP Energy Perf. Preference.
55pub const EAX_ITMT_SHIFT: u32 = 14; // Intel Turbo Boost Max Technology 3.0 available.
56pub const EAX_CORE_TEMP: u32 = 0; // Core Temperature
57pub const EAX_PKG_TEMP: u32 = 6; // Package Temperature
58pub const EAX_CORE_TYPE_SHIFT: u32 = 24; // Hybrid information. Hybrid core type.
59
60const EAX_CORE_TYPE_ATOM: u32 = 0x20; // Hybrid Atom CPU.
61const EAX_CORE_TYPE_CORE: u32 = 0x40; // Hybrid Core CPU.
62
63/// All of the context required to emulate the CPUID instruction.
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct CpuIdContext {
66    /// Id of the Vcpu associated with this context.
67    vcpu_id: usize,
68    /// The total number of vcpus on this VM.
69    cpu_count: usize,
70    /// Whether or not the IrqChip's APICs support X2APIC.
71    x2apic: bool,
72    /// Whether or not the IrqChip's APICs support a TSC deadline timer.
73    tsc_deadline_timer: bool,
74    /// The frequency at which the IrqChip's APICs run.
75    apic_frequency: u32,
76    /// The TSC frequency in Hz, if it could be determined.
77    tsc_frequency: Option<u64>,
78    /// CPU feature configurations.
79    cpu_config: CpuConfigX86_64,
80    /// __cpuid_count or a fake function for test.
81    cpuid_count: unsafe fn(u32, u32) -> CpuidResult,
82    /// __cpuid or a fake function for test.
83    cpuid: unsafe fn(u32) -> CpuidResult,
84}
85
86impl CpuIdContext {
87    pub fn new(
88        vcpu_id: usize,
89        cpu_count: usize,
90        irq_chip: Option<&dyn IrqChipX86_64>,
91        cpu_config: CpuConfigX86_64,
92        calibrated_tsc_leaf_required: bool,
93        cpuid_count: unsafe fn(u32, u32) -> CpuidResult,
94        cpuid: unsafe fn(u32) -> CpuidResult,
95    ) -> CpuIdContext {
96        CpuIdContext {
97            vcpu_id,
98            cpu_count,
99            x2apic: irq_chip.is_some_and(|chip| chip.check_capability(IrqChipCap::X2Apic)),
100            tsc_deadline_timer: irq_chip
101                .is_some_and(|chip| chip.check_capability(IrqChipCap::TscDeadlineTimer)),
102            apic_frequency: irq_chip.map_or(Apic::frequency(), |chip| chip.lapic_frequency()),
103            tsc_frequency: if calibrated_tsc_leaf_required || cpu_config.force_calibrated_tsc_leaf {
104                devices::tsc::tsc_frequency().ok()
105            } else {
106                None
107            },
108            cpu_config,
109            cpuid_count,
110            cpuid,
111        }
112    }
113}
114
115/// Adjust a CPUID instruction result to return values that work with crosvm.
116///
117/// Given an input CpuIdEntry `entry`, which represents what the Hypervisor would normally return
118/// for a given CPUID instruction result, adjust that result to reflect the capabilities of crosvm.
119/// The `ctx` argument contains all of the Vm-specific and Vcpu-specific information required to
120/// return the appropriate results.
121pub fn adjust_cpuid(entry: &mut CpuIdEntry, ctx: &CpuIdContext) {
122    match entry.function {
123        0 => {
124            if ctx.tsc_frequency.is_some() {
125                // We add leaf 0x15 for the TSC frequency if it is available.
126                entry.cpuid.eax = cmp::max(0x15, entry.cpuid.eax);
127            }
128        }
129        1 => {
130            // X86 hypervisor feature
131            if entry.index == 0 {
132                entry.cpuid.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
133            }
134            if ctx.x2apic {
135                entry.cpuid.ecx |= 1 << ECX_X2APIC_SHIFT;
136            } else {
137                entry.cpuid.ecx &= !(1 << ECX_X2APIC_SHIFT);
138            }
139            if ctx.tsc_deadline_timer {
140                entry.cpuid.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT;
141            }
142
143            if ctx.cpu_config.host_cpu_topology {
144                entry.cpuid.ebx |= EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT;
145
146                // Expose HT flag to Guest.
147                // SAFETY: trivially safe
148                let result = unsafe { (ctx.cpuid)(entry.function) };
149                entry.cpuid.edx |= result.edx & (1 << EDX_HTT_SHIFT);
150                return;
151            }
152
153            entry.cpuid.ebx = (ctx.vcpu_id << EBX_CPUID_SHIFT) as u32
154                | (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
155            if ctx.cpu_count > 1 {
156                // This field is only valid if CPUID.1.EDX.HTT[bit 28]= 1.
157                entry.cpuid.ebx |= (ctx.cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
158                // A value of 0 for HTT indicates there is only a single logical
159                // processor in the package and software should assume only a
160                // single APIC ID is reserved.
161                entry.cpuid.edx |= 1 << EDX_HTT_SHIFT;
162            }
163        }
164        2 | // Cache and TLB Descriptor information
165        0x80000002 | 0x80000003 | 0x80000004 | // Processor Brand String
166        0x80000005 | 0x80000006 // L1 and L2 cache information
167            => entry.cpuid = {
168                // SAFETY: trivially safe
169                unsafe { (ctx.cpuid)(entry.function) }},
170        4 => {
171            entry.cpuid = {
172                // SAFETY: trivially safe
173                unsafe { (ctx.cpuid_count)(entry.function, entry.index) }};
174
175            if ctx.cpu_config.host_cpu_topology {
176                return;
177            }
178
179            entry.cpuid.eax &= !0xFC000000;
180            if ctx.cpu_count > 1 {
181                let cpu_cores = if ctx.cpu_config.no_smt {
182                    ctx.cpu_count as u32
183                } else if ctx.cpu_count % 2 == 0 {
184                    (ctx.cpu_count >> 1) as u32
185                } else {
186                    1
187                };
188                entry.cpuid.eax |= (cpu_cores - 1) << EAX_CPU_CORES_SHIFT;
189            }
190        }
191        6 => {
192            let result = {
193                // SAFETY:
194                // Safe because we pass 6 for this call and the host
195                // supports the `cpuid` instruction
196                unsafe { (ctx.cpuid)(entry.function) }};
197
198            if ctx.cpu_config.enable_hwp {
199                entry.cpuid.eax |= result.eax & (1 << EAX_HWP_SHIFT);
200                entry.cpuid.eax |= result.eax & (1 << EAX_HWP_NOTIFICATION_SHIFT);
201                entry.cpuid.eax |= result.eax & (1 << EAX_HWP_EPP_SHIFT);
202                entry.cpuid.ecx |= result.ecx & (1 << ECX_EPB_SHIFT);
203
204                if ctx.cpu_config.itmt {
205                    entry.cpuid.eax |= result.eax & (1 << EAX_ITMT_SHIFT);
206                }
207            }
208        }
209        7 => {
210            if ctx.cpu_config.host_cpu_topology && entry.index == 0 {
211                // SAFETY:
212                // Safe because we pass 7 and 0 for this call and the host supports the
213                // `cpuid` instruction
214                let result = unsafe { (ctx.cpuid_count)(entry.function, entry.index) };
215                entry.cpuid.edx |= result.edx & (1 << EDX_HYBRID_CPU_SHIFT);
216            }
217            if ctx.cpu_config.hybrid_type.is_some() && entry.index == 0 {
218                entry.cpuid.edx |= 1 << EDX_HYBRID_CPU_SHIFT;
219            }
220        }
221        0x15 => {
222            if let Some(tsc_freq) = ctx.tsc_frequency {
223                // A calibrated TSC is required by the hypervisor or was forced by the user.
224                entry.cpuid = devices::tsc::fake_tsc_frequency_cpuid(tsc_freq, ctx.apic_frequency);
225            }
226        }
227        0x1A => {
228            // Hybrid information leaf.
229            if ctx.cpu_config.host_cpu_topology {
230                // SAFETY:
231                // Safe because we pass 0x1A for this call and the host supports the
232                // `cpuid` instruction
233                entry.cpuid = unsafe { (ctx.cpuid)(entry.function) };
234            }
235            if let Some(hybrid) = &ctx.cpu_config.hybrid_type {
236                match hybrid {
237                    CpuHybridType::Atom => {
238                        entry.cpuid.eax |= EAX_CORE_TYPE_ATOM << EAX_CORE_TYPE_SHIFT;
239                    }
240                    CpuHybridType::Core => {
241                        entry.cpuid.eax |= EAX_CORE_TYPE_CORE << EAX_CORE_TYPE_SHIFT;
242                    }
243                }
244            }
245        }
246        0xB | 0x1F => {
247            if ctx.cpu_config.host_cpu_topology {
248                return;
249            }
250            // Extended topology enumeration / V2 Extended topology enumeration
251            // NOTE: these will need to be split if any of the fields that differ between
252            // the two versions are to be set.
253            // On AMD, these leaves are not used, so it is currently safe to leave in.
254            entry.cpuid.edx = ctx.vcpu_id as u32; // x2APIC ID
255            if entry.index == 0 {
256                if ctx.cpu_config.no_smt || (ctx.cpu_count == 1) {
257                    // Make it so that all VCPUs appear as different,
258                    // non-hyperthreaded cores on the same package.
259                    entry.cpuid.eax = 0; // Shift to get id of next level
260                    entry.cpuid.ebx = 1; // Number of logical cpus at this level
261                } else if ctx.cpu_count % 2 == 0 {
262                    // Each core has 2 hyperthreads
263                    entry.cpuid.eax = 1; // Shift to get id of next level
264                    entry.cpuid.ebx = 2; // Number of logical cpus at this level
265                } else {
266                    // One core contain all the cpu_count hyperthreads
267                    let cpu_bits: u32 = 32 - ((ctx.cpu_count - 1) as u32).leading_zeros();
268                    entry.cpuid.eax = cpu_bits; // Shift to get id of next level
269                    entry.cpuid.ebx = ctx.cpu_count as u32; // Number of logical cpus at this level
270                }
271                entry.cpuid.ecx = (ECX_TOPO_SMT_TYPE << ECX_TOPO_TYPE_SHIFT) | entry.index;
272            } else if entry.index == 1 {
273                let cpu_bits: u32 = 32 - ((ctx.cpu_count - 1) as u32).leading_zeros();
274                entry.cpuid.eax = cpu_bits;
275                // Number of logical cpus at this level
276                entry.cpuid.ebx = (ctx.cpu_count as u32) & 0xffff;
277                entry.cpuid.ecx = (ECX_TOPO_CORE_TYPE << ECX_TOPO_TYPE_SHIFT) | entry.index;
278            } else {
279                entry.cpuid.eax = 0;
280                entry.cpuid.ebx = 0;
281                entry.cpuid.ecx = 0;
282            }
283        }
284        _ => (),
285    }
286}
287
288/// Adjust all the entries in `cpuid` based on crosvm's cpuid logic and `ctx`. Calls `adjust_cpuid`
289/// on each entry in `cpuid`, and adds any entries that should exist and are missing from `cpuid`.
290pub fn filter_cpuid(cpuid: &mut hypervisor::CpuId, ctx: &CpuIdContext) {
291    // Add an empty leaf 0x15 if we have a tsc_frequency and it's not in the current set of leaves.
292    // It will be filled with the appropriate frequency information by `adjust_cpuid`.
293    if ctx.tsc_frequency.is_some()
294        && !cpuid
295            .cpu_id_entries
296            .iter()
297            .any(|entry| entry.function == 0x15)
298    {
299        cpuid.cpu_id_entries.push(CpuIdEntry {
300            function: 0x15,
301            index: 0,
302            flags: 0,
303            cpuid: CpuidResult {
304                eax: 0,
305                ebx: 0,
306                ecx: 0,
307                edx: 0,
308            },
309        })
310    }
311
312    let entries = &mut cpuid.cpu_id_entries;
313    for entry in entries.iter_mut() {
314        adjust_cpuid(entry, ctx);
315    }
316}
317
318/// Sets up the cpuid entries for the given vcpu.  Can fail if there are too many CPUs specified or
319/// if an ioctl returns an error.
320///
321/// # Arguments
322///
323/// * `hypervisor` - `HypervisorX86_64` impl for getting supported CPU IDs.
324/// * `irq_chip` - `IrqChipX86_64` for adjusting appropriate IrqChip CPUID bits.
325/// * `vcpu` - `VcpuX86_64` for setting CPU ID.
326/// * `vcpu_id` - The vcpu index of `vcpu`.
327/// * `cpu_config` - CPU feature configurations.
328pub fn setup_cpuid(
329    hypervisor: &dyn HypervisorX86_64,
330    irq_chip: &dyn IrqChipX86_64,
331    vcpu: &dyn VcpuX86_64,
332    vcpu_id: usize,
333    nrcpus: usize,
334    cpu_config: CpuConfigX86_64,
335) -> Result<()> {
336    let mut cpuid = hypervisor
337        .get_supported_cpuid()
338        .map_err(Error::GetSupportedCpusFailed)?;
339
340    filter_cpuid(
341        &mut cpuid,
342        &CpuIdContext::new(
343            vcpu_id,
344            nrcpus,
345            Some(irq_chip),
346            cpu_config,
347            hypervisor.check_capability(HypervisorCap::CalibratedTscLeafRequired),
348            __cpuid_count,
349            __cpuid,
350        ),
351    );
352
353    vcpu.set_cpuid(&cpuid)
354        .map_err(Error::SetSupportedCpusFailed)
355}
356
357const MANUFACTURER_ID_FUNCTION: u32 = 0x00000000;
358const AMD_EBX: u32 = u32::from_le_bytes([b'A', b'u', b't', b'h']);
359const AMD_EDX: u32 = u32::from_le_bytes([b'e', b'n', b't', b'i']);
360const AMD_ECX: u32 = u32::from_le_bytes([b'c', b'A', b'M', b'D']);
361const INTEL_EBX: u32 = u32::from_le_bytes([b'G', b'e', b'n', b'u']);
362const INTEL_EDX: u32 = u32::from_le_bytes([b'i', b'n', b'e', b'I']);
363const INTEL_ECX: u32 = u32::from_le_bytes([b'n', b't', b'e', b'l']);
364
365pub fn cpu_manufacturer() -> CpuManufacturer {
366    // SAFETY:
367    // safe because MANUFACTURER_ID_FUNCTION is a well known cpuid function,
368    // and we own the result value afterwards.
369    let result = unsafe { __cpuid(MANUFACTURER_ID_FUNCTION) };
370    if result.ebx == AMD_EBX && result.edx == AMD_EDX && result.ecx == AMD_ECX {
371        return CpuManufacturer::Amd;
372    } else if result.ebx == INTEL_EBX && result.edx == INTEL_EDX && result.ecx == INTEL_ECX {
373        return CpuManufacturer::Intel;
374    }
375    CpuManufacturer::Unknown
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn cpu_manufacturer_test() {
384        // this should be amd or intel. We don't support other processors for virtualization.
385        let manufacturer = cpu_manufacturer();
386        assert_ne!(manufacturer, CpuManufacturer::Unknown);
387    }
388
389    #[test]
390    fn cpuid_copies_register() {
391        let fake_cpuid_count = |_function: u32, _index: u32| CpuidResult {
392            eax: 27,
393            ebx: 18,
394            ecx: 28,
395            edx: 18,
396        };
397        let fake_cpuid = |_function: u32| CpuidResult {
398            eax: 0,
399            ebx: 0,
400            ecx: 0,
401            edx: 0,
402        };
403        let cpu_config = CpuConfigX86_64 {
404            force_calibrated_tsc_leaf: false,
405            host_cpu_topology: true,
406            enable_hwp: false,
407            no_smt: false,
408            itmt: false,
409            hybrid_type: None,
410        };
411        let ctx = CpuIdContext {
412            vcpu_id: 0,
413            cpu_count: 0,
414            x2apic: false,
415            tsc_deadline_timer: false,
416            apic_frequency: 0,
417            tsc_frequency: None,
418            cpu_config,
419            cpuid_count: fake_cpuid_count,
420            cpuid: fake_cpuid,
421        };
422        let mut cpu_id_entry = CpuIdEntry {
423            function: 0x4,
424            index: 0,
425            flags: 0,
426            cpuid: CpuidResult {
427                eax: 31,
428                ebx: 41,
429                ecx: 59,
430                edx: 26,
431            },
432        };
433        adjust_cpuid(&mut cpu_id_entry, &ctx);
434        assert_eq!(cpu_id_entry.cpuid.eax, 27)
435    }
436}