corescout_substrate/platform/
cpuid.rs1use crate::topology::CoreType;
16
17#[cfg(target_arch = "x86_64")]
18use std::arch::x86_64::{__cpuid, __cpuid_count, __get_cpuid_max};
19
20#[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#[allow(unused_variables, unused_unsafe)]
33pub fn cpuid(leaf: u32) -> Option<Regs> {
34 #[cfg(target_arch = "x86_64")]
35 {
36 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#[allow(unused_variables, unused_unsafe)]
61pub fn cpuid_count(leaf: u32, sub_leaf: u32) -> Option<Regs> {
62 #[cfg(target_arch = "x86_64")]
63 {
64 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
85pub fn vendor() -> Option<String> {
87 let r = cpuid(0)?;
88 let mut bytes = Vec::with_capacity(12);
89 for reg in [r.ebx, r.edx, r.ecx] {
91 bytes.extend_from_slice(®.to_le_bytes());
92 }
93 Some(String::from_utf8_lossy(&bytes).trim().to_string())
94}
95
96pub 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(®.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 Some(collapse_spaces(s))
112 }
113}
114
115pub fn is_hybrid() -> bool {
119 cpuid_count(7, 0)
120 .map(|r| r.edx & (1 << 15) != 0)
121 .unwrap_or(false)
122}
123
124pub 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 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 if !is_hybrid() {
185 assert_eq!(core_type_here(), CoreType::Unknown);
186 }
187 }
188}