mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-10-01 19:36:36 +00:00
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 <danieloak@gmail.com>
This commit is contained in:
parent
071074247b
commit
d9cb21d909
2 changed files with 102 additions and 83 deletions
|
|
@ -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,119 +1079,118 @@ 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.
|
||||
*/
|
||||
|
||||
if (state->last_delta_ns > delta_ns)
|
||||
{
|
||||
adjustment_ns += state->last_delta_ns;
|
||||
}
|
||||
else
|
||||
{
|
||||
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 (adjustment_ns > max_adjust_ns)
|
||||
{
|
||||
adjustment_ns = max_adjust_ns;
|
||||
}
|
||||
else if (adjustment_ns < -max_adjust_ns)
|
||||
{
|
||||
adjustment_ns = -max_adjust_ns;
|
||||
}
|
||||
|
||||
/* Apply adjustment and store information for next time */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue