feat: add PTP support with PHC probe

Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
This commit is contained in:
Chaos Rogers 2025-10-21 22:41:19 +01:00
parent 2e8bc9ac5e
commit 5ba0421f76
3 changed files with 128 additions and 0 deletions

View file

@ -21,5 +21,9 @@ log = { version = "0.4", features = ["std"] }
daemonize = "0.5.0"
num-rational = "0.4"
num-traits = "0.2"
libc = "0.2"
[[bin]]
name = "ptp_probe"
path = "src/bin/ptp_probe.rs"

1
src/lib.rs Normal file
View file

@ -0,0 +1 @@
pub mod ptp;

123
src/ptp.rs Normal file
View file

@ -0,0 +1,123 @@
use chrono::{DateTime, TimeZone, Utc};
#[derive(Debug)]
pub enum PtpError {
Io(std::io::Error),
ChronoOutOfRange,
Unsupported,
}
impl std::fmt::Display for PtpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PtpError::Io(e) => write!(f, "I/O error: {}", e),
PtpError::ChronoOutOfRange => write!(f, "Chrono out-of-range error"),
PtpError::Unsupported => write!(f, "PTP is unsupported on this platform"),
}
}
}
impl std::error::Error for PtpError {}
impl From<std::io::Error> for PtpError {
fn from(e: std::io::Error) -> Self {
PtpError::Io(e)
}
}
#[cfg(target_os = "linux")]
mod linux {
use super::{DateTime, PtpError, TimeZone, Utc};
use libc::{clockid_t, timespec, CLOCK_REALTIME};
use std::fs::File;
use std::os::fd::AsRawFd;
pub struct PtpClock {
file: File,
}
impl PtpClock {
pub fn open(path: &str) -> Result<Self, PtpError> {
Ok(Self { file: File::open(path)? })
}
#[inline]
fn fd_to_clockid(fd: i32) -> clockid_t {
// Linux CLOCKFD encoding:
// #define CLOCKFD 3
// #define FD_TO_CLOCKID(fd) ((~(clockid_t)(fd) << 3) | CLOCKFD)
((!(fd as clockid_t)) << 3) | 3
}
fn read_timespec(&self) -> Result<timespec, PtpError> {
let mut ts = timespec { tv_sec: 0, tv_nsec: 0 };
let clk_id = Self::fd_to_clockid(self.file.as_raw_fd());
let rc = unsafe { libc::clock_gettime(clk_id, &mut ts as *mut timespec) };
if rc != 0 {
return Err(PtpError::Io(std::io::Error::last_os_error()));
}
Ok(ts)
}
pub fn now_datetime(&self) -> Result<DateTime<Utc>, PtpError> {
let ts = self.read_timespec()?;
let dt = Utc
.timestamp_opt(ts.tv_sec as i64, ts.tv_nsec as u32)
.single()
.ok_or(PtpError::ChronoOutOfRange)?;
Ok(dt)
}
pub fn offset_from_system_ns(&self) -> Result<i128, PtpError> {
// PTP Hardware Clock (PHC)
let phc = self.read_timespec()?;
// System realtime clock
let mut sys_ts = timespec { tv_sec: 0, tv_nsec: 0 };
let rc = unsafe { libc::clock_gettime(CLOCK_REALTIME, &mut sys_ts as *mut timespec) };
if rc != 0 {
return Err(PtpError::Io(std::io::Error::last_os_error()));
}
let phc_ns = (phc.tv_sec as i128) * 1_000_000_000 + (phc.tv_nsec as i128);
let sys_ns = (sys_ts.tv_sec as i128) * 1_000_000_000 + (sys_ts.tv_nsec as i128);
Ok(phc_ns - sys_ns)
}
}
pub fn is_supported() -> bool {
true
}
pub use PtpClock;
}
#[cfg(not(target_os = "linux"))]
mod non_linux {
use super::{DateTime, PtpError, Utc};
pub struct PtpClock;
impl PtpClock {
pub fn open(_path: &str) -> Result<Self, PtpError> {
Err(PtpError::Unsupported)
}
pub fn now_datetime(&self) -> Result<DateTime<Utc>, PtpError> {
Err(PtpError::Unsupported)
}
pub fn offset_from_system_ns(&self) -> Result<i128, PtpError> {
Err(PtpError::Unsupported)
}
}
pub fn is_supported() -> bool {
false
}
pub use PtpClock;
}
#[cfg(target_os = "linux")]
pub use linux::{is_supported, PtpClock};
#[cfg(not(target_os = "linux"))]
pub use non_linux::{is_supported, PtpClock};