rework NMEA delay patch to fix PPS edge rejection (RHEL-54719)

Resolves: RHEL-54719
This commit is contained in:
Miroslav Lichvar 2024-08-19 12:47:36 +02:00
parent 14b00cf4eb
commit 8a2fbb8daf

View File

@ -1,29 +1,342 @@
commit 0404c1924254c9162222dd5000a140165d21250c
commit c4d00cfd9a611c9157f57a97babde11255f4b4e4
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jan 25 15:23:44 2024 +0100
Date: Mon Jun 3 16:43:50 2024 +0200
ts2phc: Fix offset for NMEA delays over 0.5 seconds.
ts2phc: Provide source type.
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.
Add a 0.5 second correction to map the whole interval between pulses to
the preceding pulse.
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 <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
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;
}
+ 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 e9f8d8fa119109f15e2377aef0ce4c62e261cba4
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 16:43:51 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 <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index e3e9caf..33df968 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 81c447a24c75d56d414bf318ea5b5a6fc35d5129
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 16:43:52 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 <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index 33df968..be3bd9a 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) {
@@ -248,12 +244,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 683f66aa46b7f9a5bd434e65a2d5703145c43b09
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 16:43:53 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 <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/ts2phc.c b/ts2phc.c
index 9fca34d..39d31b6 100644
--- a/ts2phc.c
+++ b/ts2phc.c
@@ -420,9 +420,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 be3bd9a..f285c2b 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -249,8 +249,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;
commit 50f5a22df7a9a3f361e42bd29f84e26af04ac78b
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 16:43:54 2024 +0200
ts2phc: Add option to correct for NMEA delay.
Add an option to specify the minimum expected delay of NMEA RMC messages
to correct timestamps returned by the NMEA PPS time source.
This enables operation with receivers that have delays outside of the
expected 0-1.0s interval, or 0-pulsewidth if the PPS edge rejection is
enabled.
[ RPC: Preserve alphabetical ordering in the ts2phc man page. ]
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Signed-off-by: Richard Cochran <richardcochran@gmail.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/config.c b/config.c
index 5dcf7ca..d0bc32c 100644
--- a/config.c
+++ b/config.c
@@ -373,6 +373,7 @@ struct config_item config_tab[] = {
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),
+ PORT_ITEM_INT("ts2phc.nmea_delay", 0, INT_MIN, INT_MAX),
GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""),
GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""),
GLOB_ITEM_STR("ts2phc.nmea_serialport", "/dev/ttyS0"),
diff --git a/ts2phc.8 b/ts2phc.8
index 41f91d9..177586d 100644
--- a/ts2phc.8
+++ b/ts2phc.8
@@ -239,16 +239,6 @@ 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
-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_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).
-
.TP
.B sa_file
Specifies the location of the file containing Security Associations used
@@ -288,6 +278,26 @@ for all messages in accordance to the corresponding security association
sourced via the \fBsa_file\fR directive. Not compatible with one step ports.
Must be in the range of -1 to 255, inclusive. The default is -1 (disabled).
+.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_delay
+Specifies the minimum expected delay of NMEA RMC messages in nanoseconds.
+If the maximum delay is longer than 1 second, or 'ts2phc.pulsewidth'
+if 'ts2phc.extts_polarity' is set to "both", this option needs to be set
+accordingly to allow the timestamps from NMEA messages to be correctly
+assigned to pulses from the PPS signal and wrong PPS edges to be rejected if
+the edge rejection is enabled.
+The default is 0 nanoseconds.
+
.TP
.B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port
Specifies the remote host providing ToD information when using the
diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c
index 3a4267d..274b70a 100644
index 7a28433..3385e39 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,
@@ -33,6 +33,7 @@ struct ts2phc_nmea_pps_source {
pthread_t worker;
/* Protects anonymous struct fields, below, from concurrent access. */
pthread_mutex_t mutex;
+ tmv_t delay_correction;
struct {
struct timespec local_monotime;
struct timespec local_utctime;
@@ -187,6 +188,7 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src,
return -1;
}
rmc = tmv_add(rmc, duration_since_rmc);
+ rmc = tmv_add(rmc, nanoseconds_to_tmv(500000000));
+ rmc = tmv_add(rmc, m->delay_correction);
utc_time = tmv_to_nanoseconds(rmc);
utc_time /= (int64_t) 1000000000;
*ts = tmv_to_timespec(rmc);
@@ -232,6 +234,8 @@ struct ts2phc_pps_source *ts2phc_nmea_pps_source_create(struct ts2phc_private *p
s->pps_source.destroy = ts2phc_nmea_pps_source_destroy;
s->pps_source.getppstime = ts2phc_nmea_pps_source_getppstime;
s->config = priv->cfg;
+ s->delay_correction = nanoseconds_to_tmv(
+ config_get_int(priv->cfg, NULL, "ts2phc.nmea_delay"));
pthread_mutex_init(&s->mutex, NULL);
err = pthread_create(&s->worker, NULL, monitor_nmea_status, s);
if (err) {