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
66impl<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>> Default
67    for CpuidValue<LEAF, SUBLEAF, REG, Layout>
68{
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74/// An abstracted means of reading CPUID values.
75pub trait Cpuid {
76    /// Returns the CPUID values for a given leaf and subleaf.
77    fn read_raw(&self, leaf: u32, subleaf: u32) -> CpuidRawResult;
78
79    /// Whether a CPUID leaf is supported, as associated with the provided
80    /// [`Cpuid`] marker.
81    fn supports<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
82        &self,
83        _cpuid: CpuidValue<LEAF, SUBLEAF, REG, Layout>,
84    ) -> bool {
85        const MAX_BASE_LEAF: CpuidValue<0x0000_0000, 0x0, EAX, u32> = CpuidValue::new();
86        const MAX_HYPERVISOR_LEAF: CpuidValue<0x4000_0000, 0x0, EAX, u32> = CpuidValue::new();
87        const MAX_EXTENDED_LEAF: CpuidValue<0x8000_0000, 0x0, EAX, u32> = CpuidValue::new();
88
89        const AMD_EXTENDED_FEATURES_C: CpuidValue<0x8000_0001, 0x0, ECX, u32> = CpuidValue::new();
90        const AMD_EXTENDED_FEATURES_C_TOPOLOGY_EXTENSIONS_BIT: u32 = 22;
91
92        const AMD_CACHE_TOPOLOGY_LEAF: u32 = 0x8000_001d;
93        const AMD_PROCESSOR_TOPOLOGY_LEAF: u32 = 0x8000_001e;
94
95        if LEAF >= MAX_EXTENDED_LEAF.leaf() {
96            if LEAF > self.read(MAX_EXTENDED_LEAF) {
97                return false;
98            }
99
100            // If topology extensions are not advertised, these leaves are reserved.
101            if LEAF == AMD_CACHE_TOPOLOGY_LEAF || LEAF == AMD_PROCESSOR_TOPOLOGY_LEAF {
102                return (self.read(AMD_EXTENDED_FEATURES_C)
103                    & (1 << AMD_EXTENDED_FEATURES_C_TOPOLOGY_EXTENSIONS_BIT))
104                    != 0;
105            }
106
107            return true;
108        }
109
110        if LEAF >= MAX_HYPERVISOR_LEAF.leaf() {
111            return LEAF <= self.read(MAX_HYPERVISOR_LEAF);
112        }
113
114        LEAF <= self.read(MAX_BASE_LEAF)
115    }
116
117    /// Reads the particular CPUID value.
118    fn read<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
119        &self,
120        _value: CpuidValue<LEAF, SUBLEAF, REG, Layout>,
121    ) -> Layout {
122        let raw = self.read_raw(LEAF, SUBLEAF);
123        match REG {
124            EAX => raw.eax,
125            EBX => raw.ebx,
126            ECX => raw.ecx,
127            EDX => raw.edx,
128            _ => unreachable!("REG validated on Cpuid construction"),
129        }
130        .into()
131    }
132
133    /// Reads the particular CPUID value if supported, returning None
134    /// otherwise.
135    fn try_read<const LEAF: u32, const SUBLEAF: u32, const REG: u8, Layout: LayoutOver<u32>>(
136        &self,
137        value: CpuidValue<LEAF, SUBLEAF, REG, Layout>,
138    ) -> Option<Layout> {
139        self.supports(value).then(|| self.read(value))
140    }
141}
142
143impl<C: Cpuid> Cpuid for &C {
144    fn read_raw(&self, leaf: u32, subleaf: u32) -> CpuidRawResult {
145        (*self).read_raw(leaf, subleaf)
146    }
147}
148
149/// A [`CpuidReader`] that issues a CPUID instruction on each read.
150#[derive(Clone, Copy, Debug)]
151pub struct DirectCpuid;
152
153#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
154mod x86_only {
155    #[cfg(target_arch = "x86_64")]
156    use core::arch::x86_64::{__cpuid_count, CpuidResult as ArchCpuidResult};
157
158    #[cfg(target_arch = "x86")]
159    use core::arch::x86::{__cpuid_count, CpuidResult as ArchCpuidResult};
160
161    use super::*;
162
163    impl Cpuid for DirectCpuid {
164        #[inline]
165        fn read_raw(&self, leaf: u32, subleaf: u32) -> CpuidRawResult {
166            let ArchCpuidResult { eax, ebx, ecx, edx } = __cpuid_count(leaf, subleaf);
167            CpuidRawResult { eax, ebx, ecx, edx }
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use crate::testing::x86::FakeCpuid;
176
177    const MAX_BASIC_LEAF: CpuidValue<0x0000_0000, 0x0, EAX, u32> = CpuidValue::new();
178    const FEATURE_LEAF_1_ECX: CpuidValue<0x0000_0001, 0x0, ECX, u32> = CpuidValue::new();
179    const FEATURE_LEAF_7_EBX: CpuidValue<0x0000_0007, 0x0, EBX, u32> = CpuidValue::new();
180
181    const MAX_HYPERVISOR_LEAF: CpuidValue<0x4000_0000, 0x0, EAX, u32> = CpuidValue::new();
182    const HYPERVISOR_LEAF_1_EAX: CpuidValue<0x4000_0001, 0x0, EAX, u32> = CpuidValue::new();
183    const HYPERVISOR_LEAF_2_EAX: CpuidValue<0x4000_0002, 0x0, EAX, u32> = CpuidValue::new();
184
185    const MAX_EXTENDED_LEAF: CpuidValue<0x8000_0000, 0x0, EAX, u32> = CpuidValue::new();
186    const AMD_EXTENDED_FEATURES_C: CpuidValue<0x8000_0001, 0x0, ECX, u32> = CpuidValue::new();
187    const AMD_CACHE_TOPOLOGY_A: CpuidValue<0x8000_001d, 0x0, EAX, u32> = CpuidValue::new();
188
189    // Little more than a simple compilation test of the intended usage pattern.
190    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
191    #[test]
192    fn direct_cpuid() {
193        const CPUID_MAX_LEAF: CpuidValue<0x0, 0x0, EAX, u32> = CpuidValue::new();
194        println!("Maximum CPUID leaf number: {:#x}", DirectCpuid {}.read(CPUID_MAX_LEAF));
195    }
196
197    #[test]
198    fn cpuid_supports_basic_leaves() {
199        let mut cpuid = FakeCpuid::new();
200        cpuid.set(MAX_BASIC_LEAF, 1);
201        assert!(cpuid.supports(FEATURE_LEAF_1_ECX));
202        assert!(!cpuid.supports(FEATURE_LEAF_7_EBX));
203
204        assert_eq!(cpuid.try_read(FEATURE_LEAF_1_ECX), Some(0));
205        assert_eq!(cpuid.try_read(FEATURE_LEAF_7_EBX), None);
206    }
207
208    #[test]
209    fn cpuid_supports_hypervisor_leaves() {
210        let mut cpuid = FakeCpuid::new();
211        cpuid.set(MAX_HYPERVISOR_LEAF, 0x4000_0001);
212        assert!(cpuid.supports(HYPERVISOR_LEAF_1_EAX));
213
214        assert!(!cpuid.supports(HYPERVISOR_LEAF_2_EAX));
215    }
216
217    #[test]
218    fn cpuid_supports_extended_and_topology_leaves() {
219        let mut cpuid = FakeCpuid::new();
220        cpuid.set(MAX_EXTENDED_LEAF, 0x8000_001e);
221        // Topology extensions not yet set in 0x8000_0001 ECX.
222        assert!(!cpuid.supports(AMD_CACHE_TOPOLOGY_A));
223
224        // Set topology extensions bit (bit 22).
225        cpuid.set(AMD_EXTENDED_FEATURES_C, 1 << 22);
226        assert!(cpuid.supports(AMD_CACHE_TOPOLOGY_A));
227    }
228}