Skip to main content

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}