hal_x86_64/time/
tsc.rs

1use crate::cpu::{intrinsics, FeatureNotSupported};
2use core::{
3    sync::atomic::{AtomicU32, Ordering::*},
4    time::Duration,
5};
6use maitake::time::Clock;
7use raw_cpuid::CpuId;
8
9#[derive(Copy, Clone, Debug)]
10pub struct Rdtsc(());
11
12impl Rdtsc {
13    pub fn is_supported() -> bool {
14        CpuId::new()
15            .get_feature_info()
16            .map(|features| features.has_tsc())
17            .unwrap_or(false)
18    }
19
20    pub fn new() -> Result<Self, FeatureNotSupported> {
21        if Self::is_supported() {
22            Ok(Self(()))
23        } else {
24            Err(FeatureNotSupported::new("rdtsc"))
25        }
26    }
27
28    /// Reads the current value of the timestamp counter.
29    #[inline(always)]
30    #[must_use]
31    pub fn read_timestamp(&self) -> u64 {
32        unsafe {
33            // safety: if an instance of this type was constructed, then we
34            // checked that `rdtsc` is supported via `cpuid`.
35            intrinsics::rdtsc()
36        }
37    }
38
39    /// Returns a [`maitake::time::Clock`] defining a clock that uses `rdtsc`
40    /// timestamps to produce `maitake` ticks.
41    pub fn into_maitake_clock(self) -> Result<Clock, &'static str> {
42        // TODO(eliza): fix this
43        #![allow(unreachable_code)]
44        return Err("calibration routine doesn't really work yet, sorry!");
45
46        const NOT_YET_CALIBRATED: u32 = u32::MAX;
47
48        // 50ms is stolen from Linux, so i'm assuming its a reasonable duration
49        // for PIT-based calibration:
50        // https://github.com/torvalds/linux/blob/4ae004a9bca8bef118c2b4e76ee31c7df4514f18/arch/x86/kernel/tsc.c#L688-L839
51        const PIT_SLEEP_DURATION: Duration = Duration::from_millis(50);
52
53        static MAITAKE_TICK_SHIFT: AtomicU32 = AtomicU32::new(NOT_YET_CALIBRATED);
54
55        fn now() -> u64 {
56            let rdtsc = unsafe { intrinsics::rdtsc() };
57            let shift = MAITAKE_TICK_SHIFT.load(Relaxed);
58            rdtsc >> shift
59        }
60
61        tracing::info!("calibrating RDTSC Maitake clock from PIT...");
62        let mut pit = super::PIT.lock();
63
64        let t0 = self.read_timestamp();
65        pit.sleep_blocking(PIT_SLEEP_DURATION)
66            .expect("PIT sleep failed for some reason");
67        let t1 = self.read_timestamp();
68
69        let elapsed_cycles = t1 - t0;
70        tracing::debug!(t0, t1, elapsed_cycles, "slept for {PIT_SLEEP_DURATION:?}");
71
72        let mut shift = 0;
73        loop {
74            assert!(
75                shift < 64,
76                "shifted away all the precision in the timestamp counter! \
77                this definitely should never happen..."
78            );
79            let elapsed_ticks = elapsed_cycles >> shift;
80            tracing::debug!(shift, elapsed_ticks, "trying a new tick shift");
81
82            let elapsed_ticks: u32 = elapsed_ticks.try_into().expect(
83                "there is no way that a 50ms sleep duration is more than \
84                    u32::MAX rdtsc cycles...right?",
85            );
86
87            let tick_duration = PIT_SLEEP_DURATION / elapsed_ticks;
88            if tick_duration.as_nanos() > 0 {
89                // Ladies and gentlemen...we got him!
90                tracing::info!(?tick_duration, shift, "calibrated RDTSC Maitake clock");
91                MAITAKE_TICK_SHIFT
92                    .compare_exchange(NOT_YET_CALIBRATED, shift, AcqRel, Acquire)
93                    .map_err(|_| "RDTSC Maitake clock has already been calibrated")?;
94                return Ok(Clock::new(tick_duration, now).named("CLOCK_RDTSC"));
95            } else {
96                shift += 1;
97            }
98        }
99    }
100}