diff --git a/SOURCES/linuxptp-holdover.patch b/SOURCES/linuxptp-holdover.patch new file mode 100644 index 0000000..b6843d8 --- /dev/null +++ b/SOURCES/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/SOURCES/linuxptp-nmeadelay.patch b/SOURCES/linuxptp-nmeadelay.patch index b06c7c1..64c1ba1 100644 --- a/SOURCES/linuxptp-nmeadelay.patch +++ b/SOURCES/linuxptp-nmeadelay.patch @@ -1,29 +1,365 @@ -commit 0404c1924254c9162222dd5000a140165d21250c +commit 0c406008b530140ed6d992915a6c8a1e5abf15d5 Author: Miroslav Lichvar -Date: Thu Jan 25 15:23:44 2024 +0100 +Date: Thu Jan 25 11:26:15 2024 +0100 - ts2phc: Fix offset for NMEA delays over 0.5 seconds. + ts2phc: Don't switch system clock to nanosecond mode. - The current code of ts2phc assumes that the NMEA RMC message is received - within 0.5 seconds of the pulse the timestamp in the message is refering - to. However, with most receivers NMEA messages are referenced to the - previous pulse. This causes a 1-second error in the measured offset for - receivers with delays over 0.5 seconds. + ts2phc is not synchronizing the system clock and should not switch the + clock to the nanosecond mode with adjtimex(modes=ADJ_NANO) or make any + other modifications to it. The process that is controlling the clock + (e.g. an NTP client) might not be using the nanosecond mode. - Add a 0.5 second correction to map the whole interval between pulses to - the preceding pulse. + There are two instances of the adjtimex() call in the code. One is used + only to read the clock and can be replaced with faster clock_gettime(). + The other instance is also reading the TAI offset. Instead of switching + to the nanosecond mode, change the timestamp conversion to handle both + microsecond and nanosecond modes according to the current clock status. + + Reviewed-by: Jacob Keller + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc_generic_pps_source.c b/ts2phc_generic_pps_source.c +index d503aac..e6b8145 100644 +--- a/ts2phc_generic_pps_source.c ++++ b/ts2phc_generic_pps_source.c +@@ -38,7 +38,6 @@ static int get_ntx(struct timex *ntx) + return -1; + + memset(ntx, 0, sizeof(*ntx)); +- ntx->modes = ADJ_NANO; + code = adjtimex(ntx); + if (code == -1) { + pr_err("adjtimex failed: %m"); +@@ -93,7 +92,10 @@ static int ts2phc_generic_pps_source_getppstime(struct ts2phc_pps_source *src, + } + + ts->tv_sec = ntx.time.tv_sec + tai_offset; +- ts->tv_nsec = ntx.time.tv_usec; ++ if (ntx.status & STA_NANO) ++ ts->tv_nsec = ntx.time.tv_usec; ++ else ++ ts->tv_nsec = ntx.time.tv_usec * 1000; + + return 0; + } +diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c +index 3a4267d..5b2e06b 100644 +--- a/ts2phc_nmea_pps_source.c ++++ b/ts2phc_nmea_pps_source.c +@@ -62,14 +62,13 @@ static int open_nmea_connection(const char *host, const char *port, + + static void *monitor_nmea_status(void *arg) + { ++ struct timespec rxtime, rxtime_rt, tmo = { 2, 0 }; + struct nmea_parser *np = nmea_parser_create(); + struct pollfd pfd = { -1, POLLIN | POLLPRI }; + char *host, input[256], *port, *ptr, *uart; + struct ts2phc_nmea_pps_source *s = arg; +- struct timespec rxtime, tmo = { 2, 0 }; + int cnt, num, parsed, baud; + struct nmea_rmc rmc; +- struct timex ntx; + + if (!np) { + pr_err("failed to create NMEA parser"); +@@ -79,8 +78,6 @@ static void *monitor_nmea_status(void *arg) + port = config_get_string(s->config, NULL, "ts2phc.nmea_remote_port"); + uart = config_get_string(s->config, NULL, "ts2phc.nmea_serialport"); + baud = config_get_int(s->config, NULL, "ts2phc.nmea_baudrate"); +- memset(&ntx, 0, sizeof(ntx)); +- ntx.modes = ADJ_NANO; + + while (is_running()) { + if (pfd.fd == -1) { +@@ -92,7 +89,7 @@ static void *monitor_nmea_status(void *arg) + } + num = poll(&pfd, 1, NMEA_TMO); + clock_gettime(CLOCK_MONOTONIC, &rxtime); +- adjtimex(&ntx); ++ clock_gettime(CLOCK_REALTIME, &rxtime_rt); + if (num < 0) { + pr_err("poll failed"); + break; +@@ -124,8 +121,7 @@ static void *monitor_nmea_status(void *arg) + if (!nmea_parse(np, ptr, cnt, &rmc, &parsed)) { + pthread_mutex_lock(&s->mutex); + s->local_monotime = rxtime; +- s->local_utctime.tv_sec = ntx.time.tv_sec; +- s->local_utctime.tv_nsec = ntx.time.tv_usec; ++ s->local_utctime = rxtime_rt; + s->rmc_utctime = rmc.ts; + s->rmc_fix_valid = rmc.fix_valid; + pthread_mutex_unlock(&s->mutex); + +commit 72b44bc885e519667a12c89d5b640484807e4946 +Author: Miroslav Lichvar +Date: Tue Jun 4 08:57:15 2024 +0200 + + ts2phc: Use CLOCK_MONOTONIC_RAW for NMEA PPS timestamp. + + In the calculation of the NMEA PPS timestamp is used an interval + measured by the CLOCK_MONOTONIC system clock. This clock may have a + large frequency error when another process (e.g. phc2sys or an NTP + client) is correcting a large time error by slewing. + + This frequency error may cause the timestamp to overflow into the next + second and cause a one-second error in the measured offset, or the wrong + edge of the pulse to be rejected. + + Switch from CLOCK_MONOTONIC to CLOCK_MONOTONIC_RAW to avoid the + impact of the system clock adjustments. Signed-off-by: Miroslav Lichvar diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c -index 3a4267d..274b70a 100644 +index 5b2e06b..7a28433 100644 --- a/ts2phc_nmea_pps_source.c +++ b/ts2phc_nmea_pps_source.c -@@ -191,6 +191,7 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src, - return -1; +@@ -88,7 +88,7 @@ static void *monitor_nmea_status(void *arg) + } + } + num = poll(&pfd, 1, NMEA_TMO); +- clock_gettime(CLOCK_MONOTONIC, &rxtime); ++ clock_gettime(CLOCK_MONOTONIC_RAW, &rxtime); + clock_gettime(CLOCK_REALTIME, &rxtime_rt); + if (num < 0) { + pr_err("poll failed"); +@@ -160,7 +160,7 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src, + int64_t utc_time; + bool fix_valid; + +- clock_gettime(CLOCK_MONOTONIC, &now); ++ clock_gettime(CLOCK_MONOTONIC_RAW, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&m->mutex); + +commit 30e6c4dba892236d8cfe08dc6c55238e11504c71 +Author: Miroslav Lichvar +Date: Mon Jun 3 10:32:05 2024 +0200 + + ts2phc: Provide source type. + + Save the PPS source type in the instance and add a function to retrieve + it. This will be needed for NMEA-specific rounding of timestamps. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c +index c333f65..ae2ad46 100644 +--- a/ts2phc_pps_source.c ++++ b/ts2phc_pps_source.c +@@ -26,6 +26,8 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, + src = ts2phc_phc_pps_source_create(priv, dev); + break; } - rmc = tmv_add(rmc, duration_since_rmc); -+ rmc = tmv_add(rmc, nanoseconds_to_tmv(500000000)); - utc_time = tmv_to_nanoseconds(rmc); - utc_time /= (int64_t) 1000000000; - *ts = tmv_to_timespec(rmc); ++ if (src) ++ src->type = type; + return src; + } + +@@ -46,3 +48,8 @@ struct ts2phc_clock *ts2phc_pps_source_get_clock(struct ts2phc_pps_source *src) + + return NULL; + } ++ ++enum ts2phc_pps_source_type ts2phc_pps_source_get_type(struct ts2phc_pps_source *src) ++{ ++ return src->type; ++} +diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h +index 293c693..c87e3af 100644 +--- a/ts2phc_pps_source.h ++++ b/ts2phc_pps_source.h +@@ -53,4 +53,11 @@ int ts2phc_pps_source_getppstime(struct ts2phc_pps_source *src, struct timespec + + struct ts2phc_clock *ts2phc_pps_source_get_clock(struct ts2phc_pps_source *src); + ++/** ++ * Returns the type of the PPS source ++ * @param src Pointer to a source obtained via @ref ts2phc_pps_source_create(). ++ * @return The type of the clock. ++ */ ++enum ts2phc_pps_source_type ts2phc_pps_source_get_type(struct ts2phc_pps_source *src); ++ + #endif +diff --git a/ts2phc_pps_source_private.h b/ts2phc_pps_source_private.h +index 99e6a78..ea6a8ad 100644 +--- a/ts2phc_pps_source_private.h ++++ b/ts2phc_pps_source_private.h +@@ -13,6 +13,7 @@ + #include "ts2phc_pps_source.h" + + struct ts2phc_pps_source { ++ enum ts2phc_pps_source_type type; + void (*destroy)(struct ts2phc_pps_source *src); + int (*getppstime)(struct ts2phc_pps_source *src, struct timespec *ts); + struct ts2phc_clock *(*get_clock)(struct ts2phc_pps_source *src); + +commit bebd15ae90dbfcb74a33e5b428f24c733abf1134 +Author: Miroslav Lichvar +Date: Mon Jun 3 11:06:16 2024 +0200 + + ts2phc: Fix edge rejection for pulse widths over 0.5s. + + If the configured pulse width is longer than 0.5 seconds, the calculated + range of ignored offsets is too small to cover the wrong edge. Fix the + calculation of the limits to use the minimum of pulsewidth and + (1.0s - pulsewidth). A pulsewidth of 0.5s should give the shortest interval. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c +index 05ac225..ca9f721 100644 +--- a/ts2phc_pps_sink.c ++++ b/ts2phc_pps_sink.c +@@ -175,6 +175,8 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri + sink->correction = nanoseconds_to_tmv(correction); + + pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); ++ if (pulsewidth > 500000000) ++ pulsewidth = 1000000000 - pulsewidth; + pulsewidth /= 2; + sink->ignore_upper = 1000000000 - pulsewidth; + sink->ignore_lower = pulsewidth; + +commit 435e9fc6ebec8daa8ab9f88c2d590e35ace9b2f6 +Author: Miroslav Lichvar +Date: Mon Jun 3 13:57:23 2024 +0200 + + ts2phc: Move upper/lower rejection limit calculation. + + The ignore_upper and ignore_lower fields of the ts2phc_pps_sink struct + are calculated when the PPS source is not known yet. Replace the fields + with the configured pulsewidth and calculate the limits locally later + when needed in ts2phc_pps_sink_ignore(). This will allow an + NMEA-specific calculation of the limits. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c +index ca9f721..8121afb 100644 +--- a/ts2phc_pps_sink.c ++++ b/ts2phc_pps_sink.c +@@ -30,8 +30,7 @@ struct ts2phc_pps_sink { + struct ptp_pin_desc pin_desc; + unsigned int polarity; + tmv_t correction; +- uint32_t ignore_lower; +- uint32_t ignore_upper; ++ uint32_t pulsewidth; + struct ts2phc_clock *clock; + }; + +@@ -153,8 +152,8 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri + struct config *cfg = priv->cfg; + struct ptp_extts_request extts; + struct ts2phc_pps_sink *sink; +- int err, pulsewidth; + int32_t correction; ++ int err; + + sink = calloc(1, sizeof(*sink)); + if (!sink) { +@@ -174,12 +173,9 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri + correction = config_get_int(cfg, device, "ts2phc.extts_correction"); + sink->correction = nanoseconds_to_tmv(correction); + +- pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); +- if (pulsewidth > 500000000) +- pulsewidth = 1000000000 - pulsewidth; +- pulsewidth /= 2; +- sink->ignore_upper = 1000000000 - pulsewidth; +- sink->ignore_lower = pulsewidth; ++ sink->pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); ++ if (sink->pulsewidth > 500000000) ++ sink->pulsewidth = 1000000000 - sink->pulsewidth; + + sink->clock = ts2phc_clock_add(priv, device); + if (!sink->clock) { +@@ -243,12 +239,16 @@ static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv, + struct timespec source_ts) + { + tmv_t source_tmv = timespec_to_tmv(source_ts); ++ uint32_t ignore_lower, ignore_upper; + + source_tmv = tmv_sub(source_tmv, priv->perout_phase); + source_ts = tmv_to_timespec(source_tmv); + +- return source_ts.tv_nsec > sink->ignore_lower && +- source_ts.tv_nsec < sink->ignore_upper; ++ ignore_upper = 1000000000 - sink->pulsewidth / 2; ++ ignore_lower = sink->pulsewidth / 2; ++ ++ return source_ts.tv_nsec > ignore_lower && ++ source_ts.tv_nsec < ignore_upper; + } + + static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv, + +commit 0257b245df1a32869f356c0cfbeacfe5f0a522f5 +Author: Miroslav Lichvar +Date: Mon Jun 3 11:31:45 2024 +0200 + + ts2phc: Allow longer NMEA delays. + + Timestamps from PPS sources are rounded to the nearest pulse, i.e. for + correct synchronization the offset between the source timestamp and + external PPS event timestamp needs to be between -0.5 and +0.5 seconds. + For the pulse edge rejection to work correctly it needs to be even + smaller, between -pulsewidth/2 and +pulsewidth/2. + + With the NMEA PPS source the offset is the delay in the reception of the + RMC message. This message is not expected to come exactly on time with + the corresponding pulse. A positive delay is expected, typically at + least few tens of milliseconds, but some receivers have delays longer + than 0.5 seconds. + + Add NMEA-specific rounding of the offset to not expect negative delays + and allow positive delays of up to 1 second, or whole pulsewidth if the + edge rejection is enabled. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc.c b/ts2phc.c +index 03c88b1..4817c85 100644 +--- a/ts2phc.c ++++ b/ts2phc.c +@@ -418,9 +418,16 @@ static int ts2phc_pps_source_implicit_tstamp(struct ts2phc_private *priv, + * deduce the timestamp (actually only seconds part, nanoseconds are by + * construction zero) of this edge at the emitter based on the + * emitter's current time. ++ * ++ * With an NMEA source assume its messages always follow the pulse, i.e. ++ * assign the timestamp to the previous pulse instead of nearest pulse. + */ +- if (source_ts.tv_nsec > NS_PER_SEC / 2) ++ if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) { + source_ts.tv_sec++; ++ } else { ++ if (source_ts.tv_nsec > NS_PER_SEC / 2) ++ source_ts.tv_sec++; ++ } + source_ts.tv_nsec = 0; + + tmv = timespec_to_tmv(source_ts); +diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c +index 8121afb..0d399b8 100644 +--- a/ts2phc_pps_sink.c ++++ b/ts2phc_pps_sink.c +@@ -244,8 +244,13 @@ static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv, + source_tmv = tmv_sub(source_tmv, priv->perout_phase); + source_ts = tmv_to_timespec(source_tmv); + +- ignore_upper = 1000000000 - sink->pulsewidth / 2; +- ignore_lower = sink->pulsewidth / 2; ++ if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) { ++ ignore_upper = sink->pulsewidth; ++ ignore_lower = 0; ++ } else { ++ ignore_upper = 1000000000 - sink->pulsewidth / 2; ++ ignore_lower = sink->pulsewidth / 2; ++ } + + return source_ts.tv_nsec > ignore_lower && + source_ts.tv_nsec < ignore_upper; diff --git a/SOURCES/linuxptp-nmealeap.patch b/SOURCES/linuxptp-nmealeap.patch new file mode 100644 index 0000000..f00cb0b --- /dev/null +++ b/SOURCES/linuxptp-nmealeap.patch @@ -0,0 +1,76 @@ +commit f3c742e24a40cf75272ec39789a2cba35389230d +Author: Miroslav Lichvar +Date: Tue Jun 11 15:25:54 2024 +0200 + + nmea: Fix conversion of leap second. + + When a leap second is inserted, the RMC message reports time of + 23:59:60, which overflows in mktime() to 0:00:00 after the leap second + with an incremented TAI-UTC offset. This causes a one-second error in + the offset meaured by ts2phc. + + Check the seconds field of the RMC message and convert 60 to 59 to make + the timestamp ambiguous per is_utc_ambiguous() and ignored by ts2phc to + avoid updating the clock with the one-second error. + + Signed-off-by: Miroslav Lichvar + +diff --git a/nmea.c b/nmea.c +index 7f1d9a2..b84b42e 100644 +--- a/nmea.c ++++ b/nmea.c +@@ -155,6 +155,9 @@ static int nmea_scan_rmc(struct nmea_parser *np, struct nmea_rmc *result) + if (cnt != 3) { + return -1; + } ++ /* Convert an inserted leap second to ambiguous 23:59:59 */ ++ if (tm.tm_sec == 60) ++ tm.tm_sec = 59; + tm.tm_year += 100; + tm.tm_mon--; + tm.tm_isdst = 0; + +commit b396d361b0d290ce83395851860c2dcd074e0f3b +Author: Miroslav Lichvar +Date: Tue Jun 11 15:32:55 2024 +0200 + + ts2phc: Fix timestamp conversion for leap seconds. + + The UTC timestamp parsed from the RMC message needs to be converted to + TAI in order to calculate the PHC offset. This conversion was done after + adjusting the timestamp for the measured delay between the reception of + the message and the following pulse, which caused the offset measured by + ts2phc to have a one-second error if the message expected during or + after a leap second was missed. + + Apply the TAI-UTC offset to the timestamp parsed from the RMC message + before any adjustments are made to avoid the error. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c +index 7a28433..bdfaf19 100644 +--- a/ts2phc_nmea_pps_source.c ++++ b/ts2phc_nmea_pps_source.c +@@ -186,10 +186,9 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src, + pr_err("nmea: rmc time stamp stale"); + return -1; + } +- rmc = tmv_add(rmc, duration_since_rmc); ++ + utc_time = tmv_to_nanoseconds(rmc); + utc_time /= (int64_t) 1000000000; +- *ts = tmv_to_timespec(rmc); + + result = lstab_utc2tai(m->lstab, utc_time, &tai_offset); + switch (result) { +@@ -206,6 +205,9 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src, + pr_err("nmea: utc time stamp is ambiguous"); + break; + } ++ ++ rmc = tmv_add(rmc, duration_since_rmc); ++ *ts = tmv_to_timespec(rmc); + ts->tv_sec += tai_offset; + + return lstab_error; diff --git a/SOURCES/linuxptp-nmeareset.patch b/SOURCES/linuxptp-nmeareset.patch new file mode 100644 index 0000000..d52f279 --- /dev/null +++ b/SOURCES/linuxptp-nmeareset.patch @@ -0,0 +1,25 @@ +commit b4ad9dccf23cef2d79621cd36987428a65f2bcc9 +Author: Miroslav Lichvar +Date: Tue Jun 11 12:35:10 2024 +0200 + + ts2phc: Reset parser after RMC message. + + When the receiver was configured to generate only RMC messages, every + other message was skipped due to the parser not being in the right + state to process another message. Reset the parser to correctly parse + every valid message. + + Signed-off-by: Miroslav Lichvar + +diff --git a/nmea.c b/nmea.c +index 44c7c01..7f1d9a2 100644 +--- a/nmea.c ++++ b/nmea.c +@@ -171,6 +171,7 @@ int nmea_parse(struct nmea_parser *np, const char *ptr, int buflen, + while (buflen) { + if (!nmea_parse_symbol(np, *ptr)) { + if (!nmea_scan_rmc(np, result)) { ++ nmea_reset(np); + *parsed = count + 1; + return 0; + } diff --git a/SPECS/linuxptp.spec b/SPECS/linuxptp.spec index 3fd7efe..7714142 100644 --- a/SPECS/linuxptp.spec +++ b/SPECS/linuxptp.spec @@ -4,7 +4,7 @@ Name: linuxptp Version: 4.2 -Release: 2%{?dist} +Release: 2%{?dist}.2.alma.1 Summary: PTP implementation for Linux License: GPLv2+ @@ -33,6 +33,14 @@ Patch4: linuxptp-ucastrate.patch Patch5: linuxptp-nmeadelay.patch # fix loading and reloading of leapfile Patch6: linuxptp-lstab.patch +# Patches were taken from: +# https://gitlab.com/redhat/centos-stream/rpms/linuxptp/-/commit/341bd2c2cd058422939512d9934ad783123e5e10 +Patch7: linuxptp-nmealeap.patch +# https://github.com/richardcochran/linuxptp/commit/e110cd99e96ac8504e0feffd65048398a46e4ba8 +Patch8: linuxptp-nmeareset.patch +# https://gitlab.com/redhat/centos-stream/rpms/linuxptp/-/commit/781f2f4c40338da872cf5b90bf2292a007c26d6a +Patch9: linuxptp-holdover.patch + # check for EL-specific kernels with vclock support Patch12: linuxptp-vclock.patch @@ -113,6 +121,11 @@ PATH=..:$PATH ./run %{_mandir}/man8/*.8* %changelog +* Tue Sep 03 2024 Eduard Abdullin 4.2-2.2 +- ts2phc: Avoid unnecessary call of getppstime(). +- ts2phc: Don't switch system clock to nanosecond mode. +- nmea: Fix conversion of leap second. + * Thu Feb 22 2024 Miroslav Lichvar 4.2-2 - fix loading and reloading of leapfile