perf_timer/
arch.rs

1use core::sync::atomic::AtomicU64;
2
3#[cfg(target_arch = "x86_64")]
4pub use x64::X64 as Arch;
5
6#[cfg(target_arch = "aarch64")]
7pub use aarch64::Aarch64 as Arch;
8
9// QEMU uses the ACPI frequency when CPUID-based frequency determination is not available.
10const DEFAULT_ACPI_TIMER_FREQUENCY: u64 = 3579545;
11
12static PERF_FREQUENCY: AtomicU64 = AtomicU64::new(0);
13
14pub trait ArchFunctionality {
15    /// Value of the counter.
16    fn cpu_count() -> u64;
17    /// Value in Hz of how often the counter increment.
18    fn perf_frequency() -> u64;
19    /// Value the performance counter starts with when it rolls over.
20    fn cpu_count_start() -> u64 {
21        0
22    }
23    /// Value that the performance counter ends with before it rolls over.
24    fn cpu_count_end() -> u64 {
25        u64::MAX
26    }
27}
28
29#[cfg(target_arch = "x86_64")]
30pub(crate) mod x64 {
31    use super::*;
32    use core::{
33        arch::x86_64::{self, CpuidResult},
34        sync::atomic::Ordering,
35    };
36
37    pub struct X64;
38    impl ArchFunctionality for X64 {
39        fn cpu_count() -> u64 {
40            #[cfg(feature = "validate_cpu_features")]
41            {
42                // TSC support in bit 4.
43                if (unsafe { x86_64::__cpuid(0x01) }.edx & 0x10) != 0x10 {
44                    panic!("CPU does not support TSC");
45                }
46                // Invariant TSC support in bit 8.
47                if (unsafe { x86_64::__cpuid(0x80000007) }.edx & 0x100) != 0x100 {
48                    panic!("CPU does not support Invariant TSC");
49                }
50            }
51            unsafe { x86_64::_rdtsc() }
52        }
53
54        fn perf_frequency() -> u64 {
55            let cached = PERF_FREQUENCY.load(Ordering::Relaxed);
56            if cached != 0 {
57                return cached;
58            }
59
60            let hypervisor_leaf = unsafe { x86_64::__cpuid(0x1) };
61            let is_vm = (hypervisor_leaf.ecx & (1 << 31)) != 0;
62
63            if is_vm {
64                log::warn!("Running in a VM - CPUID-based frequency may not be reliable.");
65            }
66
67            let CpuidResult {
68                eax, // Ratio of TSC frequency to Core Crystal Clock frequency, denominator.
69                ebx, // Ratio of TSC frequency to Core Crystal Clock frequency, numerator.
70                ecx, // Core Crystal Clock frequency, in units of Hz.
71                ..
72            } = unsafe { x86_64::__cpuid(0x15) };
73
74            // If not a VM, attempt to use CPUID leaf 0x15
75            if !is_vm && ecx != 0 && eax != 0 && ebx != 0 {
76                let frequency = (ecx as u64 * ebx as u64) / eax as u64;
77                PERF_FREQUENCY.store(frequency, Ordering::Relaxed);
78                log::trace!("Used CPUID leaf 0x15 to determine CPU frequency: {}", frequency);
79                return frequency;
80            }
81
82            // If VM or CPUID 0x15 fails, attempt to use CPUID 0x16
83            // Based on testing in QEMU, leaf 0x16 is generally more reliable on VMs
84            let CpuidResult { eax, .. } = unsafe { x86_64::__cpuid(0x16) };
85            if eax != 0 {
86                // Leaf 0x16 gives the frequency in MHz.
87                let frequency = (eax * 1_000_000) as u64;
88                PERF_FREQUENCY.store(frequency, Ordering::Relaxed);
89                log::trace!("Used CPUID leaf 0x16 to determine CPU frequency: {}", frequency);
90                return frequency;
91            }
92
93            log::warn!("Unable to determine CPU frequency using CPUID leaves, using default ACPI timer frequency");
94
95            PERF_FREQUENCY.store(DEFAULT_ACPI_TIMER_FREQUENCY, Ordering::Relaxed);
96            DEFAULT_ACPI_TIMER_FREQUENCY
97        }
98    }
99}
100
101#[cfg(target_arch = "aarch64")]
102pub(crate) mod aarch64 {
103    use super::*;
104    use aarch64_cpu::registers::{self, Readable};
105    pub struct Aarch64;
106    impl ArchFunctionality for Aarch64 {
107        fn cpu_count() -> u64 {
108            registers::CNTPCT_EL0.get()
109        }
110
111        fn perf_frequency() -> u64 {
112            registers::CNTFRQ_EL0.get()
113        }
114    }
115}