strat9_kernel/arch/x86_64/boot_timestamp.rs
1//! Boot timestamp : TSC-based elapsed time from kernel entry.
2//!
3//! Captures `rdtsc()` at the very start of `kernel_main` and exposes
4//! `elapsed_ms()` / `elapsed_us()` for boot milestone logging.
5//!
6//! Before APIC timer calibration the TSC frequency is unknown, so we
7//! use a conservative default (2 GHz). Call `calibrate()` once the
8//! real frequency is known to get accurate readings.
9
10use core::sync::atomic::{AtomicU64, Ordering};
11
12/// TSC value captured at kernel entry.
13static BOOT_TSC: AtomicU64 = AtomicU64::new(0);
14
15/// TSC frequency in KHz. Default 2_000_000 KHz (= 2 GHz) until calibrated.
16static TSC_KHZ: AtomicU64 = AtomicU64::new(2_000_000);
17
18/// Capture the boot TSC. Must be called once, as early as possible.
19pub fn init() {
20 BOOT_TSC.store(super::rdtsc(), Ordering::Relaxed);
21}
22
23/// Refine TSC frequency after timer calibration.
24///
25/// `known_interval_ns` : duration of the reference interval in nanoseconds.
26/// `tsc_delta` : TSC ticks measured over that interval.
27///
28/// Example: if the APIC timer calibration measured 10 ms (10_000_000 ns)
29/// and `tsc_delta` = 20_000_000 cycles → TSC runs at 2 GHz.
30pub fn calibrate(known_interval_ns: u64, tsc_delta: u64) {
31 if known_interval_ns == 0 || tsc_delta == 0 {
32 return;
33 }
34 let khz = tsc_delta.saturating_mul(1_000_000) / known_interval_ns;
35 calibrate_khz(khz);
36}
37
38/// Set TSC frequency directly (used for CPUID leaf 0x15 calibration).
39pub fn calibrate_khz(khz: u64) {
40 // Sanity check: reject absurdly low frequencies (< 100 MHz).
41 const MIN_SANE_KHZ: u64 = 100_000;
42 if khz >= MIN_SANE_KHZ {
43 TSC_KHZ.store(khz, Ordering::Relaxed);
44 }
45}
46
47/// TSC ticks elapsed since `init()`.
48#[inline]
49fn elapsed_tsc() -> u64 {
50 let boot = BOOT_TSC.load(Ordering::Relaxed);
51 if boot == 0 {
52 return 0;
53 }
54 super::rdtsc().wrapping_sub(boot)
55}
56
57/// Milliseconds elapsed since kernel entry.
58#[inline]
59pub fn elapsed_ms() -> u64 {
60 let khz = TSC_KHZ.load(Ordering::Relaxed);
61 if khz == 0 {
62 return 0;
63 }
64 elapsed_tsc() / khz
65}
66
67/// Microseconds elapsed since kernel entry.
68#[inline]
69pub fn elapsed_us() -> u64 {
70 let khz = TSC_KHZ.load(Ordering::Relaxed);
71 if khz == 0 {
72 return 0;
73 }
74 // tsc / (khz / 1000) = tsc * 1000 / khz
75 elapsed_tsc().saturating_mul(1_000) / khz
76}
77
78/// Current TSC frequency in KHz (for external conversions).
79#[inline]
80pub fn tsc_khz() -> u64 {
81 TSC_KHZ.load(Ordering::Relaxed)
82}