Skip to main content

corescout_substrate/platform/
cpuid.rs

1//! Thin, safe wrappers over the x86 `CPUID` instruction.
2//!
3//! Only used for facts the OS does not expose portably: the vendor string, the
4//! processor brand string, and per-core hybrid classification. Everything else
5//! comes from the OS, which knows about things CPUID does not (offlining,
6//! cpusets, cgroups, cpufreq policy).
7//!
8//! # Why this needs pinning
9//!
10//! `CPUID` reports the core it executes on. On a hybrid part, leaf `0x1A`
11//! returns a *different* answer on a P-core than on an E-core. Callers that
12//! want per-core answers must pin the thread first; [`core_type_here`] is named
13//! to make that obvious.
14
15use crate::topology::CoreType;
16
17#[cfg(target_arch = "x86_64")]
18use std::arch::x86_64::{__cpuid, __cpuid_count, __get_cpuid_max};
19
20/// Raw CPUID output.
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
22pub struct Regs {
23    pub eax: u32,
24    pub ebx: u32,
25    pub ecx: u32,
26    pub edx: u32,
27}
28
29/// Execute `CPUID` with the given leaf, or `None` off x86.
30// `unused_unsafe`: newer toolchains made these intrinsics safe; the block
31// is still required on the minimum supported Rust version.
32#[allow(unused_variables, unused_unsafe)]
33pub fn cpuid(leaf: u32) -> Option<Regs> {
34    #[cfg(target_arch = "x86_64")]
35    {
36        // SAFETY: CPUID is unprivileged and has no side effects. We check the
37        // maximum supported leaf first so unsupported leaves cannot return the
38        // contents of an unrelated leaf (which is what real hardware does).
39        unsafe {
40            let max = __get_cpuid_max(leaf & 0x8000_0000).0;
41            if leaf > max {
42                return None;
43            }
44            let r = __cpuid(leaf);
45            Some(Regs {
46                eax: r.eax,
47                ebx: r.ebx,
48                ecx: r.ecx,
49                edx: r.edx,
50            })
51        }
52    }
53    #[cfg(not(target_arch = "x86_64"))]
54    {
55        None
56    }
57}
58
59/// Execute `CPUID` with an explicit sub-leaf in ECX.
60#[allow(unused_variables, unused_unsafe)]
61pub fn cpuid_count(leaf: u32, sub_leaf: u32) -> Option<Regs> {
62    #[cfg(target_arch = "x86_64")]
63    {
64        // SAFETY: as above.
65        unsafe {
66            let max = __get_cpuid_max(leaf & 0x8000_0000).0;
67            if leaf > max {
68                return None;
69            }
70            let r = __cpuid_count(leaf, sub_leaf);
71            Some(Regs {
72                eax: r.eax,
73                ebx: r.ebx,
74                ecx: r.ecx,
75                edx: r.edx,
76            })
77        }
78    }
79    #[cfg(not(target_arch = "x86_64"))]
80    {
81        None
82    }
83}
84
85/// The 12-character vendor string from leaf 0: `GenuineIntel`, `AuthenticAMD`.
86pub fn vendor() -> Option<String> {
87    let r = cpuid(0)?;
88    let mut bytes = Vec::with_capacity(12);
89    // The string is spread across EBX, EDX, ECX in that deliberately odd order.
90    for reg in [r.ebx, r.edx, r.ecx] {
91        bytes.extend_from_slice(&reg.to_le_bytes());
92    }
93    Some(String::from_utf8_lossy(&bytes).trim().to_string())
94}
95
96/// The 48-character brand string from extended leaves 0x8000_0002..=0x8000_0004.
97pub fn brand_string() -> Option<String> {
98    let mut bytes = Vec::with_capacity(48);
99    for leaf in 0x8000_0002u32..=0x8000_0004 {
100        let r = cpuid(leaf)?;
101        for reg in [r.eax, r.ebx, r.ecx, r.edx] {
102            bytes.extend_from_slice(&reg.to_le_bytes());
103        }
104    }
105    let s = String::from_utf8_lossy(&bytes);
106    let s = s.trim_end_matches('\0').trim();
107    if s.is_empty() {
108        None
109    } else {
110        // Brand strings are space padded internally on some parts.
111        Some(collapse_spaces(s))
112    }
113}
114
115/// True when the part advertises a hybrid (mixed core type) topology.
116///
117/// Leaf 7 sub-leaf 0, EDX bit 15.
118pub fn is_hybrid() -> bool {
119    cpuid_count(7, 0)
120        .map(|r| r.edx & (1 << 15) != 0)
121        .unwrap_or(false)
122}
123
124/// Classify the core the calling thread is *currently running on*.
125///
126/// Leaf `0x1A` sub-leaf 0, EAX bits 31:24: `0x40` = Core (P), `0x20` = Atom (E).
127/// The caller is responsible for pinning; see the module docs.
128pub fn core_type_here() -> CoreType {
129    if !is_hybrid() {
130        return CoreType::Unknown;
131    }
132    match cpuid_count(0x1A, 0).map(|r| r.eax >> 24) {
133        Some(0x40) => CoreType::Performance,
134        Some(0x20) => CoreType::Efficiency,
135        _ => CoreType::Unknown,
136    }
137}
138
139fn collapse_spaces(s: &str) -> String {
140    let mut out = String::with_capacity(s.len());
141    let mut last_space = false;
142    for ch in s.chars() {
143        let is_space = ch == ' ';
144        if is_space && last_space {
145            continue;
146        }
147        last_space = is_space;
148        out.push(ch);
149    }
150    out.trim().to_string()
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn collapses_internal_padding_in_brand_strings() {
159        assert_eq!(
160            collapse_spaces("  AMD Ryzen 9    7950X 16-Core Processor "),
161            "AMD Ryzen 9 7950X 16-Core Processor"
162        );
163    }
164
165    #[test]
166    #[cfg(target_arch = "x86_64")]
167    fn vendor_string_is_twelve_sane_characters() {
168        let v = vendor().expect("x86-64 always supports leaf 0");
169        assert_eq!(v.len(), 12, "unexpected vendor string {v:?}");
170        assert!(v.chars().all(|c| c.is_ascii_graphic()));
171    }
172
173    #[test]
174    #[cfg(target_arch = "x86_64")]
175    fn brand_string_is_non_empty() {
176        // Every x86-64 part made since 2003 implements the brand leaves.
177        let b = brand_string().expect("brand string leaves");
178        assert!(!b.is_empty());
179    }
180
181    #[test]
182    fn non_hybrid_parts_report_unknown_core_type() {
183        // On a non-hybrid host this must not claim P or E.
184        if !is_hybrid() {
185            assert_eq!(core_type_here(), CoreType::Unknown);
186        }
187    }
188}