Skip to main content

strat9_kernel/arch/x86_64/
cpuid.rs

1//! CPU feature detection via CPUID instruction.
2//!
3//! Provides a `CpuInfo` struct populated at boot time with vendor, model,
4//! feature flags, and XSAVE geometry. All subsequent queries go through
5//! `host()` which returns the cached result.
6
7use crate::sync::SpinLock;
8use alloc::string::String;
9use bitflags::bitflags;
10use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
11
12bitflags! {
13    /// CPU feature flags detected via CPUID.
14    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
15    pub struct CpuFeatures: u64 {
16        //  Leaf 0x01 ECX
17        const SSE3      = 1 << 0;
18        const SSSE3     = 1 << 1;
19        const FMA       = 1 << 2;
20        const SSE4_1    = 1 << 3;
21        const SSE4_2    = 1 << 4;
22        const POPCNT    = 1 << 5;
23        const AES_NI    = 1 << 6;
24        const XSAVE     = 1 << 7;
25        const AVX       = 1 << 8;
26        const F16C      = 1 << 9;
27        const VMX       = 1 << 10;
28        const X2APIC    = 1 << 11;
29        //  Leaf 0x01 EDX
30        const FPU       = 1 << 16;
31        const TSC       = 1 << 17;
32        const APIC      = 1 << 18;
33        const SSE       = 1 << 19;
34        const SSE2      = 1 << 20;
35        const FXSR      = 1 << 21;
36        //  Leaf 0x07 EBX
37        const AVX2      = 1 << 32;
38        const AVX512F   = 1 << 33;
39        const AVX512BW  = 1 << 34;
40        const AVX512VL  = 1 << 35;
41        const SHA       = 1 << 36;
42        //  Leaf 0x80000001 EDX
43        const NX        = 1 << 48;
44        const PAGES_1G  = 1 << 49;
45        const RDTSCP    = 1 << 50;
46        const LONG_MODE = 1 << 51;
47        //  Leaf 0x80000001 ECX
48        const SVM       = 1 << 56;
49    }
50}
51
52/// XCR0 component bits.
53pub const XCR0_X87: u64 = 1 << 0;
54pub const XCR0_SSE: u64 = 1 << 1;
55pub const XCR0_AVX: u64 = 1 << 2;
56pub const XCR0_OPMASK: u64 = 1 << 5;
57pub const XCR0_ZMM_HI256: u64 = 1 << 6;
58pub const XCR0_HI16_ZMM: u64 = 1 << 7;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum CpuVendor {
62    Intel,
63    Amd,
64    Unknown,
65}
66
67/// Cached CPU identification and feature information.
68#[derive(Debug, Clone)]
69pub struct CpuInfo {
70    pub vendor: CpuVendor,
71    pub features: CpuFeatures,
72    pub max_xcr0: u64,
73    pub xsave_size: usize,
74    pub family: u8,
75    pub model: u8,
76    pub stepping: u8,
77    pub model_name: [u8; 48],
78    model_name_len: usize,
79}
80
81impl CpuInfo {
82    /// Return the model name as a `&str`.
83    pub fn model_name_str(&self) -> &str {
84        let bytes = &self.model_name[..self.model_name_len];
85        core::str::from_utf8(bytes).unwrap_or("Unknown")
86    }
87
88    /// Return a vendor id string (e.g. "GenuineIntel").
89    pub fn vendor_string(&self) -> &'static str {
90        match self.vendor {
91            CpuVendor::Intel => "GenuineIntel",
92            CpuVendor::Amd => "AuthenticAMD",
93            CpuVendor::Unknown => "Unknown",
94        }
95    }
96}
97
98static HOST_CPU: SpinLock<Option<CpuInfo>> = SpinLock::new(None);
99static INITIALIZED: AtomicBool = AtomicBool::new(false);
100
101/// Lock-free cache of the host's default XCR0 mask, written once during
102/// `init()`.  Used by `normalized_xcr0()` in the context-switch hot path
103/// to avoid acquiring the `HOST_CPU` spinlock with interrupts disabled.
104pub(crate) static HOST_DEFAULT_XCR0_CACHE: AtomicU64 = AtomicU64::new(0);
105
106/// Detect and cache CPU information. Must be called once at BSP boot.
107pub fn init() {
108    let info = detect();
109    log::info!(
110        "[CPUID] {} {} (family={} model={} stepping={})",
111        info.vendor_string(),
112        info.model_name_str(),
113        info.family,
114        info.model,
115        info.stepping,
116    );
117    log::info!(
118        "[CPUID] features={:?}, max_xcr0={:#x}, xsave_size={}",
119        info.features,
120        info.max_xcr0,
121        info.xsave_size,
122    );
123    *HOST_CPU.lock() = Some(info);
124    INITIALIZED.store(true, Ordering::Release);
125    HOST_DEFAULT_XCR0_CACHE.store(host_default_xcr0(), Ordering::Release);
126}
127
128/// Return a clone of the cached host CPU info. Panics if `init()` not called.
129pub fn host() -> CpuInfo {
130    HOST_CPU
131        .lock()
132        .clone()
133        .expect("cpuid::init() not called yet")
134}
135
136/// Whether XSAVE is supported by the host.
137pub fn host_uses_xsave() -> bool {
138    INITIALIZED.load(Ordering::Acquire)
139        && HOST_CPU
140            .lock()
141            .as_ref()
142            .map_or(false, |h| h.features.contains(CpuFeatures::XSAVE))
143}
144
145/// Detect CPU features by interrogating CPUID leaves.
146fn detect() -> CpuInfo {
147    let cpuid = super::cpuid;
148
149    //  Vendor (leaf 0)
150    let (max_leaf, ebx0, ecx0, edx0) = cpuid(0, 0);
151    let vendor = match (ebx0, edx0, ecx0) {
152        (0x756E_6547, 0x4965_6E69, 0x6C65_746E) => CpuVendor::Intel,
153        (0x6874_7541, 0x6974_6E65, 0x444D_4163) => CpuVendor::Amd,
154        _ => CpuVendor::Unknown,
155    };
156
157    let mut features = CpuFeatures::empty();
158
159    //  Leaf 0x01: main feature bits
160    let (eax1, _ebx1, ecx1, edx1) = if max_leaf >= 1 {
161        cpuid(1, 0)
162    } else {
163        (0, 0, 0, 0)
164    };
165
166    let stepping = (eax1 & 0xF) as u8;
167    let base_family = (eax1 >> 8) & 0xF;
168    let base_model = (eax1 >> 4) & 0xF;
169    let ext_model = (eax1 >> 16) & 0xF;
170    let ext_family = (eax1 >> 20) & 0xFF;
171    let mut family_full: u16 = base_family as u16;
172    let mut model: u8 = base_model as u8;
173    if base_family == 6 || base_family == 15 {
174        model |= (ext_model << 4) as u8;
175    }
176    if base_family == 15 {
177        family_full += ext_family as u16;
178    }
179    let family = family_full as u8;
180
181    if ecx1 & (1 << 0) != 0 {
182        features |= CpuFeatures::SSE3;
183    }
184    if ecx1 & (1 << 9) != 0 {
185        features |= CpuFeatures::SSSE3;
186    }
187    if ecx1 & (1 << 12) != 0 {
188        features |= CpuFeatures::FMA;
189    }
190    if ecx1 & (1 << 19) != 0 {
191        features |= CpuFeatures::SSE4_1;
192    }
193    if ecx1 & (1 << 20) != 0 {
194        features |= CpuFeatures::SSE4_2;
195    }
196    if ecx1 & (1 << 23) != 0 {
197        features |= CpuFeatures::POPCNT;
198    }
199    if ecx1 & (1 << 25) != 0 {
200        features |= CpuFeatures::AES_NI;
201    }
202    if ecx1 & (1 << 26) != 0 {
203        features |= CpuFeatures::XSAVE;
204    }
205    if ecx1 & (1 << 28) != 0 {
206        features |= CpuFeatures::AVX;
207    }
208    if ecx1 & (1 << 21) != 0 {
209        features |= CpuFeatures::X2APIC;
210    }
211    if ecx1 & (1 << 29) != 0 {
212        features |= CpuFeatures::F16C;
213    }
214    if ecx1 & (1 << 5) != 0 {
215        features |= CpuFeatures::VMX;
216    }
217
218    if edx1 & (1 << 0) != 0 {
219        features |= CpuFeatures::FPU;
220    }
221    if edx1 & (1 << 4) != 0 {
222        features |= CpuFeatures::TSC;
223    }
224    if edx1 & (1 << 9) != 0 {
225        features |= CpuFeatures::APIC;
226    }
227    if edx1 & (1 << 24) != 0 {
228        features |= CpuFeatures::FXSR;
229    }
230    if edx1 & (1 << 25) != 0 {
231        features |= CpuFeatures::SSE;
232    }
233    if edx1 & (1 << 26) != 0 {
234        features |= CpuFeatures::SSE2;
235    }
236
237    //  Leaf 0x07: extended features
238    if max_leaf >= 7 {
239        let (_eax7, ebx7, _ecx7, _edx7) = cpuid(7, 0);
240        if ebx7 & (1 << 5) != 0 {
241            features |= CpuFeatures::AVX2;
242        }
243        if ebx7 & (1 << 16) != 0 {
244            features |= CpuFeatures::AVX512F;
245        }
246        if ebx7 & (1 << 29) != 0 {
247            features |= CpuFeatures::SHA;
248        }
249        if ebx7 & (1 << 30) != 0 {
250            features |= CpuFeatures::AVX512BW;
251        }
252        if ebx7 & (1 << 31) != 0 {
253            features |= CpuFeatures::AVX512VL;
254        }
255    }
256
257    //  Leaf 0x0D: XSAVE geometry
258    let (mut max_xcr0, mut xsave_size) = (XCR0_X87 | XCR0_SSE, 512usize);
259    if features.contains(CpuFeatures::XSAVE) && max_leaf >= 0x0D {
260        let (eax_d, ebx_d, _ecx_d, edx_d) = cpuid(0x0D, 0);
261        max_xcr0 = ((edx_d as u64) << 32) | eax_d as u64;
262        xsave_size = ebx_d as usize;
263    }
264
265    //  Leaf 0x80000001: extended features (AMD-V, NX, 1G pages)
266    let (max_ext, _, _, _) = cpuid(0x8000_0000, 0);
267    if max_ext >= 0x8000_0001 {
268        let (_eax_e, _ebx_e, ecx_e, edx_e) = cpuid(0x8000_0001, 0);
269        if edx_e & (1 << 20) != 0 {
270            features |= CpuFeatures::NX;
271        }
272        if edx_e & (1 << 26) != 0 {
273            features |= CpuFeatures::PAGES_1G;
274        }
275        if edx_e & (1 << 27) != 0 {
276            features |= CpuFeatures::RDTSCP;
277        }
278        if edx_e & (1 << 29) != 0 {
279            features |= CpuFeatures::LONG_MODE;
280        }
281        if ecx_e & (1 << 2) != 0 {
282            features |= CpuFeatures::SVM;
283        }
284    }
285
286    //  Leaves 0x80000002-0x80000004: brand string
287    let mut model_name = [0u8; 48];
288    let mut model_name_len = 0usize;
289    if max_ext >= 0x8000_0004 {
290        for (i, leaf) in (0x8000_0002u32..=0x8000_0004).enumerate() {
291            let (a, b, c, d) = cpuid(leaf, 0);
292            let offset = i * 16;
293            model_name[offset..offset + 4].copy_from_slice(&a.to_le_bytes());
294            model_name[offset + 4..offset + 8].copy_from_slice(&b.to_le_bytes());
295            model_name[offset + 8..offset + 12].copy_from_slice(&c.to_le_bytes());
296            model_name[offset + 12..offset + 16].copy_from_slice(&d.to_le_bytes());
297        }
298        model_name_len = model_name
299            .iter()
300            .rposition(|&b| b != 0 && b != b' ')
301            .map_or(0, |p| p + 1);
302    }
303
304    CpuInfo {
305        vendor,
306        features,
307        max_xcr0,
308        xsave_size,
309        family,
310        model,
311        stepping,
312        model_name,
313        model_name_len,
314    }
315}
316
317/// Compute the XCR0 mask for a given set of allowed features,
318/// clamped to what the host actually supports.
319pub fn xcr0_for_features(features: CpuFeatures) -> u64 {
320    let mut xcr0 = XCR0_X87 | XCR0_SSE;
321    if features.contains(CpuFeatures::AVX) {
322        xcr0 |= XCR0_AVX;
323    }
324    if features.contains(CpuFeatures::AVX512F) {
325        xcr0 |= XCR0_OPMASK | XCR0_ZMM_HI256 | XCR0_HI16_ZMM;
326    }
327    let h = host();
328    xcr0 & h.max_xcr0
329}
330
331/// Compute the XSAVE area size needed for a given XCR0 mask.
332/// Falls back to 512 (FXSAVE) if XSAVE is not supported.
333pub fn xsave_size_for_xcr0(xcr0: u64) -> usize {
334    if !host_uses_xsave() {
335        return 512;
336    }
337    let h = host();
338    if xcr0 == h.max_xcr0 {
339        return h.xsave_size;
340    }
341
342    // Enumerate each enabled XCR0 component via CPUID leaf 0xD sub-leaves.
343    // Sub-leaf n returns offset (EBX) and size (EAX) for component n.
344    // The total save area is max(offset + size) across all enabled components.
345    let mut size = 576usize; // legacy area (512) + xsave header (64)
346    for comp in 2..63 {
347        if xcr0 & (1u64 << comp) == 0 {
348            continue;
349        }
350        let (eax, ebx, _ecx, _edx) = super::cpuid(0x0D, comp);
351        let comp_size = eax as usize;
352        let comp_offset = ebx as usize;
353        size = size.max(comp_offset + comp_size);
354    }
355    size.min(h.xsave_size)
356}
357
358/// Return the host's default XCR0 mask from the lock-free cache, falling back
359/// to the locked query before `init()` is complete.
360#[inline]
361pub fn host_default_xcr0_fast() -> u64 {
362    let cached = HOST_DEFAULT_XCR0_CACHE.load(Ordering::Acquire);
363    if cached != 0 {
364        return cached;
365    }
366    host_default_xcr0()
367}
368
369/// Return the host's default XCR0 mask (all supported features).
370/// Safe to call before `init()` : returns `XCR0_X87 | XCR0_SSE` if not yet initialized.
371///
372/// Try to read the TSC frequency from CPUID leaf 0x15 (Time Stamp Counter and
373/// Core Crystal Clock Information).  Returns kHz, or None if not available.
374///
375/// This is the preferred method on Intel/AMD CPUs that support invariant TSC.
376/// Linux uses this as its primary TSC calibration source.
377pub fn tsc_frequency_khz() -> Option<u64> {
378    let max_leaf = super::cpuid(0, 0).0;
379    if max_leaf < 0x15 {
380        //  TSC frequency from CPUID leaf 0x15
381        return None;
382    }
383    let (eax, ebx, ecx, _edx) = super::cpuid(0x15, 0);
384    let core_crystal_hz = ecx as u64;
385
386    // If the core crystal clock is known, compute TSC frequency.
387    if core_crystal_hz != 0 {
388        let denom = eax as u64;
389        let num = ebx as u64;
390        if denom != 0 {
391            // TSC freq = core_crystal_hz * num / denom
392            return Some(core_crystal_hz * num / denom / 1_000);
393        }
394    }
395    None
396}
397
398pub fn host_default_xcr0() -> u64 {
399    if INITIALIZED.load(Ordering::Acquire) {
400        HOST_CPU.lock().as_ref().map_or(XCR0_X87 | XCR0_SSE, |h| {
401            // IMPORTANT: do not call xcr0_for_features() here: it calls host()
402            // and would deadlock while HOST_CPU lock is already held.
403            let mut xcr0 = XCR0_X87 | XCR0_SSE;
404            if h.features.contains(CpuFeatures::AVX) {
405                xcr0 |= XCR0_AVX;
406            }
407            if h.features.contains(CpuFeatures::AVX512F) {
408                xcr0 |= XCR0_OPMASK | XCR0_ZMM_HI256 | XCR0_HI16_ZMM;
409            }
410            // Clamp to host-supported bits.
411            xcr0 & h.max_xcr0
412        })
413    } else {
414        XCR0_X87 | XCR0_SSE
415    }
416}
417
418/// Build a Linux-style `flags` string from CPU features.
419pub fn features_to_flags_string(f: CpuFeatures) -> String {
420    let mut flags = String::new();
421    let table: &[(CpuFeatures, &str)] = &[
422        (CpuFeatures::FPU, "fpu"),
423        (CpuFeatures::TSC, "tsc"),
424        (CpuFeatures::APIC, "apic"),
425        (CpuFeatures::FXSR, "fxsr"),
426        (CpuFeatures::SSE, "sse"),
427        (CpuFeatures::SSE2, "sse2"),
428        (CpuFeatures::SSE3, "sse3"),
429        (CpuFeatures::SSSE3, "ssse3"),
430        (CpuFeatures::SSE4_1, "sse4_1"),
431        (CpuFeatures::SSE4_2, "sse4_2"),
432        (CpuFeatures::POPCNT, "popcnt"),
433        (CpuFeatures::AES_NI, "aes"),
434        (CpuFeatures::XSAVE, "xsave"),
435        (CpuFeatures::AVX, "avx"),
436        (CpuFeatures::F16C, "f16c"),
437        (CpuFeatures::FMA, "fma"),
438        (CpuFeatures::AVX2, "avx2"),
439        (CpuFeatures::AVX512F, "avx512f"),
440        (CpuFeatures::AVX512BW, "avx512bw"),
441        (CpuFeatures::AVX512VL, "avx512vl"),
442        (CpuFeatures::SHA, "sha_ni"),
443        (CpuFeatures::X2APIC, "x2apic"),
444        (CpuFeatures::NX, "nx"),
445        (CpuFeatures::PAGES_1G, "pdpe1gb"),
446        (CpuFeatures::RDTSCP, "rdtscp"),
447        (CpuFeatures::LONG_MODE, "lm"),
448        (CpuFeatures::VMX, "vmx"),
449        (CpuFeatures::SVM, "svm"),
450    ];
451    for &(feat, name) in table {
452        if f.contains(feat) {
453            if !flags.is_empty() {
454                flags.push(' ');
455            }
456            flags.push_str(name);
457        }
458    }
459    flags
460}