RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / time.rs
4338 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//! A microsecond clock for the network stack.
//!
//! The PIT ticks only ~18.2 times a second (55 ms), far too coarse to time
//! a ping (QEMU answers in well under a millisecond) or to run TCP's
//! retransmit timers. Every x86-64 CPU has a *time stamp counter* (TSC): a
//! 64-bit counter that goes up at a fixed rate, read with `rdtsc`. We don't
//! know that rate, so we measure it once against the PIT (apic.rs does this
//! while it calibrates the APIC timer, over 2 PIT ticks) and from then on
//! `now_us` is just `rdtsc / rate`.
//!
//! Before calibration (or if it never happens, e.g. in some tests) we fall
//! back to counting PIT ticks.

use core::sync::atomic::{AtomicU64, Ordering};

/// One PIT tick in microseconds (1 / 18.2065 Hz).
pub const PIT_TICK_US: u64 = 54_925;

static TSC_PER_MS: AtomicU64 = AtomicU64::new(0);
static TSC_START: AtomicU64 = AtomicU64::new(0);

fn rdtsc() -> u64 {
    unsafe { core::arch::x86_64::_rdtsc() }
}

/// Called with a TSC reading at a PIT tick edge (`start`) and the number of
/// ticks it took to reach `end`.
pub fn calibrate(start: u64, end: u64, pit_ticks: u64) {
    let per_ms = (end - start) * 1000 / (pit_ticks * PIT_TICK_US);
    // Keep the clock monotonic: the TSC clock starts at the PIT uptime.
    let uptime_us = crate::interrupts::ticks() * PIT_TICK_US;
    TSC_START.store(end.saturating_sub(uptime_us / 1000 * per_ms), Ordering::Relaxed);
    TSC_PER_MS.store(per_ms.max(1), Ordering::Relaxed);
}

/// Microseconds since boot.
pub fn now_us() -> u64 {
    let per_ms = TSC_PER_MS.load(Ordering::Relaxed);
    if per_ms == 0 {
        return crate::interrupts::ticks() * PIT_TICK_US;
    }
    (rdtsc() - TSC_START.load(Ordering::Relaxed)) * 1000 / per_ms
}

/// The PIT tick by which `us` microseconds from now will surely have
/// passed. For sleeping/timeouts, which may run late but never early: we
/// may be anywhere inside the current ~55 ms tick, so one extra tick.
pub fn deadline_after_us(us: u64) -> u64 {
    crate::interrupts::ticks() + us.div_ceil(PIT_TICK_US) + 1
}

// ---------------------------------------------------------------------------
// Wall-clock time (CLOCK_REALTIME)
// ---------------------------------------------------------------------------

/// Unix time (microseconds since 1970-01-01 UTC) at boot (now_us = 0),
/// read from the CMOS clock on first use. 0 = not read yet.
static BOOT_REALTIME_US: AtomicU64 = AtomicU64::new(0);

/// Read a CMOS register (the PC's battery-backed real-time clock).
fn cmos(reg: u8) -> u8 {
    use x86_64::instructions::port::Port;
    unsafe {
        Port::<u8>::new(0x70).write(reg);
        Port::<u8>::new(0x71).read()
    }
}

/// The RTC's date and time as Unix seconds. QEMU's RTC runs on UTC.
fn rtc_unix_seconds() -> u64 {
    x86_64::instructions::interrupts::without_interrupts(|| {
        // Don't read in the middle of an update (bit 7 of status A).
        while cmos(0x0A) & 0x80 != 0 {
            core::hint::spin_loop();
        }
        let [sec, min, hour, day, month, year] = [0x00, 0x02, 0x04, 0x07, 0x08, 0x09].map(cmos);
        let status_b = cmos(0x0B);
        // Usually the values are BCD (0x59 = 59) unless bit 2 says binary.
        let bin = |v: u8| if status_b & 0x04 != 0 { v as u64 } else { ((v >> 4) * 10 + (v & 0xf)) as u64 };
        let mut h = bin(hour & 0x7f);
        if status_b & 0x02 == 0 && hour & 0x80 != 0 {
            h = (h + 12) % 24; // 12-hour clock, PM
        }
        let (y, m, d) = (2000 + bin(year), bin(month), bin(day));
        days_from_civil(y, m, d) * 86400 + h * 3600 + bin(min) * 60 + bin(sec)
    })
}

/// Days since 1970-01-01 for a date (Howard Hinnant's algorithm).
fn days_from_civil(y: u64, m: u64, d: u64) -> u64 {
    let y = if m <= 2 { y - 1 } else { y };
    let era = y / 400;
    let yoe = y - era * 400;
    let mp = (m + 9) % 12;
    let doy = (153 * mp + 2) / 5 + d - 1;
    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
    era * 146097 + doe - 719468
}

/// Microseconds since 1970-01-01 UTC (clock_gettime(CLOCK_REALTIME)).
pub fn realtime_us() -> u64 {
    let mut boot = BOOT_REALTIME_US.load(Ordering::Relaxed);
    if boot == 0 {
        boot = (rtc_unix_seconds() * 1_000_000).saturating_sub(now_us()).max(1);
        BOOT_REALTIME_US.store(boot, Ordering::Relaxed);
    }
    boot + now_us()
}