use rtime_core::clock::LeapIndicator;
use rtime_core::source::{SourceId, SourceMeasurement};
use rtime_core::timestamp::{NtpDuration, NtpTimestamp};
#[cfg(not(target_os = "linux"))]
use crate::RefClockError;
#[derive(Debug, Clone)]
pub struct PpsEdge {
pub timestamp: NtpTimestamp,
pub sequence: u32,
}
#[cfg(target_os = "linux")]
#[allow(dead_code)]
const PPS_FETCH: u64 = 0xC00870A4;
pub struct PpsDriver {
device: String,
source_id: SourceId,
}
impl PpsDriver {
pub fn new(device: &str) -> Self {
Self {
device: device.to_string(),
source_id: SourceId::RefClock {
driver: "PPS".to_string(),
unit: 0,
},
}
}
pub fn device(&self) -> &str {
&self.device
}
pub fn process_edge(&self, edge: &PpsEdge, local_time: NtpTimestamp) -> SourceMeasurement {
let edge_frac = edge.timestamp.fraction();
let half_second = 1u32 << 31;
let offset_nanos = if edge_frac < half_second {
let frac_nanos = ((edge_frac as u64) * 1_000_000_000) >> 32;
-(frac_nanos as i64)
} else {
let complement = u32::MAX - edge_frac + 1;
let frac_nanos = ((complement as u64) * 1_000_000_000) >> 32;
frac_nanos as i64
};
let offset = NtpDuration::from_nanos(offset_nanos);
let delay = NtpDuration::from_nanos(1_000); let dispersion = NtpDuration::from_nanos(500);
SourceMeasurement {
id: self.source_id.clone(),
offset,
delay,
dispersion,
jitter: 0.000_001, stratum: 1, leap_indicator: LeapIndicator::NoWarning,
root_delay: NtpDuration::ZERO,
root_dispersion: NtpDuration::ZERO,
time: local_time,
}
}
}
#[cfg(target_os = "linux")]
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
#[allow(dead_code)]
struct PpsKtime {
sec: i64,
nsec: i32,
flags: u32,
}
#[cfg(target_os = "linux")]
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
#[allow(dead_code)]
struct PpsFdata {
info: PpsKinfo,
timeout: PpsKtime,
}
#[cfg(target_os = "linux")]
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
#[allow(dead_code)]
struct PpsKinfo {
assert_sequence: u32,
clear_sequence: u32,
assert_tu: PpsKtime,
clear_tu: PpsKtime,
current_mode: i32,
}
#[cfg(target_os = "linux")]
#[allow(dead_code)]
fn pps_ktime_to_ntp(ktime: &PpsKtime) -> NtpTimestamp {
const NTP_UNIX_DIFF: u64 = 2_208_988_800;
let ntp_seconds = (ktime.sec as u64) + NTP_UNIX_DIFF;
let fraction = ((ktime.nsec as u64) << 32) / 1_000_000_000;
NtpTimestamp::new(ntp_seconds as u32, fraction as u32)
}
#[cfg(not(target_os = "linux"))]
pub fn fetch_pps_edge(_device: &str) -> Result<PpsEdge, RefClockError> {
Err(RefClockError::DeviceNotFound(
"PPS not supported on this platform".into(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pps_driver_creation() {
let driver = PpsDriver::new("/dev/pps0");
assert_eq!(driver.device(), "/dev/pps0");
}
#[test]
fn test_pps_edge_exact_second() {
let driver = PpsDriver::new("/dev/pps0");
let edge = PpsEdge {
timestamp: NtpTimestamp::new(3_900_000_000, 0),
sequence: 1,
};
let local_time = NtpTimestamp::new(3_900_000_000, 0);
let m = driver.process_edge(&edge, local_time);
assert_eq!(m.stratum, 1);
assert_eq!(m.offset.to_nanos(), 0);
}
#[test]
fn test_pps_edge_clock_ahead() {
let driver = PpsDriver::new("/dev/pps0");
let frac = ((1u64 << 32) / 1000) as u32; let edge = PpsEdge {
timestamp: NtpTimestamp::new(3_900_000_000, frac),
sequence: 2,
};
let local_time = NtpTimestamp::now();
let m = driver.process_edge(&edge, local_time);
let offset_ns = m.offset.to_nanos();
assert!(offset_ns < 0, "expected negative offset, got {offset_ns}");
assert!(
(offset_ns + 1_000_000).abs() < 1000,
"expected ~-1ms offset, got {offset_ns} ns"
);
}
#[test]
fn test_pps_edge_clock_behind() {
let driver = PpsDriver::new("/dev/pps0");
let frac = ((999u64 * (1u64 << 32)) / 1000) as u32;
let edge = PpsEdge {
timestamp: NtpTimestamp::new(3_900_000_000, frac),
sequence: 3,
};
let local_time = NtpTimestamp::now();
let m = driver.process_edge(&edge, local_time);
let offset_ns = m.offset.to_nanos();
assert!(offset_ns > 0, "expected positive offset, got {offset_ns}");
assert!(
(offset_ns - 1_000_000).abs() < 1000,
"expected ~+1ms offset, got {offset_ns} ns"
);
}
#[test]
fn test_pps_measurement_source_id() {
let driver = PpsDriver::new("/dev/pps0");
let edge = PpsEdge {
timestamp: NtpTimestamp::new(1000, 0),
sequence: 0,
};
let m = driver.process_edge(&edge, NtpTimestamp::now());
match &m.id {
SourceId::RefClock { driver, unit } => {
assert_eq!(driver, "PPS");
assert_eq!(*unit, 0);
}
_ => panic!("expected RefClock source ID"),
}
}
#[test]
fn test_pps_measurement_properties() {
let driver = PpsDriver::new("/dev/pps0");
let edge = PpsEdge {
timestamp: NtpTimestamp::new(1000, 0),
sequence: 0,
};
let m = driver.process_edge(&edge, NtpTimestamp::now());
assert!(m.delay.to_nanos() <= 1_000); assert!(m.dispersion.to_nanos() <= 1_000);
assert_eq!(m.leap_indicator, LeapIndicator::NoWarning);
assert_eq!(m.root_delay, NtpDuration::ZERO);
assert_eq!(m.root_dispersion, NtpDuration::ZERO);
}
#[cfg(target_os = "linux")]
#[test]
fn test_pps_ktime_to_ntp() {
let ktime = PpsKtime {
sec: 0, nsec: 0,
flags: 0,
};
let ts = pps_ktime_to_ntp(&ktime);
assert_eq!(ts.seconds(), 2_208_988_800);
assert_eq!(ts.fraction(), 0);
}
#[cfg(target_os = "linux")]
#[test]
fn test_pps_ktime_to_ntp_with_nanos() {
let ktime = PpsKtime {
sec: 1_000_000,
nsec: 500_000_000, flags: 0,
};
let ts = pps_ktime_to_ntp(&ktime);
assert_eq!(ts.seconds(), (1_000_000u64 + 2_208_988_800u64) as u32);
assert_eq!(ts.fraction(), 2_147_483_648);
}
}