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 approximation 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>
---
v4->v5: rebase on top of variable renames
v3->v4: patch is new.

 config.c                |  1 +
 missing.h               | 52 +++++++++++++++++++++++++
 ts2phc.8                | 17 +++++++-
 ts2phc.c                | 86 +++++++++++++++++++++++++++--------------
 ts2phc.h                |  1 +
 ts2phc_phc_pps_source.c | 44 ++++++++++++++++++---
 ts2phc_pps_sink.c       | 16 +++++++-
 7 files changed, 180 insertions(+), 37 deletions(-)

diff --git a/config.c b/config.c
index f3c52baff765..760d0e12b0b6 100644
--- a/config.c
+++ b/config.c
@@ -321,6 +321,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 89cb51360ef7..7f06da3220f2 100644
--- a/missing.h
+++ b/missing.h
@@ -97,6 +97,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 25c8bb3a5fa6..de7445830dbb 100644
--- a/ts2phc.c
+++ b/ts2phc.c
@@ -407,6 +407,40 @@ static void ts2phc_reconfigure(struct ts2phc_private *priv)
        pr_info("selecting %s as the reference clock", ref_clk->name);
 }
 
+static int ts2phc_approximate_pps_source_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
+        * a precise perout timestamp, we'll have to use this crude
+        * approximation.
+        */
+       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;
@@ -425,18 +459,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_approximate_pps_source_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) {
@@ -485,7 +510,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);
@@ -498,22 +523,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_approximate_pps_source_tstamp(priv, &source_tmv);
+       if (err < 0)
                return err;
-       }
 
-       /*
-        * As long as the kernel doesn't support a proper API for reporting
-        * a precise perout timestamp, we'll have to use this crude
-        * approximation.
-        */
-       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;
 }
@@ -667,13 +681,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 2766b7b2c9d1..63c630bb9dcb 100644
--- a/ts2phc.h
+++ b/ts2phc.h
@@ -44,6 +44,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 d9e00f3b525d..952b96b544b2 100644
--- a/ts2phc_phc_pps_source.c
+++ b/ts2phc_phc_pps_source.c
@@ -27,7 +27,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));
 
@@ -44,18 +47,49 @@ 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(CLOCKID_TO_FD(s->clock->clkid), PTP_PEROUT_REQUEST2,
-                 &perout_request)) {
+       err = ioctl(CLOCKID_TO_FD(s->clock->clkid), 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(CLOCKID_TO_FD(s->clock->clkid),
+                           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 678ff7e3bb90..30b348df9bde 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -237,6 +237,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)
 {
@@ -265,8 +278,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.25.1



_______________________________________________
Linuxptp-devel mailing list
Linuxptp-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxptp-devel

Reply via email to