proka_kernel/libs/time/tsc.rs
1use super::pit::PIT;
2use core::arch::x86_64::_rdtsc;
3use core::sync::atomic::{AtomicU64, Ordering};
4// TSC frequency in Hz
5static TSC_FREQUENCY: AtomicU64 = AtomicU64::new(0);
6
7/// Initialize TSC by calibrating it against the PIT
8pub fn init() {
9 let mut pit = PIT.lock();
10
11 // Check if TSC is supported? (Assuming yes for x86_64)
12
13 // We will wait for approximately 50ms worth of PIT ticks
14 // 1193182 Hz / 1000 * 50 ~= 59659 ticks
15 // This fits in u16 (65535)
16 const PIT_FREQ: u64 = 1_193_182;
17 const CAL_MS: u64 = 50;
18 const TARGET_TICKS: u16 = (PIT_FREQ * CAL_MS / 1000) as u16;
19
20 // Run calibration with interrupts disabled to minimize jitter
21 let freq = x86_64::instructions::interrupts::without_interrupts(|| {
22 // Setup PIT Ch2 in Mode 0 with max count
23 // We start from 0xFFFF (65535) and count down
24 pit.start_one_shot(0xFFFF);
25
26 // Wait for a short while to ensure counting started and stable
27 // (Just reading it once is usually enough to settle?)
28 let start_pit = pit.read_count();
29 let start_tsc = unsafe { _rdtsc() };
30
31 let mut end_pit;
32 let mut end_tsc;
33
34 loop {
35 end_pit = pit.read_count();
36 end_tsc = unsafe { _rdtsc() };
37
38 // Check delta (handling potential wrap if any, though Mode 0 shouldn't)
39 // Since we count down: delta = start - end
40 if start_pit >= end_pit {
41 let delta = start_pit - end_pit;
42 if delta >= TARGET_TICKS {
43 break;
44 }
45 } else {
46 // If end > start, something weird happened (reload? or wrap?)
47 // Just break to avoid infinite loop
48 break;
49 }
50 }
51
52 let pit_delta = (start_pit - end_pit) as u64;
53 let tsc_delta = end_tsc - start_tsc;
54
55 // Calculate Frequency
56 // freq = tsc_delta / time
57 // time = pit_delta / PIT_FREQ
58 // freq = tsc_delta * PIT_FREQ / pit_delta
59
60 if pit_delta == 0 {
61 0 // Failed
62 } else {
63 (tsc_delta * PIT_FREQ) / pit_delta
64 }
65 });
66
67 TSC_FREQUENCY.store(freq, Ordering::Relaxed);
68}
69
70/// Read the current TSC value
71pub fn read() -> u64 {
72 // Use lfence to prevent out-of-order execution if needed,
73 // but for simple timing _rdtsc is often sufficient.
74 // _rdtscp is better if available.
75 unsafe { _rdtsc() }
76}
77
78/// Get the TSC frequency in Hz
79pub fn frequency() -> u64 {
80 TSC_FREQUENCY.load(Ordering::Relaxed)
81}
82
83/// Get time since boot in seconds (f64)
84pub fn time_since_boot() -> f64 {
85 let freq = frequency();
86 if freq == 0 {
87 return 0.0;
88 }
89 let ticks = read();
90 ticks as f64 / freq as f64
91}
92
93/// Sleep for a given number of microseconds using TSC
94/// Requires initialization first
95pub fn sleep_us(us: u64) {
96 let freq = frequency();
97 if freq == 0 {
98 // Fallback to PIT if TSC not calibrated
99 PIT.lock().sleep_blocking(us);
100 return;
101 }
102
103 let ticks = (us * freq) / 1_000_000;
104 let start = read();
105 while read() - start < ticks {
106 core::hint::spin_loop();
107 }
108}