diff --git a/netutils/ptpd/Kconfig b/netutils/ptpd/Kconfig index fc26e821b..371497609 100644 --- a/netutils/ptpd/Kconfig +++ b/netutils/ptpd/Kconfig @@ -209,6 +209,26 @@ config NETUTILS_PTPD_DELAYREQ_AVGCOUNT ---help--- Measured path delay is averaged over this many samples. +config NETUTILS_PTPD_OUTLIER_THRESHOLD_NS + int "PTP outlier rejection threshold (ns)" + default 0 + range 0 1000000000 + ---help--- + A phase error measurement that differs from the median of the + latest accepted ones by more than this many nanoseconds is + discarded instead of being used to correct the clock. It protects + the frequency estimate and the phase correction from a single + disturbed sample, for example a receive timestamp taken late by + the scheduler with software timestamping. + + A change that lasts is accepted after several discarded samples in + a row, so the daemon still follows a real step of the master, and + a short burst of disturbed samples is still ridden out. + + Choose a value well above the normal spread of the measurement: + a few microseconds are typical with hardware timestamping and + hundreds with software timestamping. 0 disables the rejection. + config NETUTILS_PTPD_STATUSFILE string "PTP daemon status file path" default "/tmp/ptpd.status" diff --git a/netutils/ptpd/ptpd.c b/netutils/ptpd/ptpd.c index 8b8adf9a2..969cbc352 100644 --- a/netutils/ptpd/ptpd.c +++ b/netutils/ptpd/ptpd.c @@ -65,6 +65,22 @@ #include "netutils/netlib.h" #include "ptpv2.h" +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#if CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS > 0 +/* Outlier rejection of the measured phase error: number of recent samples + * the median is taken over, the least number of samples needed before + * anything is rejected, and how many samples in a row can be rejected + * before they are taken as a real change of the phase. + */ + +# define PTP_OUTLIER_HISTORY 5 +# define PTP_OUTLIER_MIN_HISTORY 3 +# define PTP_OUTLIER_MAX_CONSECUTIVE 8 +#endif + /**************************************************************************** * Private Types ****************************************************************************/ @@ -128,6 +144,12 @@ struct ptp_state_s long drift_avg_total_ms; long drift_ppb; bool has_last_delta; +#if CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS > 0 + int64_t delta_hist[PTP_OUTLIER_HISTORY]; + unsigned int delta_hist_count; + unsigned int delta_hist_next; + unsigned int outlier_count; +#endif /* Identity of currently selected clock source, * from the latest announcement message. @@ -1241,6 +1263,69 @@ static int ptp_process_announce(FAR struct ptp_state_s *state, return OK; } +#if CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS > 0 +/* Tell whether a phase error measurement is an outlier, i.e. it differs from + * the median of the latest accepted ones by more than the threshold. A + * measurement that is disturbed on its own (a late receive timestamp, for + * example) would otherwise move the frequency and phase corrections. + * + * A change that lasts is not an outlier: after a few rejections in a row + * the measurement is accepted and the history starts over. + */ + +static bool ptp_is_outlier(FAR struct ptp_state_s *state, int64_t delta_ns) +{ + int64_t sorted[PTP_OUTLIER_HISTORY]; + int64_t deviation; + unsigned int count = state->delta_hist_count; + unsigned int i; + unsigned int j; + + if (count >= PTP_OUTLIER_MIN_HISTORY) + { + for (i = 0; i < count; i++) + { + int64_t value = state->delta_hist[i]; + + for (j = i; j > 0 && sorted[j - 1] > value; j--) + { + sorted[j] = sorted[j - 1]; + } + + sorted[j] = value; + } + + deviation = delta_ns - sorted[count / 2]; + if (deviation < 0) + { + deviation = -deviation; + } + + if (deviation > CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS) + { + if (++state->outlier_count < PTP_OUTLIER_MAX_CONSECUTIVE) + { + return true; + } + + state->delta_hist_count = 0; + state->delta_hist_next = 0; + } + } + + state->outlier_count = 0; + state->delta_hist[state->delta_hist_next] = delta_ns; + state->delta_hist_next = (state->delta_hist_next + 1) % + PTP_OUTLIER_HISTORY; + if (state->delta_hist_count < PTP_OUTLIER_HISTORY) + { + state->delta_hist_count++; + } + + return false; +} +#endif + /* Update local clock either by smooth adjustment or by jumping. * Remote time was remote_timestamp at local_timestamp. */ @@ -1286,6 +1371,11 @@ static int ptp_update_local_clock(FAR struct ptp_state_s *state, state->drift_avg_total_ms = 0; state->drift_ppb = 0; state->has_last_delta = false; +#if CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS > 0 + state->delta_hist_count = 0; + state->delta_hist_next = 0; + state->outlier_count = 0; +#endif if (ret == OK) { @@ -1312,6 +1402,15 @@ static int ptp_update_local_clock(FAR struct ptp_state_s *state, (int64_t)CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * CONFIG_CLOCK_ADJTIME_PERIOD_MS; +#if CONFIG_NETUTILS_PTPD_OUTLIER_THRESHOLD_NS > 0 + if (ptp_is_outlier(state, delta_ns)) + { + ptpwarn("Discarding outlier sample: delta %" PRId64 " ns\n", + delta_ns); + return OK; + } +#endif + if (!state->has_last_delta) { /* First measurement after jump or startup: no previous