diff --git a/linuxptp-holdover.patch b/linuxptp-holdover.patch new file mode 100644 index 0000000..b6843d8 --- /dev/null +++ b/linuxptp-holdover.patch @@ -0,0 +1,360 @@ +commit c7acd4b5bdb95e45d93833ffaac9cac51dfe934b +Author: Miroslav Lichvar +Date: Thu May 16 15:52:48 2024 +0200 + + ts2phc: Avoid unnecessary call of getppstime(). + + Don't get the ToD timestamp for the pulse polarity detection if it won't + be needed (i.e. extts_polarity is not "both"). This allows PPS + timestamps to be saved even when the ToD source fails. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c +index 0d399b8..76912a2 100644 +--- a/ts2phc_pps_sink.c ++++ b/ts2phc_pps_sink.c +@@ -277,21 +277,22 @@ static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv, + goto out; + } + +- err = ts2phc_pps_source_getppstime(priv->src, &source_ts); +- if (err < 0) { +- pr_debug("source ts not valid"); +- return 0; +- } +- +- if (sink->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE) && +- ts2phc_pps_sink_ignore(priv, sink, source_ts)) { ++ if (sink->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE)) { ++ err = ts2phc_pps_source_getppstime(priv->src, &source_ts); ++ if (err < 0) { ++ pr_debug("source ts not valid"); ++ return 0; ++ } + +- pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld", +- sink->name, event.index, event.t.sec, event.t.nsec, +- (int64_t) source_ts.tv_sec, source_ts.tv_nsec); ++ if (ts2phc_pps_sink_ignore(priv, sink, source_ts)) { ++ pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld", ++ sink->name, event.index, event.t.sec, ++ event.t.nsec, (int64_t)source_ts.tv_sec, ++ source_ts.tv_nsec); + +- result = EXTTS_IGNORE; +- goto out; ++ result = EXTTS_IGNORE; ++ goto out; ++ } + } + + out: + +commit 9880cccc928351a42a3fa9e018949442aca7ddae +Author: Miroslav Lichvar +Date: Thu May 16 15:52:49 2024 +0200 + + ts2phc: Add holdover support. + + If the external PPS signal is generated by a clock with better long-term + stability than the clock synchronizing to it, it can work as a good time + source for some period of time after losing its own time source (e.g. + GPS receiver losing its signal). + + Add an option to specify a holdover interval where ts2phc can continue + synchronizing the clock without any ToD information. Allow that only in + the LOCKED_STABLE servo state, which needs to be enabled by the + servo_num_offset_values option. + + This is supported only in the non-automatic mode and when the pulse + polarity detection is disabled. + + Signed-off-by: Miroslav Lichvar + +diff --git a/config.c b/config.c +index c220a3e..58481db 100644 +--- a/config.c ++++ b/config.c +@@ -344,6 +344,7 @@ struct config_item config_tab[] = { + PORT_ITEM_INT("ts2phc.channel", 0, 0, INT_MAX), + PORT_ITEM_INT("ts2phc.extts_correction", 0, INT_MIN, INT_MAX), + PORT_ITEM_ENU("ts2phc.extts_polarity", PTP_RISING_EDGE, extts_polarity_enu), ++ PORT_ITEM_INT("ts2phc.holdover", 0, 0, INT_MAX), + PORT_ITEM_INT("ts2phc.master", 0, 0, 1), + PORT_ITEM_INT("ts2phc.nmea_baudrate", 9600, 300, INT_MAX), + GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""), +diff --git a/ts2phc.8 b/ts2phc.8 +index 852a527..bcc6f61 100644 +--- a/ts2phc.8 ++++ b/ts2phc.8 +@@ -168,6 +168,16 @@ by changing the clock frequency instead of stepping the clock. When + set to 0.0, the servo will never step the clock except on start. + The default is 0.0. + ++.TP ++.B ts2phc.holdover ++The holdover interval, specified in seconds. When the ToD information stops ++working (e.g. GNSS receiver lost its fix), ts2phc is allowed for the specified ++interval to continue synchronizing the target clock as long as the servo is in ++the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the ++\fBservo_num_offset_values\fP option. The holdover is not supported with the ++\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP. ++The default is 0 (disabled). ++ + .TP + .B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port + Specifies the remote host providing ToD information when using the +diff --git a/ts2phc.c b/ts2phc.c +index 4817c85..d552e0f 100644 +--- a/ts2phc.c ++++ b/ts2phc.c +@@ -440,9 +440,10 @@ static int ts2phc_pps_source_implicit_tstamp(struct ts2phc_private *priv, + + static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) + { ++ struct timespec source_ts, now; + tmv_t source_tmv; + struct ts2phc_clock *c; +- int valid, err; ++ int holdover, valid; + + if (autocfg) { + if (!priv->ref_clock) { +@@ -456,9 +457,20 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) + return; + } + } else { +- err = ts2phc_pps_source_implicit_tstamp(priv, &source_tmv); +- if (err < 0) ++ valid = !ts2phc_pps_source_implicit_tstamp(priv, &source_tmv); ++ } ++ ++ if (valid) { ++ priv->holdover_start = 0; ++ holdover = 0; ++ } else { ++ clock_gettime(CLOCK_MONOTONIC, &now); ++ ++ if (!priv->holdover_start) ++ priv->holdover_start = now.tv_sec; ++ if (now.tv_sec >= priv->holdover_start + priv->holdover_length) + return; ++ holdover = 1; + } + + LIST_FOREACH(c, &priv->clocks, list) { +@@ -475,6 +487,16 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) + continue; + } + ++ if (holdover) { ++ if (c->servo_state != SERVO_LOCKED_STABLE) ++ continue; ++ source_ts = tmv_to_timespec(ts); ++ if (source_ts.tv_nsec > NS_PER_SEC / 2) ++ source_ts.tv_sec++; ++ source_ts.tv_nsec = 0; ++ source_tmv = timespec_to_tmv(source_ts); ++ } ++ + offset = tmv_to_nanoseconds(tmv_sub(ts, source_tmv)); + + if (c->no_adj) { +@@ -486,8 +508,15 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) + adj = servo_sample(c->servo, offset, tmv_to_nanoseconds(ts), + SAMPLE_WEIGHT, &c->servo_state); + +- pr_info("%s offset %10" PRId64 " s%d freq %+7.0f", +- c->name, offset, c->servo_state, adj); ++ if (holdover && c->servo_state != SERVO_LOCKED_STABLE) { ++ pr_info("%s lost holdover lock (offset %10" PRId64 ")", ++ c->name, offset); ++ continue; ++ } ++ ++ pr_info("%s offset %10" PRId64 " s%d freq %+7.0f%s", ++ c->name, offset, c->servo_state, adj, ++ holdover ? " holdover" : ""); + + switch (c->servo_state) { + case SERVO_UNLOCKED: +@@ -751,6 +780,9 @@ int main(int argc, char *argv[]) + return -1; + } + ++ priv.holdover_length = config_get_int(cfg, NULL, "ts2phc.holdover"); ++ priv.holdover_start = 0; ++ + while (is_running()) { + struct ts2phc_clock *c; + +diff --git a/ts2phc.h b/ts2phc.h +index 4833ded..5dbde9b 100644 +--- a/ts2phc.h ++++ b/ts2phc.h +@@ -55,6 +55,8 @@ struct ts2phc_private { + bool state_changed; + LIST_HEAD(port_head, ts2phc_port) ports; + LIST_HEAD(clock_head, ts2phc_clock) clocks; ++ int holdover_length; ++ time_t holdover_start; + }; + + struct ts2phc_clock *ts2phc_clock_add(struct ts2phc_private *priv, +commit bf237fd55d4983a42d9344890dd861f18bea70ca +Author: Miroslav Lichvar +Date: Thu Jul 25 12:43:59 2024 +0200 + + ts2phc: Describe servo options in man page. + + Copy and adapt missing servo options used by ts2phc from the ptp4l man + page. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc.8 b/ts2phc.8 +index bcc6f61..4c54576 100644 +--- a/ts2phc.8 ++++ b/ts2phc.8 +@@ -117,6 +117,16 @@ command line option. + + .SH GLOBAL OPTIONS + ++.TP ++.B clock_servo ++The servo which is used to synchronize the local clock. Valid values ++are "pi" for a PI controller, "linreg" for an adaptive controller ++using linear regression, "ntpshm" and "refclock_sock" for the NTP SHM and ++chrony SOCK reference clocks respectively to allow another process to ++synchronize the local clock, and "nullf" for a servo that always dials ++frequency offset zero (for use in SyncE nodes). ++The default is "pi". ++ + .TP + .B first_step_threshold + The maximum offset, specified in seconds, that the servo will correct by +@@ -162,12 +172,64 @@ with the log level of the message as a number. The default is an empty string + argument). + + .TP +-.B step_threshold +-The maximum offset, specified in seconds, that the servo will correct +-by changing the clock frequency instead of stepping the clock. When +-set to 0.0, the servo will never step the clock except on start. ++.B ntpshm_segment ++The number of the SHM segment used by ntpshm servo. ++The default is 0. ++ ++.TP ++.B pi_integral_const ++The integral constant of the PI controller. When set to 0.0, the ++integral constant will be set by the following formula from the current ++sync interval. ++The default is 0.0. ++ ++ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync) ++ ++.TP ++.B pi_integral_exponent ++The ki_exponent constant in the formula used to set the integral constant of ++the PI controller from the sync interval. ++The default is 0.4. ++ ++.TP ++.B pi_integral_norm_max ++The ki_norm_max constant in the formula used to set the integral constant of ++the PI controller from the sync interval. ++The default is 0.3. ++ ++.TP ++.B pi_integral_scale ++The ki_scale constant in the formula used to set the integral constant of ++the PI controller from the sync interval. ++The default is 0.3. ++ ++.TP ++.B pi_proportional_const ++The proportional constant of the PI controller. When set to 0.0, the ++proportional constant will be set by the following formula from the current ++sync interval. + The default is 0.0. + ++kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync) ++ ++.TP ++.B pi_proportional_exponent ++The kp_exponent constant in the formula used to set the proportional constant of ++the PI controller from the sync interval. ++The default is \-0.3. ++ ++.TP ++.B pi_proportional_norm_max ++The kp_norm_max constant in the formula used to set the proportional constant of ++the PI controller from the sync interval. ++The default is 0.7 ++ ++.TP ++.B pi_proportional_scale ++The kp_scale constant in the formula used to set the proportional constant of ++the PI controller from the sync interval. ++The default is 0.7. ++ + .TP + .B ts2phc.holdover + The holdover interval, specified in seconds. When the ToD information stops +@@ -178,6 +240,28 @@ the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the + \fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP. + The default is 0 (disabled). + ++.TP ++.B servo_num_offset_values ++The number of offset values considered in order to transition from the ++SERVO_LOCKED to the SERVO_LOCKED_STABLE state. ++The transition occurs once the last 'servo_num_offset_values' offsets ++are all below the 'servo_offset_threshold' value. ++The default value is 10. ++ ++.TP ++.B servo_offset_threshold ++The offset threshold used in order to transition from the SERVO_LOCKED ++to the SERVO_LOCKED_STABLE state. The transition occurs once the ++last 'servo_num_offset_values' offsets are all below the threshold value. ++The default value of offset_threshold is 0 (disabled). ++ ++.TP ++.B step_threshold ++The maximum offset, specified in seconds, that the servo will correct ++by changing the clock frequency instead of stepping the clock. When ++set to 0.0, the servo will never step the clock except on start. ++The default is 0.0. ++ + .TP + .B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port + Specifies the remote host providing ToD information when using the + +commit 7734d2fe8ac52afaa233262548615f79021ae6ee +Author: Miroslav Lichvar +Date: Thu Jul 25 12:46:43 2024 +0200 + + ts2phc: Fix description of holdover option in man page. + + Fix the man page to explain that the LOCKED_STABLE servo state is + enabled by setting the servo_offset_threshold, not + servo_num_offset_values, which is already enabled by default. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc.8 b/ts2phc.8 +index 4c54576..c0b718b 100644 +--- a/ts2phc.8 ++++ b/ts2phc.8 +@@ -236,7 +236,7 @@ The holdover interval, specified in seconds. When the ToD information stops + working (e.g. GNSS receiver lost its fix), ts2phc is allowed for the specified + interval to continue synchronizing the target clock as long as the servo is in + the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the +-\fBservo_num_offset_values\fP option. The holdover is not supported with the ++\fBservo_offset_threshold\fP option. The holdover is not supported with the + \fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP. + The default is 0 (disabled). + diff --git a/linuxptp.spec b/linuxptp.spec index 8a3eaac..86cf692 100644 --- a/linuxptp.spec +++ b/linuxptp.spec @@ -39,6 +39,8 @@ Patch7: linuxptp-nmealeap.patch Patch8: linuxptp-nmeareset.patch # add options to configure multicast IP addresses Patch9: linuxptp-addropts.patch +# add holdover support to ts2phc +Patch10: linuxptp-holdover.patch # check for EL-specific kernels with vclock support Patch12: linuxptp-vclock.patch