This API was introduced for 2 reasons: 1. Some hardware can emit PPS signals but not starting from arbitrary absolute times, but rather just emit at a certain phase offset from the beginning of each second. We _could_ patch ts2phc to always specify a start time of 0.000000000 to PTP_PEROUT_REQUEST, and in theory that should then become the kernel's responsibility to advance that time in the past by an integer number of seconds while keeping the phase untouched, but in practice, we would never know whether that would actually work with all in-kernel PHC drivers, since it wasn't enforced as a requirement before. So there was a need for a new flag that only specifies the phase of the periodic signal, and not the absolute start time.
2. Some hardware can, rather unfortunately, not distinguish between a rising and a falling extts edge. And, since whatever rises also has to fall before rising again, the strategy in ts2phc is to set a 'large' pulse width (half the period) and ignore the extts event corresponding to the mid-way between one second and another. This is all fine, but currently, ts2phc.pulsewidth is a read-only property in the config file. The kernel is not instructed in any way to use this value, it is simply that must be configured based on prior knowledge of the PHC's implementation. This API changes that. The introduction of a phase adjustment for the PHC kind of PPS sources means we have to adjust our implicit deduction of the precise perout timestamp. We put that code into a common function and convert all call sites to call that. We also need to do the same thing for the edge ignoring logic. Signed-off-by: Vladimir Oltean <olte...@gmail.com> --- v5->v6: - eliminate use of offensive word "approximate" - continue to use clock->fd rather than CLOCKID_TO_FD v4->v5: rebase on top of variable renames v3->v4: patch is new. config.c | 1 + missing.h | 52 +++++++++++++++++++++++ ts2phc.8 | 17 +++++++- ts2phc.c | 94 +++++++++++++++++++++++++++-------------- ts2phc.h | 1 + ts2phc_phc_pps_source.c | 41 ++++++++++++++++-- ts2phc_pps_sink.c | 16 ++++++- 7 files changed, 182 insertions(+), 40 deletions(-) diff --git a/config.c b/config.c index b5cf3970849f..e454c91ff0a1 100644 --- a/config.c +++ b/config.c @@ -327,6 +327,7 @@ struct config_item config_tab[] = { GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""), GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""), GLOB_ITEM_STR("ts2phc.nmea_serialport", "/dev/ttyS0"), + PORT_ITEM_INT("ts2phc.perout_phase", -1, 0, 999999999), PORT_ITEM_INT("ts2phc.pin_index", 0, 0, INT_MAX), GLOB_ITEM_INT("ts2phc.pulsewidth", 500000000, 1000000, 999000000), PORT_ITEM_ENU("tsproc_mode", TSPROC_FILTER, tsproc_enu), diff --git a/missing.h b/missing.h index 4c7ac57e5728..86669762046a 100644 --- a/missing.h +++ b/missing.h @@ -114,6 +114,58 @@ struct compat_ptp_clock_caps { #endif /*LINUX_VERSION_CODE < 5.8*/ +/* + * Bits of the ptp_perout_request.flags field: + */ + +#ifndef PTP_PEROUT_ONE_SHOT +#define PTP_PEROUT_ONE_SHOT (1<<0) +#endif + +#ifndef PTP_PEROUT_DUTY_CYCLE +#define PTP_PEROUT_DUTY_CYCLE (1<<1) +#endif + +#ifndef PTP_PEROUT_PHASE +#define PTP_PEROUT_PHASE (1<<2) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,9,0) + +struct compat_ptp_perout_request { + union { + /* + * Absolute start time. + * Valid only if (flags & PTP_PEROUT_PHASE) is unset. + */ + struct ptp_clock_time start; + /* + * Phase offset. The signal should start toggling at an + * unspecified integer multiple of the period, plus this value. + * The start time should be "as soon as possible". + * Valid only if (flags & PTP_PEROUT_PHASE) is set. + */ + struct ptp_clock_time phase; + }; + struct ptp_clock_time period; /* Desired period, zero means disable. */ + unsigned int index; /* Which channel to configure. */ + unsigned int flags; + union { + /* + * The "on" time of the signal. + * Must be lower than the period. + * Valid only if (flags & PTP_PEROUT_DUTY_CYCLE) is set. + */ + struct ptp_clock_time on; + /* Reserved for future use. */ + unsigned int rsv[4]; + }; +}; + +#define ptp_perout_request compat_ptp_perout_request + +#endif /* LINUX_VERSION_CODE < 5.9 */ + #ifndef PTP_MAX_SAMPLES #define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */ #endif /* PTP_MAX_SAMPLES */ diff --git a/ts2phc.8 b/ts2phc.8 index 36d56cce0270..ded6f9ac8afb 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -176,10 +176,23 @@ connection will be used in preference to the configured serial port. The default serial port is "/dev/ttyS0". The default baudrate is 9600 bps. .TP +.B ts2phc.perout_phase +Configures the offset between the beginning of the second and the PPS +source's rising edge. Available only for the PHC kind of PPS source. The supported +range is 0 to 999999999 nanoseconds. The default is 0 nanoseconds, but +leaving this option unspecified will not transmit the phase to the kernel, +instead PPS will be requested to start at an absolute time equal to the +nearest 2nd full second since the start of the program. This should yield +the same effect, but may not work with drivers that do not support +starting periodic output at an absolute time. +.TP .B ts2phc.pulsewidth -The expected pulse width of the external PPS signal in nanoseconds. +The pulse width of the external PPS signal in nanoseconds. When 'ts2phc.extts_polarity' is "both", the given pulse width is used -to detect and discard the time stamp of the unwanted edge. +to detect and discard the time stamp of the unwanted edge. In case the PPS +source is of the PHC kind, an attempt is made to request the kernel to actually +emit using this pulse width. If this fails, it is assumed that the specified +pulse width is correct, and the value is used in the edge rejection algorithm. The supported range is 1000000 to 990000000 nanoseconds. The default is 500000000 nanoseconds. .TP diff --git a/ts2phc.c b/ts2phc.c index 5f0acc31dd47..f7a57e479bf8 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -398,6 +398,44 @@ static void ts2phc_reconfigure(struct ts2phc_private *priv) pr_info("selecting %s as the reference clock", ref_clk->name); } +static int ts2phc_pps_source_implicit_tstamp(struct ts2phc_private *priv, + tmv_t *source_tmv) +{ + struct timespec source_ts; + tmv_t tmv; + int err; + + err = ts2phc_pps_source_getppstime(priv->src, &source_ts); + if (err < 0) { + pr_err("source ts not valid"); + return err; + } + + tmv = timespec_to_tmv(source_ts); + tmv = tmv_sub(tmv, priv->perout_phase); + source_ts = tmv_to_timespec(tmv); + + /* + * As long as the kernel doesn't support a proper API for reporting + * back a precise perout timestamp, we'll have to implicitly assume + * assumption that the current time on the PPS source is still within + * +/- half a second of the past perout output edge, and hence, we can + * 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. + */ + if (source_ts.tv_nsec > NS_PER_SEC / 2) + source_ts.tv_sec++; + source_ts.tv_nsec = 0; + + tmv = timespec_to_tmv(source_ts); + tmv = tmv_add(tmv, priv->perout_phase); + + *source_tmv = tmv; + + return 0; +} + static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) { tmv_t source_tmv; @@ -416,18 +454,9 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) return; } } else { - struct timespec source_ts; - - err = ts2phc_pps_source_getppstime(priv->src, &source_ts); - if (err < 0) { - pr_err("source ts not valid"); + err = ts2phc_pps_source_implicit_tstamp(priv, &source_tmv); + if (err < 0) return; - } - 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); } LIST_FOREACH(c, &priv->clocks, list) { @@ -476,7 +505,7 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) static int ts2phc_collect_pps_source_tstamp(struct ts2phc_private *priv) { struct ts2phc_clock *pps_src_clock; - struct timespec source_ts; + tmv_t source_tmv; int err; pps_src_clock = ts2phc_pps_source_get_clock(priv->src); @@ -489,26 +518,11 @@ static int ts2phc_collect_pps_source_tstamp(struct ts2phc_private *priv) if (!pps_src_clock) return 0; - err = ts2phc_pps_source_getppstime(priv->src, &source_ts); - if (err < 0) { - pr_err("source ts not valid"); + err = ts2phc_pps_source_implicit_tstamp(priv, &source_tmv); + if (err < 0) return err; - } - /* - * As long as the kernel doesn't support a proper API for reporting - * back a precise perout timestamp, we'll have to implicitly assume - * assumption that the current time on the PPS source is still within - * +/- half a second of the past perout output edge, and hence, we can - * 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. - */ - if (source_ts.tv_nsec > NS_PER_SEC / 2) - source_ts.tv_sec++; - source_ts.tv_nsec = 0; - - ts2phc_clock_add_tstamp(pps_src_clock, timespec_to_tmv(source_ts)); + ts2phc_clock_add_tstamp(pps_src_clock, source_tmv); return 0; } @@ -662,13 +676,29 @@ int main(int argc, char *argv[]) } STAILQ_FOREACH(iface, &cfg->interfaces, list) { - if (1 == config_get_int(cfg, interface_name(iface), "ts2phc.master")) { + const char *dev = interface_name(iface); + + if (1 == config_get_int(cfg, dev, "ts2phc.master")) { + int perout_phase; + if (pps_source) { fprintf(stderr, "too many PPS sources\n"); ts2phc_cleanup(&priv); return -1; } - pps_source = interface_name(iface); + pps_source = dev; + perout_phase = config_get_int(cfg, dev, + "ts2phc.perout_phase"); + /* + * We use a default value of -1 to distinguish whether + * to use the PTP_PEROUT_PHASE API or not. But if we + * don't use that (and therefore we use absolute start + * time), the phase is still zero, by our application's + * convention. + */ + if (perout_phase < 0) + perout_phase = 0; + priv.perout_phase = nanoseconds_to_tmv(perout_phase); } else { if (ts2phc_pps_sink_add(&priv, interface_name(iface))) { fprintf(stderr, "failed to add PPS sink\n"); diff --git a/ts2phc.h b/ts2phc.h index 7bdd65bdf572..f0ffa43287be 100644 --- a/ts2phc.h +++ b/ts2phc.h @@ -48,6 +48,7 @@ struct ts2phc_private { STAILQ_HEAD(sink_ifaces_head, ts2phc_pps_sink) sinks; unsigned int n_sinks; struct ts2phc_sink_array *polling_array; + tmv_t perout_phase; struct config *cfg; struct pmc_agent *agent; struct ts2phc_clock *ref_clock; diff --git a/ts2phc_phc_pps_source.c b/ts2phc_phc_pps_source.c index f2a9c6196a45..3201888c791b 100644 --- a/ts2phc_phc_pps_source.c +++ b/ts2phc_phc_pps_source.c @@ -28,7 +28,10 @@ static int ts2phc_phc_pps_source_activate(struct config *cfg, const char *dev, { struct ptp_perout_request perout_request; struct ptp_pin_desc desc; + int32_t perout_phase; + int32_t pulsewidth; struct timespec ts; + int err; memset(&desc, 0, sizeof(desc)); @@ -45,17 +48,47 @@ static int ts2phc_phc_pps_source_activate(struct config *cfg, const char *dev, perror("clock_gettime"); return -1; } + perout_phase = config_get_int(cfg, dev, "ts2phc.perout_phase"); memset(&perout_request, 0, sizeof(perout_request)); perout_request.index = s->channel; - perout_request.start.sec = ts.tv_sec + 2; - perout_request.start.nsec = 0; perout_request.period.sec = 1; perout_request.period.nsec = 0; + perout_request.flags = 0; + pulsewidth = config_get_int(cfg, dev, "ts2phc.pulsewidth"); + if (pulsewidth) { + perout_request.flags |= PTP_PEROUT_DUTY_CYCLE; + perout_request.on.sec = pulsewidth / NS_PER_SEC; + perout_request.on.nsec = pulsewidth % NS_PER_SEC; + } + if (perout_phase != -1) { + perout_request.flags |= PTP_PEROUT_PHASE; + perout_request.phase.sec = perout_phase / NS_PER_SEC; + perout_request.phase.nsec = perout_phase % NS_PER_SEC; + } else { + perout_request.start.sec = ts.tv_sec + 2; + perout_request.start.nsec = 0; + } - if (ioctl(s->clock->fd, PTP_PEROUT_REQUEST2, &perout_request)) { + err = ioctl(s->clock->fd, PTP_PEROUT_REQUEST2, &perout_request); + if (err) { + /* Backwards compatibility with old ts2phc where the pulsewidth + * property would be just informative (a way to filter out + * events in the case that the PPS sink can only do extts on + * both rising and falling edges). There, nothing would be + * configured on the PHC PPS source towards achieving that + * pulsewidth. So in case the ioctl failed, try again with the + * DUTY_CYCLE flag unset, in an attempt to avoid a hard + * failure. + */ + perout_request.flags &= ~PTP_PEROUT_DUTY_CYCLE; + memset(&perout_request.rsv, 0, 4 * sizeof(unsigned int)); + err = ioctl(s->clock->fd, PTP_PEROUT_REQUEST2, &perout_request); + } + if (err) { pr_err(PTP_PEROUT_REQUEST_FAILED); - return -1; + return err; } + return 0; } diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c index b0ac6da375ae..d25bc8990863 100644 --- a/ts2phc_pps_sink.c +++ b/ts2phc_pps_sink.c @@ -236,6 +236,19 @@ static void ts2phc_pps_sink_destroy(struct ts2phc_pps_sink *sink) free(sink); } +static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv, + struct ts2phc_pps_sink *sink, + struct timespec source_ts) +{ + tmv_t source_tmv = timespec_to_tmv(source_ts); + + 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; +} + static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv, struct ts2phc_pps_sink *sink) { @@ -264,8 +277,7 @@ static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv, } if (sink->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE) && - source_ts.tv_nsec > sink->ignore_lower && - source_ts.tv_nsec < sink->ignore_upper) { + 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, -- 2.34.1 _______________________________________________ Linuxptp-devel mailing list Linuxptp-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linuxptp-devel