uefi_async/common/tick.rs
1use core::sync::atomic::{AtomicU64, Ordering};
2use core::time::Duration;
3use uefi::boot::stall;
4
5/// Reads the current value of the hardware cycle counter (timestamp).
6///
7/// This function provides a high-resolution time source by accessing
8/// architecture-specific registers:
9/// * **x86 / x86_64**: Uses the `RDTSC` (Read Time Stamp Counter) instruction.
10/// * **AArch64**: Uses the `CNTVCT_EL0` (Virtual Count Register) system register.
11///
12/// # Safety
13/// While technically wrapping `unsafe` architecture instructions, this is
14/// generally safe on modern processors. However, note that the frequency
15/// of these counters may vary on older systems with power-saving features
16/// (non-invariant TSC).
17#[inline(always)]
18pub fn tick() -> u64 {
19 #[cfg(target_arch = "x86")]
20 unsafe { core::arch::x86::_rdtsc() }
21
22 #[cfg(target_arch = "x86_64")]
23 unsafe { core::arch::x86_64::_rdtsc() }
24
25 #[cfg(target_arch = "aarch64")]
26 unsafe {
27 let mut ticks: u64;
28 core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks);
29 ticks
30 }
31}
32
33/// Estimates the hardware clock frequency (ticks per second) using a blocking delay.
34///
35/// This function measures the number of ticks elapsed over a 100ms period
36/// using the UEFI `stall` service and extrapolates the result to 1 second.
37///
38/// # Behavior
39/// * This is a **blocking** operation that takes at least 100 milliseconds to complete.
40/// * It is typically called once during the initialization of the Executor
41/// to normalize task intervals.
42///
43/// # Returns
44/// The estimated number of hardware ticks per second (Hz).
45#[deprecated(since = "0.2.4", note = "Use `FREQ.hz()` instead")]
46pub fn calc_freq_blocking() -> u64 {
47 let start = tick();
48 // Use the UEFI stall service (assumed provided by the environment)
49 stall(Duration::from_millis(100));
50 let end = tick();
51 let ticks_per_100ms = end - start;
52
53 // Scale 100ms up to 1000ms (1 second)
54 ticks_per_100ms * 10
55}
56
57/// A globally accessible frequency provider for high-precision timing.
58///
59/// `ClockFreq` stores the hardware clock frequency (typically derived from the CPU TSC)
60/// converted into various time units. These values are used to transform raw hardware
61/// "ticks" into human-readable durations without performing expensive division
62/// in the hot loop of the executor.
63///
64/// # Design
65/// All frequency components are stored as [`AtomicU64`] to ensure thread-safety
66/// (if multiple cores are used) and to allow the values to be initialized at runtime
67/// while remaining in a `static` context.
68#[derive(Debug)]
69pub struct ClockFreq { hz: AtomicU64, ms: AtomicU64, us: AtomicU64, ns: AtomicU64, ps: AtomicU64 }
70impl ClockFreq {
71 /// Returns the system frequency in Hertz (ticks per second).
72 #[inline(always)]
73 pub fn hz(&self) -> u64 { self.hz.load(Ordering::Relaxed) }
74
75 /// Returns the number of ticks per millisecond.
76 #[inline(always)]
77 pub fn ms(&self) -> u64 { self.ms.load(Ordering::Relaxed) }
78
79 /// Returns the number of ticks per microsecond.
80 #[inline(always)]
81 pub fn us(&self) -> u64 { self.us.load(Ordering::Relaxed) }
82
83 /// Returns the number of ticks per nanosecond.
84 #[inline(always)]
85 pub fn ns(&self) -> u64 { self.ns.load(Ordering::Relaxed) }
86
87 /// Returns the number of ticks per picosecond.
88 #[inline(always)]
89 pub fn ps(&self) -> u64 { self.ps.load(Ordering::Relaxed) }
90}
91
92/// The global frequency calibration data.
93///
94/// This must be initialized via [`init_clock_freq`] before starting the executor,
95/// otherwise all timing-based futures will fail to progress or return 0.
96pub static FREQ: ClockFreq = ClockFreq {
97 hz: AtomicU64::new(0), ms: AtomicU64::new(0), us: AtomicU64::new(0),
98 ns: AtomicU64::new(0), ps: AtomicU64::new(0),
99};
100
101/// Calibrates the hardware clock frequency by sampling for 50ms.
102///
103/// This function performs a blocking stall for 50 milliseconds to measure the
104/// difference in hardware ticks (TSC). It then populates the global [`FREQ`]
105/// structure with pre-calculated unit ratios.
106///
107/// # Accuracy
108/// Using a 50ms window provides a balance between boot speed and measurement accuracy,
109/// typically resulting in less than 0.01% error on modern UEFI platforms.
110///
111/// # Safety
112/// This function must be called exactly once during the initiation phase
113/// of the application. Calling it multiple times may cause slight variations
114/// in reported frequency if the CPU power state changes.
115pub(crate) fn init_clock_freq() -> u64 {
116 let start = tick();
117 stall(Duration::from_millis(50)); // Sampling time 50ms
118 let end = tick();
119
120 let ticks_per_50ms = end - start;
121 let hz = ticks_per_50ms * 20; // Frequency converted to 1 second
122
123 // Pre-calculate and store in static variables
124 FREQ.hz.store(hz, Ordering::Relaxed);
125 FREQ.ms.store((hz / 1000).max(1), Ordering::Relaxed);
126 FREQ.us.store((hz / 100_0000).max(1), Ordering::Relaxed);
127 FREQ.ns.store((hz / 10_0000_0000).max(1), Ordering::Relaxed);
128 // Picosecond (ps) time is only meaningful when the clock speed is > 1 GHz; otherwise, the result is 0.
129 FREQ.ps.store(hz / 1_0000_0000_0000, Ordering::Relaxed);
130
131 hz
132}