1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
//! `cpuid` 内部函数
#![allow(clippy::module_name_repetitions)]
use crate::arch::asm;
#[cfg(test)]
use stdarch_test::assert_instr;
/// `cpuid` 指令的结果。
#[allow(clippy::missing_inline_in_public_items)]
// ^^ CpuidResult 的 Debug 派生的暗示不是 #[inline],这没关系。
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub struct CpuidResult {
/// EAX 寄存器。
#[stable(feature = "simd_x86", since = "1.27.0")]
pub eax: u32,
/// EBX 寄存器。
#[stable(feature = "simd_x86", since = "1.27.0")]
pub ebx: u32,
/// ECX 寄存器。
#[stable(feature = "simd_x86", since = "1.27.0")]
pub ecx: u32,
/// EDX 寄存器。
#[stable(feature = "simd_x86", since = "1.27.0")]
pub edx: u32,
}
/// 返回给定 `leaf` (`EAX`) 和 `sub_leaf` (`ECX`) 的 `cpuid` 指令的结果。
///
/// [`__get_cpuid_max(0)`](fn.__get_cpuid_max.html) 的第一个元组参数返回支持的最高叶子值。
/// 对于叶子包含子叶,第二个元组参数返回支持的最高子叶值。
///
/// [CPUID 维基百科][wiki_cpuid] 页面包含如何使用 `EAX` 和 `ECX` 寄存器查询哪些信息,以及如何解释 `EAX`,`EBX`,`ECX` 和 `EDX` 返回的结果。
///
///
/// 引用的是:
/// - [英特尔 64 位和 IA-32 架构软件开发人员手册第 2 卷:
/// 指令集引用,AZ][intel64_ref]。
/// - [AMD64 Architecture Programmer's Manual, Volume 3: General-Purpose and System Instructions][amd64_ref].
///
/// [wiki_cpuid]: https://en.wikipedia.org/wiki/CPUID
/// [intel64_ref]: https://cdrdv2-public.intel.com/671110/325383-sdm-vol-2abcd.pdf
/// [amd64_ref]: http://support.amd.com/TechDocs/24594.pdf
///
///
///
///
///
///
///
#[inline]
#[cfg_attr(test, assert_instr(cpuid))]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn __cpuid_count(leaf: u32, sub_leaf: u32) -> CpuidResult {
let eax;
let ebx;
let ecx;
let edx;
// LLVM 有时会保留 `ebx` 供其内部使用,因此我们需要为它使用临时寄存器。
//
#[cfg(target_arch = "x86")]
{
asm!(
"mov {0}, ebx",
"cpuid",
"xchg {0}, ebx",
out(reg) ebx,
inout("eax") leaf => eax,
inout("ecx") sub_leaf => ecx,
out("edx") edx,
options(nostack, preserves_flags),
);
}
#[cfg(target_arch = "x86_64")]
{
asm!(
"mov {0:r}, rbx",
"cpuid",
"xchg {0:r}, rbx",
out(reg) ebx,
inout("eax") leaf => eax,
inout("ecx") sub_leaf => ecx,
out("edx") edx,
options(nostack, preserves_flags),
);
}
CpuidResult { eax, ebx, ecx, edx }
}
/// 请参见 [`__cpuid_count`](fn.__cpuid_count.html)。
#[inline]
#[cfg_attr(test, assert_instr(cpuid))]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn __cpuid(leaf: u32) -> CpuidResult {
__cpuid_count(leaf, 0)
}
/// 主机是否支持 `cpuid` 指令?
#[inline]
pub fn has_cpuid() -> bool {
#[cfg(target_env = "sgx")]
{
false
}
#[cfg(all(not(target_env = "sgx"), target_arch = "x86_64"))]
{
true
}
#[cfg(all(not(target_env = "sgx"), target_arch = "x86"))]
{
// 对 i586 和 i686 Rust 的优化目标是启用了 SSE 并支持 cpuid 的目标:
//
#[cfg(target_feature = "sse")]
{
true
}
// 如果未启用 SSE,请检测 cpuid 是否可用:
#[cfg(not(target_feature = "sse"))]
unsafe {
// 在 `x86` 上,`cpuid` 指令并非始终可用。
// 这遵循以下指示的方法:
// http://wiki.osdev.org/CPUID#Checking_CPUID_availability
// https://software.intel.com/en-us/articles/using-cpuid-to-detect-the-presence-of-sse-41-and-sse-42-instruction-sets/
// 它通过检查 EFLAGS 寄存器的第 21 位是否可修改来检测 `cpuid` 是否可用。
//
// 如果是,则 `cpuid` 可用。
let result: u32;
asm!(
// 读取 eflags 并保存它的副本
"pushfd",
"pop {result}",
"mov {result}, {saved_flags}",
// 翻转标志的第 21 位
"xor $0x200000, {result}",
// 加载修改后的标志并将其读回。
// 只有在 cpuid 可用时才能修改位 21。
"push {result}",
"popfd",
"pushfd",
"pop {result}",
// 使用 xor 找出第 21 位是否发生了变化
"xor {saved_flags}, {result}",
result = out(reg) result,
saved_flags = out(reg) _,
options(nomem, att_syntax),
);
// popfd (A) 和 pushfd (B) 之间存在竞争,由于中断,调试器逐步通过 asm 等原因,可能已修改了 21st 之后的其他位。
//
//
// 因此,明确检查第 21 位是否被修改。
//
// 如果结果为零,则未修改 cpuid 位。
// 如果结果为 `0x200000` (non-zero),则说明 cpuid 已正确修改,并且 CPU 支持 cpuid 指令:
//
//
//
//
(result & 0x200000) != 0
}
}
}
/// 返回最高支持的 `leaf` (`EAX`) 和子叶 (`ECX`) `cpuid` 值。
///
/// 如果支持 `cpuid`,并且 `leaf` 为零,则第一个元组参数包含 `cpuid` 支持的最高 `leaf` 值。
/// 对于包含子叶的 `leaf',第二个元组参数包含支持的最高子叶值。
///
/// 另请参见 [`__cpuid`](fn.__cpuid.html) 和 [`__cpuid_count`](fn.__cpuid_count.html)。
///
///
///
///
#[inline]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn __get_cpuid_max(leaf: u32) -> (u32, u32) {
let CpuidResult { eax, ebx, .. } = __cpuid(leaf);
(eax, ebx)
}
#[cfg(test)]
mod tests {
use crate::core_arch::x86::*;
#[test]
fn test_always_has_cpuid() {
// 所有当前测试过的目标都有说明
// FIXME: 将没有 `cpuid` 的目标添加到 CI
assert!(cpuid::has_cpuid());
}
#[test]
fn test_has_cpuid_idempotent() {
assert_eq!(cpuid::has_cpuid(), cpuid::has_cpuid());
}
}