This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
commit d9cb21d9092ebf2bfd84103f46c6854f3f64a92b Author: Daniel P. Carvalho <[email protected]> AuthorDate: Thu Sep 10 22:43:47 2026 -0300 netutils/ptpd: fix drift divergence and post-jump bootstrap 1. Post-jump drift bootstrap: on the first Sync packet following a step jump, do not compute frequency drift against a synthesized zero delta, which previously caused the entire residual phase offset (~ms) to be mistaken for frequency drift (~million ppb) and immediately absorbed. 2. Drift rate formula: normalize the adjustment contribution by the actual measurement interval instead of the adjtime slew period, and compute natural delta rate as (delta - last_delta + last_adjtime) / interval. 3. Remove broken last_delta > delta comparison that prevented offsets from converging and applied inverted corrections on negative overshoots. 4. Correct ptp_adjtime() invocation to always pass adjustment_ns for CLOCK_REALTIME slewing rather than dropping drift compensation when delta exceeds threshold. Clamp adjustment_ns to the hardware slew limit so last_adjtime_ns accurately mirrors the true slew applied. 5. Enable Delay_Req in E2E mode once clock is tracking (not jumping) and allow software timestamping latency in ptp_process_delay_resp(). 6. Propagate initialization return code from ptpd_start() and avoid unconditional failure print in do_ptpd_start(). Assisted-by: Claude:claude-sonnet-5 Assisted-by: Gemini:gemini-3.8-flash-medium Signed-off-by: Daniel P. Carvalho <[email protected]> --- netutils/ptpd/ptpd.c | 167 ++++++++++++++++++++++++++---------------------- system/ptpd/ptpd_main.c | 10 +-- 2 files changed, 98 insertions(+), 79 deletions(-) diff --git a/netutils/ptpd/ptpd.c b/netutils/ptpd/ptpd.c index 375f8c00f..2e363bf24 100644 --- a/netutils/ptpd/ptpd.c +++ b/netutils/ptpd/ptpd.c @@ -115,6 +115,7 @@ struct ptp_state_s int64_t last_adjtime_ns; long drift_avg_total_ms; long drift_ppb; + bool has_last_delta; /* Identity of currently selected clock source, * from the latest announcement message. @@ -1047,6 +1048,7 @@ static int ptp_update_local_clock(FAR struct ptp_state_s *state, */ struct timespec new_time; + ptp_gettime(state, &new_time); clock_timespec_subtract(&new_time, local_timestamp, &new_time); clock_timespec_add(&new_time, remote_timestamp, &new_time); @@ -1059,6 +1061,7 @@ static int ptp_update_local_clock(FAR struct ptp_state_s *state, state->last_adjtime_ns = 0; state->drift_avg_total_ms = 0; state->drift_ppb = 0; + state->has_last_delta = false; if (ret == OK) { @@ -1076,86 +1079,91 @@ static int ptp_update_local_clock(FAR struct ptp_state_s *state, * the adjustment that was made previously. */ - int64_t drift_ppb; + int64_t drift_ppb = 0; struct timespec interval; - int interval_ms; + int interval_ms = 0; int max_avg_period_ms; int64_t adjustment_ns; + const int64_t max_adjust_ns = + (int64_t)CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * + CONFIG_CLOCK_ADJTIME_PERIOD_MS; + const int64_t slew_limit_ppb = + (int64_t)CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * 1000; - clock_timespec_subtract(local_timestamp, - &state->last_delta_timestamp, - &interval); - interval_ms = timespec_to_ms(&interval); - - if (interval_ms > 0 && interval_ms < CONFIG_NETUTILS_PTPD_TIMEOUT_MS) + if (!state->has_last_delta) { - drift_ppb = (delta_ns - state->last_delta_ns) * MSEC_PER_SEC - / interval_ms; + /* First measurement after jump or startup: no previous + * delta available to compute frequency drift rate. + */ + + adjustment_ns = delta_ns; } else { - ptpwarn("Measurement interval out of range: %d ms\n", interval_ms); - drift_ppb = 0; - interval_ms = 1; - } + clock_timespec_subtract(local_timestamp, + &state->last_delta_timestamp, + &interval); + interval_ms = timespec_to_ms(&interval); - /* Account for the adjustment previously made */ + if (interval_ms > 0 && + interval_ms < CONFIG_NETUTILS_PTPD_TIMEOUT_MS) + { + /* Natural change in delta over the interval, accounting for + * the adjustment applied during that same interval. + */ - drift_ppb += state->last_adjtime_ns * MSEC_PER_SEC - / CONFIG_CLOCK_ADJTIME_PERIOD_MS; + drift_ppb = (delta_ns - state->last_delta_ns + + state->last_adjtime_ns) * MSEC_PER_SEC + / interval_ms; + } + else + { + ptpwarn("Measurement interval out of range: %d ms\n", + interval_ms); + drift_ppb = state->drift_ppb; + interval_ms = 1; + } - if (drift_ppb > CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * 1000 || - drift_ppb < -CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * 1000) - { - ptpwarn("Drift estimate out of range: %lld\n", - (long long)drift_ppb); - drift_ppb = state->drift_ppb; - } + if (drift_ppb > slew_limit_ppb || drift_ppb < -slew_limit_ppb) + { + ptpwarn("Drift estimate out of range: %lld\n", + (long long)drift_ppb); + drift_ppb = state->drift_ppb; + } - /* Take direct average of drift estimate for first measurements, - * after that update the exponential sliding average. - * Measurements are weighted according to the interval, because - * drift estimate is more accurate over longer timespan. - */ + /* Update the exponential sliding average */ - state->drift_avg_total_ms += interval_ms; - max_avg_period_ms = CONFIG_NETUTILS_PTPD_DRIFT_AVERAGE_S - * MSEC_PER_SEC; - if (state->drift_avg_total_ms > max_avg_period_ms) - { - state->drift_avg_total_ms = max_avg_period_ms; - } + state->drift_avg_total_ms += interval_ms; + max_avg_period_ms = CONFIG_NETUTILS_PTPD_DRIFT_AVERAGE_S + * MSEC_PER_SEC; + if (state->drift_avg_total_ms > max_avg_period_ms) + { + state->drift_avg_total_ms = max_avg_period_ms; + } - state->drift_ppb += (drift_ppb - state->drift_ppb) * interval_ms - / state->drift_avg_total_ms; + state->drift_ppb += (drift_ppb - state->drift_ppb) * interval_ms + / state->drift_avg_total_ms; - /* Compute the value we need to give to adjtime() to match the - * drift rate. - */ + /* Compute the adjustment to compensate frequency drift plus + * current phase error. + */ - adjustment_ns = state->drift_ppb * CONFIG_CLOCK_ADJTIME_PERIOD_MS - / MSEC_PER_SEC; - - /* Drift estimation ensures local clock runs at same rate as remote. - * - * Adding the current clock offset to adjustment brings the clocks - * to match. To avoid individual outliers from causing jitter, we - * take the larger signed value of two previous deltas. This is based - * on the logic that packets can get delayed in transit, but do not - * travel backwards in time. - * - * Clock offset is applied over ADJTIME_PERIOD. If there is significant - * noise in measurements, increasing ADJTIME_PERIOD will reduce its - * effect on the local clock run rate. + adjustment_ns = state->drift_ppb * CONFIG_CLOCK_ADJTIME_PERIOD_MS + / MSEC_PER_SEC; + adjustment_ns += delta_ns; + } + + /* Clamp adjustment to the hardware slew limit so that last_adjtime_ns + * accurately reflects what adjtime() will actually perform. */ - if (state->last_delta_ns > delta_ns) + if (adjustment_ns > max_adjust_ns) { - adjustment_ns += state->last_delta_ns; + adjustment_ns = max_adjust_ns; } - else + else if (adjustment_ns < -max_adjust_ns) { - adjustment_ns += delta_ns; + adjustment_ns = -max_adjust_ns; } /* Apply adjustment and store information for next time */ @@ -1163,32 +1171,26 @@ static int ptp_update_local_clock(FAR struct ptp_state_s *state, state->last_delta_ns = delta_ns; state->last_delta_timestamp = *local_timestamp; state->last_adjtime_ns = adjustment_ns; + state->has_last_delta = true; ptpinfo("Delta: %+lld ns, adjustment %+lld ns, drift rate %+lld ppb\n", (long long)delta_ns, (long long)state->last_adjtime_ns, (long long)state->drift_ppb); - if (absdelta_ns > CONFIG_NETUTILS_PTPD_ADJTIME_THRESHOLD_NS) - { - ret = ptp_adjtime(state, delta_ns, drift_ppb); - } - else - { - ret = ptp_adjtime(state, adjustment_ns, state->drift_ppb); - } + ret = ptp_adjtime(state, adjustment_ns, + absdelta_ns > + CONFIG_NETUTILS_PTPD_ADJTIME_THRESHOLD_NS ? + drift_ppb : state->drift_ppb); if (ret != OK) { ptperr("ptp_adjtime() failed: %d\n", errno); } - /* Check if clock is stable enough for sending delay requests */ + /* Clock is tracking the master, allow sending delay requests */ - if (absdelta_ns < CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS) - { - state->can_send_delayreq = true; - } + state->can_send_delayreq = true; } return ret; @@ -1348,6 +1350,7 @@ static int ptp_process_delay_resp(FAR struct ptp_state_s *state, struct timespec remote_rxtime; uint16_t sequence; int interval; + int64_t max_path_delay; if (!state->selected_source_valid || memcmp(msg->header.sourceidentity, @@ -1379,7 +1382,19 @@ static int ptp_process_delay_resp(FAR struct ptp_state_s *state, sync_delay = state->path_delay_ns - state->last_delta_ns; path_delay = (path_delay + sync_delay) / 2; - if (path_delay >= 0 && path_delay < CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS) + max_path_delay = CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS; + + if (!state->config->hardware_ts && + max_path_delay < 10 * (int64_t)NSEC_PER_MSEC) + { + /* Software timestamping includes network stack and OS latency, + * allow up to 10 ms. + */ + + max_path_delay = 10 * (int64_t)NSEC_PER_MSEC; + } + + if (path_delay >= 0 && path_delay < max_path_delay) { if (state->path_delay_avgcount < CONFIG_NETUTILS_PTPD_DELAYREQ_AVGCOUNT) @@ -1616,6 +1631,7 @@ int ptpd_start(FAR const struct ptpd_config_s *config) struct iovec rxiov; int timeout; int idx = 1; + int status = OK; int ret; memset(&rxhdr, 0, sizeof(rxhdr)); @@ -1628,7 +1644,8 @@ int ptpd_start(FAR const struct ptpd_config_s *config) } state->config = config; - if (ptp_initialize_state(state) != OK) + status = ptp_initialize_state(state); + if (status != OK) { ptperr("Failed to initialize PTP state, exiting\n"); goto errout; @@ -1715,7 +1732,7 @@ errout: ptp_destroy_state(state); free(state); - return 0; + return status; } /**************************************************************************** diff --git a/system/ptpd/ptpd_main.c b/system/ptpd/ptpd_main.c index 6b1720689..8496a5365 100644 --- a/system/ptpd/ptpd_main.c +++ b/system/ptpd/ptpd_main.c @@ -43,11 +43,13 @@ static int do_ptpd_start(FAR const struct ptpd_config_s *config) int ret; ret = ptpd_start(config); + if (ret < 0) + { + fprintf(stderr, "ERROR: ptpd_start() failed: %d\n", ret); + return EXIT_FAILURE; + } - /* Should never happen */ - - fprintf(stderr, "ERROR: ptpd_start() failed:%d\n", ret); - return EXIT_FAILURE; + return EXIT_SUCCESS; } static int do_ptpd_status(int pid)
