netutils/ptpd: Add ingress latency compensation for RX timestamps.

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>
This commit is contained in:
Daniel P. Carvalho 2026-09-19 14:40:40 -03:00 • committed by Xiang Xiao
parent 6d10b44bc1
commit 585db4b657
4 changed files with 48 additions and 1 deletions

View file

@ -53,6 +53,7 @@ struct ptpd_config_s
enum ptp_delay_mechanism_e delay_mechanism;
bool bmca;
sa_family_t af;
int32_t ingress_latency_ns; /* Hardware RX timestamp latency (ns) */
};
/* PTPD status information structure */

View file

@ -219,4 +219,24 @@ config NETUTILS_PTPD_STATUSFILE
memory, making it work across all build modes (Flat,
Protected, Kernel). Written atomically via temp + rename.
config NETUTILS_PTPD_INGRESS_LATENCY_NS
int "PTP hardware receive timestamp latency (ns)"
default 0
range -1000000 1000000
---help---
Fixed delay, in nanoseconds, between a frame reaching the wire
reference plane and the moment the MAC latches its hardware
receive timestamp. This is the ingressLatency port parameter of
IEEE 1588: the PHY and the MAC clock-domain crossing make the
timestamp point lag the true arrival of the frame.
The latency is subtracted from every hardware receive timestamp,
so a positive value moves the timestamps earlier. It has no
effect with software timestamping. It can be overridden at
run time with the -I option.
The value depends on the PHY and board and has to be measured,
for example by comparing a physical PPS output against a
reference. The default of 0 applies no compensation.
endif # NETUTILS_PTPD

View file

@ -314,6 +314,22 @@ static int64_t timespec_to_ms(FAR const struct timespec *ts)
return ts->tv_sec * MSEC_PER_SEC + (ts->tv_nsec / NSEC_PER_MSEC);
}
/* Add a positive or negative number of nanoseconds to a timespec value. */
static void timespec_add_ns(FAR struct timespec *ts, int64_t ns)
{
int64_t total = ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec + ns;
ts->tv_sec = total / NSEC_PER_SEC;
ts->tv_nsec = total % NSEC_PER_SEC;
if (ts->tv_nsec < 0)
{
ts->tv_sec--;
ts->tv_nsec += NSEC_PER_SEC;
}
}
/* Get positive or negative delta between two timespec values.
* If value would exceed int64 limit (292 years), return INT64_MAX/MIN.
*/
@ -500,6 +516,11 @@ static int ptp_getrxtime(FAR struct ptp_state_s *state,
if (ts->tv_sec > 0 || ts->tv_nsec > 0)
{
/* The MAC latches the timestamp later than the frame
* reaches the wire: compensate the ingress latency.
*/
timespec_add_ns(ts, -state->config->ingress_latency_ns);
return OK;
}
}

View file

@ -164,6 +164,7 @@ static void usage(FAR const char *progname)
" -P P2P, support peer delay request-response\n"
" -i [dev] interface device to use, for example 'eth0'\n"
" -p [dev] clock device to use\n"
" -I [ns] hardware RX timestamp latency to compensate\n"
" -t [pid] look the status of ptp daemon\n"
" -d [pid] stop ptp daemon\n",
progname);
@ -195,8 +196,9 @@ int main(int argc, FAR char *argv[])
#endif
config.bmca = false;
config.af = AF_INET;
config.ingress_latency_ns = CONFIG_NETUTILS_PTPD_INGRESS_LATENCY_NS;
while ((option = getopt(argc, argv, "p:i:t:d:rs246BEHSP")) != ERROR)
while ((option = getopt(argc, argv, "p:i:t:d:I:rs246BEHSP")) != ERROR)
{
switch (option)
{
@ -251,6 +253,9 @@ int main(int argc, FAR char *argv[])
case 'p':
config.clock = optarg;
break;
case 'I':
config.ingress_latency_ns = atoi(optarg);
break;
case 'r':
config.clock = "realtime";
break;