Skip to main content

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}