ptp_time/lib.rs
1//! Safe Rust wrapper for Linux PTP (Precision Time Protocol) Hardware Clock driver.
2//!
3//! This crate provides a safe interface to the Linux PTP kernel driver,
4//! exposing the following ioctls:
5//! - `ptp_clock_caps` - Get clock capabilities
6//! - `ptp_sys_offset` - Get system offset measurements
7//! - `ptp_sys_offset_precise` - Get precise system offset measurements
8//! - `ptp_sys_offset_extended` - Get extended system offset measurements
9
10use std::{
11 fs::File,
12 io::{Error, Result},
13 mem::MaybeUninit,
14 os::{
15 fd::AsRawFd,
16 raw::c_ulong,
17 },
18 path::PathBuf,
19};
20
21pub mod ptp;
22use ptp::*;
23
24// PTP ioctl constants - These are standard Linux PTP driver ioctls
25// Based on linux/ptp_clock.h: PTP_CLK_MAGIC = '=' = 0x3D
26
27// Correct ioctl values partially calculated from the header file and partially
28// worked out by stracing chrony. If anyone can explain where the top 16 bits
29// come from I'd be grateful.
30
31// _IOR(PTP_CLK_MAGIC, 1, struct ptp_clock_caps) -> 0x80503d01
32// _IOW(PTP_CLK_MAGIC, 5, struct ptp_sys_offset) -> 0x43403d05
33// _IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise) -> 0xc0403d08
34// _IOWR(PTP_CLK_MAGIC, 9, struct ptp_sys_offset_extended) -> 0xc4c03d09
35
36const PTP_CLOCK_GETCAPS: c_ulong = 0x80503d01; // _IOR(PTP_CLK_MAGIC, 1, struct ptp_clock_caps)
37const PTP_SYS_OFFSET: c_ulong = 0x43403d05; // _IOW(PTP_CLK_MAGIC, 5, struct ptp_sys_offset)
38const PTP_SYS_OFFSET_PRECISE: c_ulong = 0xc0403d08; // _IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise)
39const PTP_SYS_OFFSET_EXTENDED: c_ulong = 0xc4c03d09; // _IOWR(PTP_CLK_MAGIC, 9, struct ptp_sys_offset_extended)
40
41/// A safe wrapper for PTP hardware clock devices
42pub struct PtpDevice(File);
43
44impl PtpDevice {
45 /// Create a new PTP device from a path
46 pub fn new(path: PathBuf) -> Result<PtpDevice> {
47 Ok(PtpDevice(File::open(path)?))
48 }
49
50 /// Perform ioctl request and check result for possible errors
51 unsafe fn ioctl<T>(&self, request: c_ulong, value: &mut T) -> Result<()> {
52 match libc::ioctl(self.0.as_raw_fd(), request as _, value) {
53 0 => Ok(()),
54 _ => Err(Error::last_os_error()),
55 }
56 }
57
58 /// Perform ioctl request with uninitialized memory
59 unsafe fn ioctl_uninit<T>(&self, request: c_ulong) -> Result<T> {
60 let mut value: MaybeUninit<T> = MaybeUninit::uninit();
61 self.ioctl(request, &mut value)?;
62 Ok(unsafe { value.assume_init() })
63 }
64
65 /// Get the clock capabilities
66 pub fn get_caps(&self) -> Result<ptp_clock_caps> {
67 // Safety: PTP_CLOCK_GETCAPS writes ptp_clock_caps, for which memory is allocated and returned by ioctl_uninit
68 unsafe { self.ioctl_uninit(PTP_CLOCK_GETCAPS) }
69 }
70
71 /// Get system offset measurements
72 pub fn get_sys_offset(&self) -> Result<ptp_sys_offset> {
73 let mut offset = ptp_sys_offset::default();
74 // Safety: PTP_SYS_OFFSET expects and writes to a ptp_sys_offset, which lives for the duration of the call
75 unsafe { self.ioctl(PTP_SYS_OFFSET, &mut offset)? };
76 Ok(offset)
77 }
78
79 /// Get precise system offset measurements
80 pub fn get_sys_offset_precise(&self) -> Result<ptp_sys_offset_precise> {
81 let mut offset = ptp_sys_offset_precise::default();
82 // Safety: PTP_SYS_OFFSET_PRECISE expects and writes to a ptp_sys_offset_precise, which lives for the duration of the call
83 unsafe { self.ioctl(PTP_SYS_OFFSET_PRECISE, &mut offset)? };
84 Ok(offset)
85 }
86
87 /// Get extended system offset measurements
88 pub fn get_sys_offset_extended(&self) -> Result<ptp_sys_offset_extended> {
89 let mut offset = ptp_sys_offset_extended::default();
90 // Safety: PTP_SYS_OFFSET_EXTENDED expects and writes to a ptp_sys_offset_extended, which lives for the duration of the call
91 unsafe { self.ioctl(PTP_SYS_OFFSET_EXTENDED, &mut offset)? };
92 Ok(offset)
93 }
94}