netutils/ptpd: IEEE 1588 P2P delay mechanism, hardware clock phase-lock and outlier rejection - #3782
daniel-p-carvalho wants to merge 7 commits into
Conversation
cc84f8f to
4871f0b
Compare
|
please fix the conflict. @daniel-p-carvalho |
ee5f983
4871f0b to
ee5f983
Compare
310ea5d to
3b8c815
Compare
|
Rebased onto latest
All 6 commits have been verified with |
|
@daniel-p-carvalho please rebase and fix the conflict. |
3b8c815 to
35073e5
Compare
|
Hi @wenquan1, Following your request to test PR #3789 on physical hardware, we identified a regression when running on flat builds ( To address this cleanly without breaking your improvements for Protected/Kernel modes, we updated this PR with a hybrid approach in commit
We have validated this on real hardware (running NuttX flat build). Could you please take a look and share your review? Thanks! |
ptp_sendmsg() called a blocking recvmsg(state->tx_socket, ...) right after sending a Delay_Req whenever hardware_ts was set, assuming a Linux-style MSG_ERRQUEUE/loopback semantics NuttX does not have. Since tx_socket and event_socket share the same underlying connection, this call instead blocked on and consumed whatever PTP packet arrived next on the wire — almost always the Delay_Resp, which typically arrives within milliseconds of the request. Its payload was read into a local buffer that went out of scope on return, so the packet never reached ptp_process_rx_packet() and path_delay_ns stayed at 0 in -H mode. t3 is now captured locally via ptp_gettime(), the same way -S mode already did, until hardware TX timestamping is supported. Also replaces the path delay heuristic in ptp_process_delay_resp() (which derived an approximation of (t2-t1) from path_delay_ns and last_delta_ns, only valid once the clock had already converged) with the canonical IEEE 1588-2008 §11.3 formula: store (t2-t1) directly from Sync/Follow_Up as sync_diff_ns, then average it with (t4-t3) from the Delay_Req/Delay_Resp exchange. Relaxes the path delay ceiling to 10ms unconditionally, since Delay_Req's t3 is software- timestamped in both modes until hardware TX timestamping is supported. Assisted-by: Claude:claude-sonnet-5 Assisted-by: Gemini:gemini-3.8-flash-medium Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
Implements the Peer-to-Peer (P2P) transparent clock delay measurement mechanism (IEEE 1588-2008 §11.4 / IEEE 802.1AS / IEC/IEEE 61850-9-3) in apps/netutils/ptpd: - Add PTP_MSGTYPE_PDELAY_REQ, PTP_MSGTYPE_PDELAY_RESP, and PTP_MSGTYPE_PDELAY_RESP_FOLLOW_UP definitions and structs in ptpv2.h. - Define IEEE 1588-2008 Annex F peer delay multicast MAC address 01:80:c2:00:00:0e and Annex D peer delay IP address 224.0.0.107. - Replace bool delay_e2e with enum ptp_delay_mechanism_e (PTP_DELAY_NONE, PTP_DELAY_E2E, PTP_DELAY_P2P) in include/netutils/ptpd.h. - Add -P CLI option in system/ptpd/ptpd_main.c with mutual exclusion check against -E, and display last_transmitted_pdelayreq in status. - Implement responder logic in ptp_process_pdelay_req() sending Pdelay_Resp (t2) and Pdelay_Resp_Follow_Up (t3) regardless of master or slave state. - Implement requester logic in ptp_send_pdelay_req() gated on the physical link without requiring prior BMCA master selection. - Implement ptp_process_pdelay_resp() and ptp_process_pdelay_resp_followup() using canonical mean path delay formula ((t4 - t1) - (t3 - t2)) / 2. - Refactor path delay bounds checking and moving average filter into ptp_record_path_delay() shared across E2E and P2P mechanisms. - Set PTP version 2.0 and controlField 0x05 in Pdelay_Req, Pdelay_Resp and Pdelay_Resp_Follow_Up, and in the own-identity header, so that peers such as linuxptp accept the messages. - Clear pdelay_waiting_followup when a new Pdelay_Req is sent, so an orphaned Pdelay_Resp_Follow_Up from an abandoned cycle is not paired with stale timestamps. - Warn at startup when P2P is selected without CONFIG_SCHED_TICKLESS, since a tick-driven clock cannot resolve the peer delay. - Skip IP multicast join/leave handling for AF_PACKET. Assisted-by: Claude:claude-sonnet-5 Assisted-by: Gemini:gemini-3.8-flash-medium Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
A single drift-rate sample computed between two consecutive sync updates was clamped against CLOCK_ADJTIME_SLEWLIMIT_PPM - the hardware's slew-rate safety limit, not a bound on how large a real crystal-oscillator drift measurement can plausibly be. An abnormally short or long measurement interval (e.g. right after a clock source outage/reconnect, or a burst of closely spaced sync packets following packet loss) could therefore produce a wildly implausible sample that still passed the check and corrupted the long-term drift_ppb average. Add CONFIG_NETUTILS_PTPD_MAX_DRIFT_PPB (default 500000, well above any real crystal's few-hundred-ppm drift) as a dedicated plausibility bound, intentionally much tighter than CLOCK_ADJTIME_SLEWLIMIT_PPM. A sample outside this bound is discarded and the previous averaged drift_ppb is kept unchanged instead of being corrupted. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
When state->clockid is configured to a hardware PTP clock device (e.g., /dev/ptp0) instead of CLOCK_REALTIME, ptp_adjtime() previously passed only the measured frequency drift (-ppb) to clock_adjtime(), ignoring the residual phase offset (delta_ns / adjustment_ns). As a result, while the hardware counter tracked frequency, its phase was never pulled into alignment with the master clock. Convert delta_ns (which combines frequency drift and current phase error clamped to max_adjust_ns) to ppb over CONFIG_CLOCK_ADJTIME_PERIOD_MS, acting as a proportional-integral (PI) phase servo. This drives the hardware clock to phase lock with the master via POSIX clock_adjtime() using ADJ_FREQUENCY without requiring proprietary ioctl calls. Assisted-by: Claude:claude-sonnet-5 Assisted-by: Gemini:gemini-3.8-flash-medium Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
The MAC latches a hardware receive timestamp later than the frame reaches the wire, because of the PHY and the clock domain crossing. This fixed delay is the ingressLatency port parameter of IEEE 1588 and shows up as a constant phase error between the local and the master clock. Subtract the configured latency from every hardware receive timestamp in ptp_getrxtime(), the single place where they enter the daemon, so Sync, Delay_Resp and the peer delay messages are all corrected. - Add CONFIG_NETUTILS_PTPD_INGRESS_LATENCY_NS (default 0, which applies no compensation). - Add the -I option to override it at run time. - Add ingress_latency_ns to struct ptpd_config_s. Software timestamps are not affected. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
…tus() PR apache#3789 replaced the in-memory sigqueue + shared memory IPC in ptpd_status() with file-based IPC to support Protected and Kernel modes across address spaces. However, on microcontrollers running CONFIG_BUILD_FLAT, a filesystem or /tmp (TMPFS) is rarely mounted or available, causing ptpd_status() to fail with -ETIMEDOUT (errno 110) because the status file cannot be created. Retain the file-based IPC for !CONFIG_BUILD_FLAT (Protected and Kernel modes) while restoring the zero-overhead in-memory sigqueue + semaphore IPC for CONFIG_BUILD_FLAT. Both modes share the status serialization logic via ptp_populate_status() and support all fields including P2P. Assisted-by: Gemini:gemini-3.8-flash-medium Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
A single Sync sample whose receive timestamp was taken late, for example because the task was scheduled late with software timestamping, was fed straight into the phase correction and the drift estimate, and could pull the clock away from the master. - Add CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS (default 0, which disables the check). A phase error that differs by more than this many nanoseconds from the median of the last five accepted samples is discarded, with a warning. - Accept the sample after eight consecutive rejections and restart the history from it, so that a real step of the master is still followed while a short burst of disturbed samples is ridden out. - Restart the history whenever the clock is stepped, since the old samples no longer describe the new time base. - With the default of 0 the behaviour is unchanged. Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com> Assisted-by: Claude:claude-sonnet-5
35073e5 to
09abb1f
Compare
Summary
apps/netutils/ptpdonly implemented the end-to-end (E2E) delay mechanism. The IEC/IEEE 61850-9-3 profile uses the peer-to-peer (P2P) delay mechanism instead, so a Grandmaster running that profile (linuxptp'sptp4l) could not be synchronized against without switching it to E2E. This PR adds P2P, lets the daemon discipline a hardware PTP clock (/dev/ptp0) in phase as well as in frequency, and adds an optional filter that keeps a single disturbed Sync sample from moving the clock.The series was rewritten after review: the custom socket ioctls (
SIOCG_TX_HW_TIMESTAMP,SIOCS_PTP_ADJFREQ,SIOCS_PTP_ADJPHASE) that an earlier revision consumed are gone, replaced by the POSIXclock_adjtime()interface on a PTP hardware clock device, as suggested in the review of the kernel side. It is rebased on currentmaster(which already contains #3779).Commits
Fix Delay_Resp consumption by sendmsg.ptp_sendmsg()did a blockingrecvmsg()on the transmit socket right after sending aDelay_Reqwhen hardware timestamping was selected. That socket shares the connection with the event socket, so the call swallowed the next PTP packet on the wire, normally theDelay_Resp, into a buffer that was then discarded, andpath_delay_nsstayed at 0. The path delay is also computed with the IEEE 1588-2008 clause 11.3 formula instead of an approximation that only held once the clock had converged. This bug is still present inmaster.Implement IEEE 1588 peer-to-peer (P2P) delay mechanism.Adds thePdelay_Req,Pdelay_RespandPdelay_Resp_Follow_Upmessages, the-Poption (exclusive with-E), requester and responder logic, and replacesbool delay_e2ewithenum ptp_delay_mechanism_einstruct ptpd_config_s. The messages carry PTP version 2.0 andcontrolField0x05, whichlinuxptprequires to accept them. An orphanedPdelay_Resp_Follow_Upfrom an abandoned cycle is rejected instead of being paired with stale timestamps, P2P withoutCONFIG_SCHED_TICKLESSprints a warning at startup, and IP multicast handling is skipped forAF_PACKET.Discard implausible drift-rate samples before averaging.A single drift sample taken over an unusually short or long interval could pass the slew-rate check and corrupt the long-term average. AddsCONFIG_NETUTILS_PTPD_MAX_DRIFT_PPB(default 500000) as a dedicated plausibility bound.Phase-lock hardware clock via POSIX clock_adjtime.When the clock is a PTP hardware clock, the residual phase offset is now converted to a frequency correction overCONFIG_CLOCK_ADJTIME_PERIOD_MSand applied withADJ_FREQUENCY, so the counter, and any PPS output driven by it, is pulled into phase instead of only tracking the frequency.Add ingress latency compensation for RX timestamps.AddsCONFIG_NETUTILS_PTPD_INGRESS_LATENCY_NS(default 0) and the-Ioption. The value is subtracted from every hardware receive timestamp, which is theingressLatencyport parameter of IEEE 1588.Retain in-memory IPC for CONFIG_BUILD_FLAT in ptpd_status().The file based IPC introduced for the protected and kernel builds madeptpd -ttime out on flat builds without a mounted/tmp. The in-memory queue is kept forCONFIG_BUILD_FLATand the file is used otherwise.Discard outlier Sync phase error samples.A Sync sample whose receive timestamp was taken late, for example because the task was scheduled late with software timestamping, went straight into the phase correction and the drift estimate. AddsCONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS(default 0, which disables the check). A phase error that differs by more than this many nanoseconds from the median of the last five accepted samples is discarded with a warning. After eight consecutive rejections the sample is accepted and the history restarts from it, so a real step of the master is still followed while a short burst of disturbed samples is ridden out. The history is also restarted whenever the clock is stepped.Impact
-Pand-I,CONFIG_NETUTILS_PTPD_MAX_DRIFT_PPB,CONFIG_NETUTILS_PTPD_INGRESS_LATENCY_NS,CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS. The defaults leave the previous behaviour unchanged (E2E remains available with-E, ingress compensation is 0, outlier rejection is off).struct ptpd_config_schanges:delay_e2eis replaced bydelay_mechanism, andingress_latency_nsis added. The only user in the tree issystem/ptpd, which is updated here./dev/ptp0from arch/arm/stm32: implement PTP hardware clock driver (/dev/ptp0) nuttx#20180. Users that keep the defaultCLOCK_REALTIMEare not affected.Delay_ReqandPdelay_Reqis timestamped in software.Testing
Built with
stm32f4discovery:netnshplusNETUTILS_PTPD, in two configurations (with and withoutNET_PKTandNET_TIMESTAMP), without errors or warnings fromptpd. The last commit was also built withCONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NSat 0 and at 100000../tools/checkpatch.sh -g <base>..HEADpasses.On hardware: a custom STM32F407 board with the STM32 Ethernet MAC and a DP83848 PHY, over
AF_PACKET(ptpd -2 -s -p /dev/ptp0 -B -H -P -i eth0), against a GNSS-referencedptp4lGrandmaster using the IEC/IEEE 61850-9-3 profile. These runs were done with this series together with the hardware transmit timestamp series mentioned above:path_delayof about 1.2 µs.path_delay9265.3 ns (σ = 2.5 ns),driftabout -79.95 ppm, phase offset about -2.2 µs.path_delaybetween 9.26 and 9.33 µs and no failed queries.This exact series, without the transmit timestamp series, was also run on the same hardware (5 minutes). Its transmit timestamps are taken in software, after
sendmsg()returns, and the measured peer delay came out negative (about -18 µs), which the path delay filter rejects, sopath_delay_nsstayed at 0 and the phase offset was worse (about -6.7 µs against -2.4 µs with hardware transmit timestamps). In other words P2P on this hardware needs accurate transmit timestamps. The ingress compensation is implemented but its value has not been calibrated, so the default 0 applies no correction.Outlier rejection (commit 7):
-S) the deviations are hundreds of microseconds and come in bursts; a limit of three consecutive rejections let one of those bursts through, which is why the limit is eight. The accuracy of-Sis still bounded by the receive timestamp being taken when the task wakes up, and this filter does not change that.