statime_linux/clock/
mod.rs

1//! Implementation of the abstract clock for the linux platform
2
3use std::path::Path;
4
5use clock_steering::{unix::UnixClock, TimeOffset};
6use statime::{
7    config::{LeapIndicator, TimePropertiesDS},
8    time::{Duration, Time},
9    Clock, OverlayClock, SharedClock,
10};
11
12#[derive(Debug, Clone)]
13pub struct LinuxClock {
14    clock: clock_steering::unix::UnixClock,
15    is_tai: bool,
16}
17
18impl LinuxClock {
19    pub const CLOCK_TAI: Self = Self {
20        clock: UnixClock::CLOCK_TAI,
21        is_tai: true,
22    };
23
24    pub fn open(path: impl AsRef<Path>) -> std::io::Result<Self> {
25        let clock = UnixClock::open(path)?;
26
27        Ok(Self {
28            clock,
29            is_tai: false,
30        })
31    }
32
33    // This forces the clock to start with seconds such that the current time in
34    // nanoseconds is representable as a u64.
35    pub fn init(&mut self) -> Result<(), clock_steering::unix::Error> {
36        use clock_steering::Clock;
37
38        let ts = self.clock.now()?;
39        #[allow(clippy::unnecessary_cast)]
40        if ts.seconds < 0 || ts.seconds as i64 > (u64::MAX / 1000000000) as i64 {
41            self.clock.step_clock(TimeOffset {
42                seconds: -ts.seconds + 1,
43                nanos: 0,
44            })?;
45        }
46
47        Ok(())
48    }
49
50    pub fn open_idx(idx: u32) -> std::io::Result<Self> {
51        let path = format!("/dev/ptp{}", idx);
52        Self::open(path)
53    }
54
55    /// Return three timestamps t1 t2 and t3 minted in that order.
56    /// T1 and T3 are minted using the system TAI clock and T2 by the hardware
57    /// clock
58    pub fn system_offset(&self) -> Result<(Time, Time, Time), clock_steering::unix::Error> {
59        // The clock crate's system offset gives the T1 and T3 timestamps on the
60        // CLOCK_REALTIME timescale which is UTC, not TAI, so we need to correct
61        // here.
62        self.clock.system_offset().map(|(mut t1, t2, mut t3)| {
63            use clock_steering::Clock;
64            let tai_offset = UnixClock::CLOCK_REALTIME.get_tai().unwrap();
65            t1.seconds += tai_offset as libc::time_t;
66            t3.seconds += tai_offset as libc::time_t;
67            (
68                clock_timestamp_to_time(t1),
69                clock_timestamp_to_time(t2),
70                clock_timestamp_to_time(t3),
71            )
72        })
73    }
74
75    pub fn get_tai_offset(&self) -> Result<i32, clock_steering::unix::Error> {
76        use clock_steering::Clock;
77        if self.is_tai {
78            UnixClock::CLOCK_REALTIME.get_tai()
79        } else {
80            self.clock.get_tai()
81        }
82    }
83}
84
85fn clock_timestamp_to_time(t: clock_steering::Timestamp) -> Time {
86    Time::from_nanos((t.seconds as u64) * 1_000_000_000 + (t.nanos as u64))
87}
88
89fn time_from_timestamp(timestamp: clock_steering::Timestamp, fallback: Time) -> Time {
90    let Ok(seconds): Result<u64, _> = timestamp.seconds.try_into() else {
91        return fallback;
92    };
93
94    let nanos = seconds * 1_000_000_000 + timestamp.nanos as u64;
95    Time::from_nanos_subnanos(nanos, 0)
96}
97
98impl Clock for LinuxClock {
99    type Error = clock_steering::unix::Error;
100
101    fn now(&self) -> Time {
102        use clock_steering::Clock;
103
104        let timestamp = self.clock.now().unwrap();
105        time_from_timestamp(timestamp, Time::from_fixed_nanos(0))
106    }
107
108    fn set_frequency(&mut self, freq: f64) -> Result<Time, Self::Error> {
109        use clock_steering::Clock;
110        log::trace!("Setting clock frequency to {:e}ppm", freq);
111        let timestamp = if self.is_tai {
112            // Clock tai can't directly adjust frequency, so drive this through
113            // clock_realtime and adjust the received timestamp
114            let mut ts = UnixClock::CLOCK_REALTIME.set_frequency(freq)?;
115            ts.seconds += UnixClock::CLOCK_REALTIME.get_tai()? as libc::time_t;
116            ts
117        } else {
118            self.clock.set_frequency(freq)?
119        };
120        Ok(time_from_timestamp(timestamp, statime::Clock::now(self)))
121    }
122
123    fn step_clock(&mut self, time_offset: Duration) -> Result<Time, Self::Error> {
124        use clock_steering::Clock;
125
126        // Since we want nanos to be in [0,1_000_000_000), we need
127        // euclidean division and remainder.
128        let offset_nanos: i128 = time_offset.nanos_rounded();
129        let offset = TimeOffset {
130            seconds: offset_nanos
131                .div_euclid(1_000_000_000)
132                .try_into()
133                .expect("Unexpected jump larger than 2^64 seconds"),
134            nanos: offset_nanos.rem_euclid(1_000_000_000) as _, // Result will always fit in u32
135        };
136
137        log::trace!(
138            "Stepping clock {:e}ns",
139            (offset.seconds as f64) * 1e9 + (offset.nanos as f64)
140        );
141
142        let timestamp = if self.is_tai {
143            // Clock tai can't directly step, so drive this through clock_realtime
144            // and adjust the received timestamp
145            let mut ts = UnixClock::CLOCK_REALTIME.step_clock(offset)?;
146            ts.seconds += UnixClock::CLOCK_REALTIME.get_tai()? as libc::time_t;
147            ts
148        } else {
149            self.clock.step_clock(offset)?
150        };
151        Ok(time_from_timestamp(timestamp, statime::Clock::now(self)))
152    }
153
154    fn set_properties(&mut self, time_properties: &TimePropertiesDS) -> Result<(), Self::Error> {
155        use clock_steering::Clock;
156
157        // These properties should always be communicated to the system clock.
158
159        if let Some(offset) = time_properties.utc_offset() {
160            UnixClock::CLOCK_REALTIME.set_tai(offset as _)?;
161        }
162
163        UnixClock::CLOCK_REALTIME.set_leap_seconds(match time_properties.leap_indicator() {
164            LeapIndicator::NoLeap => clock_steering::LeapIndicator::NoWarning,
165            LeapIndicator::Leap61 => clock_steering::LeapIndicator::Leap61,
166            LeapIndicator::Leap59 => clock_steering::LeapIndicator::Leap59,
167        })?;
168
169        Ok(())
170    }
171}
172
173pub fn libc_timespec_into_instant(spec: libc::timespec) -> Time {
174    Time::from_fixed_nanos(spec.tv_sec as i128 * 1_000_000_000i128 + spec.tv_nsec as i128)
175}
176
177pub trait PortTimestampToTime {
178    fn port_timestamp_to_time(&self, ts: timestamped_socket::socket::Timestamp) -> Time;
179}
180
181impl PortTimestampToTime for LinuxClock {
182    fn port_timestamp_to_time(&self, mut ts: timestamped_socket::socket::Timestamp) -> Time {
183        // get_tai gives zero if this is a hardware clock, and the needed
184        // correction when this port uses software timestamping
185        ts.seconds += self.get_tai_offset().expect("Unable to get tai offset") as i64;
186        Time::from_fixed_nanos(ts.seconds as i128 * 1_000_000_000i128 + ts.nanos as i128)
187    }
188}
189
190impl PortTimestampToTime for OverlayClock<LinuxClock> {
191    fn port_timestamp_to_time(&self, ts: timestamped_socket::socket::Timestamp) -> Time {
192        let roclock_time = self.underlying().port_timestamp_to_time(ts);
193        self.time_from_underlying(roclock_time)
194    }
195}
196
197impl PortTimestampToTime for SharedClock<OverlayClock<LinuxClock>> {
198    fn port_timestamp_to_time(&self, ts: timestamped_socket::socket::Timestamp) -> Time {
199        self.0.lock().unwrap().port_timestamp_to_time(ts)
200    }
201}