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 // tsc_khz = tsc_delta / (known_interval_ns / 1_000_000)
35 // = tsc_delta * 1_000_000 / known_interval_ns
36 let khz = tsc_delta.saturating_mul(1_000_000) / known_interval_ns;
37 if khz > 0 {
38 TSC_KHZ.store(khz, Ordering::Relaxed);
39 }
40}
41
42/// TSC ticks elapsed since `init()`.
43#[inline]
44fn elapsed_tsc() -> u64 {
45 let boot = BOOT_TSC.load(Ordering::Relaxed);
46 if boot == 0 {
47 return 0;
48 }
49 super::rdtsc().wrapping_sub(boot)
50}
51
52/// Milliseconds elapsed since kernel entry.
53#[inline]
54pub fn elapsed_ms() -> u64 {
55 let khz = TSC_KHZ.load(Ordering::Relaxed);
56 if khz == 0 {
57 return 0;
58 }
59 elapsed_tsc() / khz
60}
61
62/// Microseconds elapsed since kernel entry.
63#[inline]
64pub fn elapsed_us() -> u64 {
65 let khz = TSC_KHZ.load(Ordering::Relaxed);
66 if khz == 0 {
67 return 0;
68 }
69 // tsc / (khz / 1000) = tsc * 1000 / khz
70 elapsed_tsc().saturating_mul(1_000) / khz
71}
72
73/// Current TSC frequency in KHz (for external conversions).
74#[inline]
75pub fn tsc_khz() -> u64 {
76 TSC_KHZ.load(Ordering::Relaxed)
77}