Add clock capabilities and example binary#137
Add clock capabilities and example binary#137davidv1992 merged 1 commit intopendulum-project:mainfrom
Conversation
|
@davidv1992 See this as a draft - just an idea on how an API could look like. Users such as |
|
Follow-up on #134 |
d832824 to
dbd52c9
Compare
|
This PR moves clock_steering significantly closer to being a general use library. We are currently not setup to maintain it as such, so we'll need to have an internal discussion whether we want it to be that. This might take us a few weeks. |
No worries, I'm not in a hurry. And I am also open to other suggestions. Bottom line however is that |
davidv1992
left a comment
There was a problem hiding this comment.
We've had the internal discussion, and decided to move forward with this.
Having a look, mostly this looks like a reasonable direction, however there are a number of smaller issues that need some attention, most importantly the fact that there currently seems to be going on a bit of unit confusion. If you can fix the things identified below we should most likely be good to go.
|
|
||
| [dependencies] | ||
| libc = "0.2.165" | ||
| nix = { version = "0.27", features = ["ioctl"] } |
There was a problem hiding this comment.
We conciously make the choice to not use nix, so as to avoid it and its dependencies for ntpd-rs. Could you add manual implementations for the ioctls where needed, or implement the needed functionality in libc?
There was a problem hiding this comment.
Sure, can do, but could you elaborate on that choice? Just curious.
There was a problem hiding this comment.
We try to keep the number of dependencies as low as reasonable. We decided a while ago that nix, while somewhat useful, did not provide enough utility for us to to justify its place in our supply chain.
|
|
||
| Ok(ClockCapabilities { | ||
| max_frequency_adjustment_ppb: caps.max_adj as u64, | ||
| max_offset_adjustment_ns: caps.max_phase_adj as u32, |
There was a problem hiding this comment.
This should probably be a clamped value instead of an outright cast.
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| pub struct ClockCapabilities { | ||
| /// Maximum frequency adjustment capability in parts per million. | ||
| pub max_frequency_adjustment_ppb: u64, |
There was a problem hiding this comment.
Given the units used in the rest of the interface for clocks (f64 respresenting desired frequency in ppm) it would be better to use that same unit here.
There was a problem hiding this comment.
I will in fact make both variables here f64. That makes most sense I believe.
| impl Default for ClockCapabilities { | ||
| fn default() -> Self { | ||
| Self { | ||
| max_frequency_adjustment_ppb: 32_768_000_000, // 32768000 ppm |
There was a problem hiding this comment.
This value is incorrect, should match 500 ppm. Note that the kernel uses two units for frequency offsets, ppb and scaled ppm, where the latter is ppm multiplied by 2^16 (a.k.a. a fixed point format)
There was a problem hiding this comment.
Right. Thanks for spotting!
| #[cfg(target_os = "linux")] | ||
| capabilities: Option<ClockCapabilities>, |
There was a problem hiding this comment.
Is there a reason to want explicit caching for the clock capabilities, instead of just looking them up when asked and trusting the user to do any caching if need be?
There was a problem hiding this comment.
I was considering the runtime costs caused by the syscall, and the values reported by the kernel are not going to change. But you're right, this isn't a frequent operation, and dropping the cache makes the code a bit cleaner.
| assert!(max_freq > 0); | ||
| assert!(max_freq >= 32768000); // At least 32768000 ppm |
There was a problem hiding this comment.
These checks are redundant. Also let's just check that the actual value is correct, instead of a mere reasonability check, since this is a known clock with a guaranteed max frequency of 500ppm. (which also isn't the value tested for here).
|
Forgot to asl this, but have you checked the reported limits on actual hardware? The header file suggests the max_freq_adj field to be in ppb, which is weird and inconsistent with the rest of the linux time APIs so having some confirmation this is actually correct would be nice. (not that it would be surprising that there is inconsistency here, the time APIs are notorious for being quite awkward, especially for ptp hardware clocks.) |
|
@zonque Would you be able to take a look at the review comments? |
Yes, will do soon. Sorry, I got busy with too many other things meanwhile 🙈 |
Yes I did. We are using a PTP driver for our own hardware, and that reports a maximum value for frequency adjustment of 100_000 ppb.
😏 |
440fba9 to
cf1e65d
Compare
Addressed the comments now and pushed again. |
cf1e65d to
214e962
Compare
davidv1992
left a comment
There was a problem hiding this comment.
Just a few minor things remaining, thanks for the rest of the fixes :). Feel free to ignore the note to self.
| #[derive(Debug, Clone, Copy, PartialEq)] | ||
| pub struct ClockCapabilities { | ||
| /// Maximum frequency adjustment capability in parts per billion. | ||
| pub max_frequency_adjustment_ppb: f64, |
There was a problem hiding this comment.
You missed the conversion to ppm in the interface here.
| pub max_frequency_adjustment_ppb: f64, | ||
|
|
||
| /// Maximum offset adjustment capability in nanoseconds. | ||
| pub max_offset_adjustment_ns: f64, |
There was a problem hiding this comment.
I don't agree with the choice to make this an f64. Again, lets keep the interface consistent and use a u32 here, matching TimeOffset below
| #[repr(C)] | ||
| pub struct PtpClockCaps { | ||
| pub max_adj: libc::c_int, // Maximum frequency adjustment in parts per billion | ||
| pub n_alarm: libc::c_int, // Number of programmable alarms | ||
| pub n_ext_ts: libc::c_int, // Number of external time stamp channels | ||
| pub n_per_out: libc::c_int, // Number of programmable periodic signals | ||
| pub pps: libc::c_int, // Whether the clock supports a PPS callback | ||
| pub n_pins: libc::c_int, // Number of input/output pins | ||
| pub cross_timestamping: libc::c_int, // Whether the clock supports precise system-device cross timestamps | ||
| pub adjust_phase: libc::c_int, // Whether the clock supports adjust phase | ||
| pub max_phase_adj: libc::c_int, // Maximum offset adjustment in nanoseconds | ||
| pub rsv: [libc::c_int; 11], // Reserved for future use | ||
| } | ||
|
|
||
| // PTP_CLOCK_GETCAPS = _IOR('=', 1, struct ptp_clock_caps) | ||
| // | ||
| // Linux _IOR encoding: (IOC_READ << 30) | (size << 16) | (type << 8) | nr | ||
| // IOC_READ = 2, type = b'=' = 0x3D, nr = 1 | ||
| const PTP_CLOCK_GETCAPS: u32 = | ||
| (2u32 << 30) | ((std::mem::size_of::<PtpClockCaps>() as u32) << 16) | ((b'=' as u32) << 8) | 1; |
There was a problem hiding this comment.
Note to self: this needs upstreaming to libc.
|
@zonque: I think this is really close to the finish line, would you mind if I push it the final bit or do you prefer to do that yourself? |
I can look into it earliest tomorrow, but if you prefer that, I have no objections of you'd take care of the final bits. |
I just pushed again, please check. If you merge this, please squash the commits. |
davidv1992
left a comment
There was a problem hiding this comment.
Thank you for doing this work. We are looking at doing an overhaul of the algorithm soon, and that will provide us with a decent opportunity to start using this information.
Add a type to express a clock's capability of steering the clock and its phase. For regular POSIX clocks, the reported values default to what the Linux kernel is able to handle. On the first call of `Clock::capabilities()`, and when executed on Linux, the code will attempt to call the `PTP_CLOCK_GETCAPS` `ioctl()` once. In case the file descriptor belongs to a PTP clock, this will return a struct that tells us the capabilities of the underlying hardware. Otherwise, we fall back to the POSIX defaults.
c448e17 to
dfcf7d4
Compare
No description provided.