ostd/arch/x86/cpu/cpuid.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! CPU information from the CPUID instruction.
4
5use core::arch::x86_64::CpuidResult;
6
7use spin::Once;
8
9static MAX_LEAF: Once<u32> = Once::new();
10static MAX_HYPERVISOR_LEAF: Once<u32> = Once::new();
11static MAX_EXTENDED_LEAF: Once<u32> = Once::new();
12
13#[repr(u32)]
14enum Leaf {
15 Base = 0x00,
16 Xstate = 0x0d,
17 Tsc = 0x15,
18
19 HypervisorBase = 0x40000000,
20 KvmFeatures = 0x40000001,
21 ExtBase = 0x80000000,
22}
23
24/// Executes the CPUID instruction for the given leaf and subleaf.
25///
26/// This method will return `None` if the leaf is not supported.
27pub fn cpuid(leaf: u32, subleaf: u32) -> Option<CpuidResult> {
28 fn raw_cpuid(leaf: u32, subleaf: u32) -> CpuidResult {
29 core::arch::x86_64::__cpuid_count(leaf, subleaf)
30 }
31
32 let max_leaf = if leaf < Leaf::HypervisorBase as u32 {
33 // Standard leaves (0x0000_0000 - 0x3FFF_FFFF)
34 *MAX_LEAF.call_once(|| raw_cpuid(Leaf::Base as u32, 0).eax)
35 } else if leaf < Leaf::ExtBase as u32 {
36 // Hypervisor leaves (0x4000_0000 - 0x7FFF_FFFF)
37 *MAX_HYPERVISOR_LEAF.call_once(|| raw_cpuid(Leaf::HypervisorBase as u32, 0).eax)
38 } else {
39 // Extended leaves (0x8000_0000 - 0xFFFF_FFFF)
40 *MAX_EXTENDED_LEAF.call_once(|| raw_cpuid(Leaf::ExtBase as u32, 0).eax)
41 };
42
43 if leaf > max_leaf {
44 None
45 } else {
46 Some(raw_cpuid(leaf, subleaf))
47 }
48}
49
50/// Queries the frequency in Hz of the Time Stamp Counter (TSC).
51///
52/// This is based on the information given by the CPUID instruction in the Time Stamp Counter and
53/// Nominal Core Crystal Clock Information Leaf.
54///
55/// Note that the CPUID leaf is currently only supported by new Intel CPUs. This method will return
56/// `None` if it is not supported.
57pub(in crate::arch) fn query_tsc_freq() -> Option<u64> {
58 let CpuidResult {
59 eax: denominator,
60 ebx: numerator,
61 ecx: crystal_freq,
62 ..
63 } = cpuid(Leaf::Tsc as u32, 0)?;
64
65 if denominator == 0 || numerator == 0 {
66 return None;
67 }
68
69 // If the nominal core crystal clock frequency is not enumerated, we can either obtain that
70 // information from a hardcoded table or rely on the processor base frequency. The Intel
71 // documentation recommends the first approach [1], but Linux uses the second approach because
72 // the first approach is difficult to implement correctly for all corner cases [2]. However,
73 // the second approach does not provide 100% accurate frequencies, so Linux must adjust them at
74 // runtime [2]. For now, we avoid these headaches by faithfully reporting that the TSC
75 // frequency is unavailable.
76 //
77 // [1]: Intel(R) 64 and IA-32 Architectures Software Developer’s Manual,
78 // Section 20.7.3, Determining the Processor Base Frequency
79 // [2]: https://github.com/torvalds/linux/commit/604dc9170f2435d27da5039a3efd757dceadc684
80 if crystal_freq == 0 {
81 return None;
82 }
83
84 Some((crystal_freq as u64) * (numerator as u64) / (denominator as u64))
85}
86
87/// Queries the supported XSTATE features, i.e., the supported bits of `XCR0` and `IA32_XSS`.
88pub(in crate::arch) fn query_xstate_max_features() -> Option<u64> {
89 let res0 = cpuid(Leaf::Xstate as u32, 0)?;
90 let res1 = cpuid(Leaf::Xstate as u32, 1)?;
91
92 // Supported bits in `XCR0`.
93 let xcr_bits = (res0.eax as u64) | ((res0.edx as u64) << 32);
94 // Supported bits in `IA32_XSS`.
95 let xss_bits = (res1.ecx as u64) | ((res1.edx as u64) << 32);
96
97 Some(xcr_bits | xss_bits)
98}
99
100/// Queries the size in bytes of the XSAVE area containing states enabled by `XCRO` and `IA32_XSS`.
101pub(in crate::arch) fn query_xsave_area_size() -> Option<u32> {
102 cpuid(Leaf::Xstate as u32, 1).map(|res| res.ebx)
103}
104
105/// Queries whether the system is running in QEMU.
106///
107/// This function uses the CPUID instruction to detect the QEMU hypervisor signature.
108pub(in crate::arch) fn query_if_running_in_qemu() -> bool {
109 let Some(signature) = query_hypervisor_signature() else {
110 return false;
111 };
112
113 // Check for the QEMU hypervisor signature: "TCGTCGTCGTCG" or "KVMKVMKVM\0\0\0".
114 // Reference: <https://wiki.osdev.org/QEMU_fw_cfg#Detecting_QEMU>
115 //
116 // TODO: The "KVMKVMKVM\0\0\0" signature is commonly shared by KVM-based VMMs
117 // (not unique to QEMU). Detecting QEMU via its ACPI tables
118 // (e.g., OEM IDs or the QEMU0002 device) would generally be more accurate.
119 matches!(&signature, b"TCGTCGTCGTCG" | b"KVMKVMKVM\0\0\0")
120}
121
122/// Queries whether the system is running under KVM.
123pub(in crate::arch) fn query_if_running_under_kvm() -> bool {
124 let Some(signature) = query_hypervisor_signature() else {
125 return false;
126 };
127
128 matches!(&signature, b"KVMKVMKVM\0\0\0")
129}
130
131/// Queries the 12-byte hypervisor signature.
132fn query_hypervisor_signature() -> Option<[u8; 12]> {
133 let result = cpuid(Leaf::HypervisorBase as u32, 0)?;
134
135 let mut signature = [0u8; 12];
136 signature[0..4].copy_from_slice(&result.ebx.to_ne_bytes());
137 signature[4..8].copy_from_slice(&result.ecx.to_ne_bytes());
138 signature[8..12].copy_from_slice(&result.edx.to_ne_bytes());
139
140 Some(signature)
141}
142
143/// Queries if the KVM feature indicated by `bit` is available.
144pub(in crate::arch) fn query_kvm_feature(bit: u32) -> bool {
145 cpuid(Leaf::KvmFeatures as u32, 0).is_some_and(|result| result.eax & (1 << bit) != 0)
146}