Skip to main content

regio/x86/
cpuid.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::marker::PhantomData;
6
7use super::{EAX, EBX, ECX, EDX};
8use crate::LayoutOver;
9
10/// The raw output of a CPUID instruction.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct CpuidRawResult {
13    /// The output EAX register.
14    pub eax: u32,
15    /// The output EBX register.
16    pub ebx: u32,
17    /// The output ECX register.
18    pub ecx: u32,
19    /// The output EDX register.
20    pub edx: u32,
21}
22
23impl CpuidRawResult {
24    pub const fn zeroed() -> Self {
25        Self { eax: 0, ebx: 0, ecx: 0, edx: 0 }
26    }
27}
28
29/// A marker for the CPUID layout associated with a particular leaf, subleaf,
30/// and output register.
31///
32/// The output register, `REG`, must be one of [`EAX`], [`EBX`], [`ECX`],
33/// or [`EDX`].
34#[derive(Clone, Copy, Debug)]
35pub struct CpuidValue<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
36    PhantomData<Layout>,
37);
38
39impl<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>
40    CpuidValue<LEAF, SUBLEAF, REG, Layout>
41{
42    pub const fn new() -> Self {
43        assert!(REG <= EDX);
44        Self(PhantomData {})
45    }
46
47    /// The associated leaf.
48    ///
49    /// Despite being a part of the type itself, it is convenient to have
50    /// this accessible as an accessor, given that it is expected that one
51    /// would be primarily dealing in const `Cpuid` objects.
52    pub const fn leaf(&self) -> u32 {
53        LEAF
54    }
55
56    /// The associated subleaf.
57    ///
58    /// Despite being a part of the type itself, it is convenient to have
59    /// this accessible as an accessor, given that it is expected that one
60    /// would be primarily dealing in const `Cpuid` objects.
61    pub const fn subleaf(&self) -> u32 {
62        SUBLEAF
63    }
64}
65
66/// An abstracted means of reading CPUID values.
67pub trait Cpuid {
68    /// Returns the CPUID values for a given leaf and subleaf.
69    fn read_raw(&self, leaf: u32, subleaf: u32) -> CpuidRawResult;
70
71    /// Whether a CPUID leaf is supported, as associated with the provided
72    /// [`Cpuid`] marker.
73    fn supports<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
74        &self,
75        _cpuid: CpuidValue<LEAF, SUBLEAF, REG, Layout>,
76    ) -> bool {
77        const MAX_BASE_LEAF: CpuidValue<0x0000_0000, 0x0, EAX, u32> = CpuidValue::new();
78        const MAX_HYPERVISOR_LEAF: CpuidValue<0x4000_0000, 0x0, EAX, u32> = CpuidValue::new();
79        const MAX_EXTENDED_LEAF: CpuidValue<0x8000_0000, 0x0, EAX, u32> = CpuidValue::new();
80
81        const AMD_EXTENDED_FEATURES_C: CpuidValue<0x8000_0001, 0x0, ECX, u32> = CpuidValue::new();
82        const AMD_EXTENDED_FEATURES_C_TOPOLOGY_EXTENSIONS_BIT: u32 = 22;
83
84        const AMD_CACHE_TOPOLOGY_LEAF: u32 = 0x8000_001d;
85        const AMD_PROCESSOR_TOPOLOGY_LEAF: u32 = 0x8000_001e;
86
87        if LEAF >= MAX_EXTENDED_LEAF.leaf() {
88            if LEAF > self.read(MAX_EXTENDED_LEAF) {
89                return false;
90            }
91
92            // If topology extensions are not advertised, these leaves are reserved.
93            if LEAF == AMD_CACHE_TOPOLOGY_LEAF || LEAF == AMD_PROCESSOR_TOPOLOGY_LEAF {
94                return (self.read(AMD_EXTENDED_FEATURES_C)
95                    & (1 << AMD_EXTENDED_FEATURES_C_TOPOLOGY_EXTENSIONS_BIT))
96                    != 0;
97            }
98
99            return true;
100        }
101
102        if LEAF >= MAX_HYPERVISOR_LEAF.leaf() {
103            return LEAF <= self.read(MAX_HYPERVISOR_LEAF);
104        }
105
106        LEAF <= self.read(MAX_BASE_LEAF)
107    }
108
109    /// Reads the particular CPUID value.
110    fn read<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
111        &self,
112        _value: CpuidValue<LEAF, SUBLEAF, REG, Layout>,
113    ) -> Layout {
114        let raw = self.read_raw(LEAF, SUBLEAF);
115        match REG {
116            EAX => raw.eax,
117            EBX => raw.ebx,
118            ECX => raw.ecx,
119            EDX => raw.edx,
120            _ => unreachable!("REG validated on Cpuid construction"),
121        }
122        .into()
123    }
124
125    /// Reads the particular CPUID value if supported, returning None
126    /// otherwise.
127    fn try_read<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
128        &self,
129        value: CpuidValue<LEAF, SUBLEAF, REG, Layout>,
130    ) -> Option<Layout> {
131        self.supports(value).then(|| self.read(value))
132    }
133}
134
135impl<C: Cpuid> Cpuid for &C {
136    fn read_raw(&self, leaf: u32, subleaf: u32) -> CpuidRawResult {
137        (*self).read_raw(leaf, subleaf)
138    }
139}
140
141/// A [`CpuidReader`] that issues a CPUID instruction on each read.
142#[derive(Clone, Copy, Debug)]
143pub struct DirectCpuid;
144
145#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
146mod x86_only {
147    #[cfg(target_arch = "x86_64")]
148    use core::arch::x86_64::{__cpuid_count, CpuidResult as ArchCpuidResult};
149
150    #[cfg(target_arch = "x86")]
151    use core::arch::x86::{__cpuid_count, CpuidResult as ArchCpuidResult};
152
153    use super::*;
154
155    impl Cpuid for DirectCpuid {
156        #[inline]
157        fn read_raw(&self, leaf: u32, subleaf: u32) -> CpuidRawResult {
158            let ArchCpuidResult { eax, ebx, ecx, edx } = __cpuid_count(leaf, subleaf);
159            CpuidRawResult { eax, ebx, ecx, edx }
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::testing::x86::FakeCpuid;
168
169    const MAX_BASIC_LEAF: CpuidValue<0x0000_0000, 0x0, EAX, u32> = CpuidValue::new();
170    const FEATURE_LEAF_1_ECX: CpuidValue<0x0000_0001, 0x0, ECX, u32> = CpuidValue::new();
171    const FEATURE_LEAF_7_EBX: CpuidValue<0x0000_0007, 0x0, EBX, u32> = CpuidValue::new();
172
173    const MAX_HYPERVISOR_LEAF: CpuidValue<0x4000_0000, 0x0, EAX, u32> = CpuidValue::new();
174    const HYPERVISOR_LEAF_1_EAX: CpuidValue<0x4000_0001, 0x0, EAX, u32> = CpuidValue::new();
175    const HYPERVISOR_LEAF_2_EAX: CpuidValue<0x4000_0002, 0x0, EAX, u32> = CpuidValue::new();
176
177    const MAX_EXTENDED_LEAF: CpuidValue<0x8000_0000, 0x0, EAX, u32> = CpuidValue::new();
178    const AMD_EXTENDED_FEATURES_C: CpuidValue<0x8000_0001, 0x0, ECX, u32> = CpuidValue::new();
179    const AMD_CACHE_TOPOLOGY_A: CpuidValue<0x8000_001d, 0x0, EAX, u32> = CpuidValue::new();
180
181    // Little more than a simple compilation test of the intended usage pattern.
182    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
183    #[test]
184    fn direct_cpuid() {
185        const CPUID_MAX_LEAF: CpuidValue<0x0, 0x0, EAX, u32> = CpuidValue::new();
186        println!("Maximum CPUID leaf number: {:#x}", DirectCpuid {}.read(CPUID_MAX_LEAF));
187    }
188
189    #[test]
190    fn cpuid_supports_basic_leaves() {
191        let mut cpuid = FakeCpuid::new();
192        cpuid.set(MAX_BASIC_LEAF, 1);
193        assert!(cpuid.supports(FEATURE_LEAF_1_ECX));
194        assert!(!cpuid.supports(FEATURE_LEAF_7_EBX));
195
196        assert_eq!(cpuid.try_read(FEATURE_LEAF_1_ECX), Some(0));
197        assert_eq!(cpuid.try_read(FEATURE_LEAF_7_EBX), None);
198    }
199
200    #[test]
201    fn cpuid_supports_hypervisor_leaves() {
202        let mut cpuid = FakeCpuid::new();
203        cpuid.set(MAX_HYPERVISOR_LEAF, 0x4000_0001);
204        assert!(cpuid.supports(HYPERVISOR_LEAF_1_EAX));
205
206        assert!(!cpuid.supports(HYPERVISOR_LEAF_2_EAX));
207    }
208
209    #[test]
210    fn cpuid_supports_extended_and_topology_leaves() {
211        let mut cpuid = FakeCpuid::new();
212        cpuid.set(MAX_EXTENDED_LEAF, 0x8000_001e);
213        // Topology extensions not yet set in 0x8000_0001 ECX.
214        assert!(!cpuid.supports(AMD_CACHE_TOPOLOGY_A));
215
216        // Set topology extensions bit (bit 22).
217        cpuid.set(AMD_EXTENDED_FEATURES_C, 1 << 22);
218        assert!(cpuid.supports(AMD_CACHE_TOPOLOGY_A));
219    }
220}