ts2phc_phc_master: make use of new kernel API for perout waveform

This API was introduced for 2 reasons:

1. Some hardware can emit PPS signals but not starting from arbitrary
   absolute times, but rather phase-aligned to the beginning of a
   second. We _could_ patch ts2phc to always specify a start time of
   0.000000000 to PTP_PEROUT_REQUEST, but in practice, we would never
   know whether that would actually work with all in-tree PHC drivers.
   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 master PHC 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 <olteanv@gmail.com>
master
Vladimir Oltean 2020-05-25 10:57:44 +03:00
parent d5b3b1e07b
commit f078f19339
7 changed files with 150 additions and 32 deletions

View File

@ -314,6 +314,7 @@ struct config_item config_tab[] = {
GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""), GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""),
GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""), GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""),
GLOB_ITEM_STR("ts2phc.nmea_serialport", "/dev/ttyS0"), 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), PORT_ITEM_INT("ts2phc.pin_index", 0, 0, INT_MAX),
GLOB_ITEM_INT("ts2phc.pulsewidth", 500000000, 1000000, 999000000), GLOB_ITEM_INT("ts2phc.pulsewidth", 500000000, 1000000, 999000000),
PORT_ITEM_ENU("tsproc_mode", TSPROC_FILTER, tsproc_enu), PORT_ITEM_ENU("tsproc_mode", TSPROC_FILTER, tsproc_enu),

View File

@ -97,6 +97,59 @@ struct compat_ptp_clock_caps {
#endif /*LINUX_VERSION_CODE < 5.8*/ #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)
/* from upcoming Linux kernel version 5.9 */
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.8*/
#ifndef PTP_MAX_SAMPLES #ifndef PTP_MAX_SAMPLES
#define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */ #define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */
#endif /* PTP_MAX_SAMPLES */ #endif /* PTP_MAX_SAMPLES */

View File

@ -167,6 +167,11 @@ specified, then the given remote connection will be used in preference
to the configured serial port. to the configured serial port.
The default is "/dev/ttyS0". The default is "/dev/ttyS0".
.TP .TP
.B ts2phc.perout_phase
Configures the offset between the beginning of the second and the PPS
master's rising edge. Available only for a PHC master. The supported
range is 0 to 999999999 nanoseconds. The default is 0 nanoseconds.
.TP
.B ts2phc.pulsewidth .B ts2phc.pulsewidth
The expected pulse width of the external PPS signal in nanoseconds. The expected pulse width of the external PPS signal in nanoseconds.
When 'ts2phc.extts_polarity' is "both", the given pulse width is used When 'ts2phc.extts_polarity' is "both", the given pulse width is used

View File

@ -402,6 +402,40 @@ static void ts2phc_reconfigure(struct ts2phc_private *priv)
pr_info("selecting %s as the source clock", src->name); pr_info("selecting %s as the source clock", src->name);
} }
static int ts2phc_approximate_master_tstamp(struct ts2phc_private *priv,
tmv_t *master_tmv)
{
struct timespec master_ts;
tmv_t tmv;
int err;
err = ts2phc_master_getppstime(priv->master, &master_ts);
if (err < 0) {
pr_err("master ts not valid");
return err;
}
tmv = timespec_to_tmv(master_ts);
tmv = tmv_sub(tmv, priv->perout_phase);
master_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 (master_ts.tv_nsec > NS_PER_SEC / 2)
master_ts.tv_sec++;
master_ts.tv_nsec = 0;
tmv = timespec_to_tmv(master_ts);
tmv = tmv_add(tmv, priv->perout_phase);
*master_tmv = tmv;
return 0;
}
static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
{ {
tmv_t source_tmv; tmv_t source_tmv;
@ -420,19 +454,10 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
return; return;
} }
} else { } else {
struct timespec source_ts; err = ts2phc_approximate_master_tstamp(priv, &source_tmv);
if (err < 0)
err = ts2phc_master_getppstime(priv->master, &source_ts);
if (err < 0) {
pr_err("source ts not valid");
return; 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) { LIST_FOREACH(c, &priv->clocks, list) {
int64_t offset; int64_t offset;
@ -480,7 +505,7 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
static int ts2phc_collect_master_tstamp(struct ts2phc_private *priv) static int ts2phc_collect_master_tstamp(struct ts2phc_private *priv)
{ {
struct clock *master_clock; struct clock *master_clock;
struct timespec master_ts; tmv_t master_tmv;
int err; int err;
master_clock = ts2phc_master_get_clock(priv->master); master_clock = ts2phc_master_get_clock(priv->master);
@ -493,22 +518,11 @@ static int ts2phc_collect_master_tstamp(struct ts2phc_private *priv)
if (!master_clock) if (!master_clock)
return 0; return 0;
err = ts2phc_master_getppstime(priv->master, &master_ts); err = ts2phc_approximate_master_tstamp(priv, &master_tmv);
if (err < 0) { if (err < 0)
pr_err("source ts not valid");
return err; return err;
}
/* clock_add_tstamp(master_clock, master_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 (master_ts.tv_nsec > NS_PER_SEC / 2)
master_ts.tv_sec++;
master_ts.tv_nsec = 0;
clock_add_tstamp(master_clock, timespec_to_tmv(master_ts));
return 0; return 0;
} }
@ -657,13 +671,29 @@ int main(int argc, char *argv[])
} }
STAILQ_FOREACH(iface, &cfg->interfaces, list) { 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) { if (pps_source) {
fprintf(stderr, "too many PPS sources\n"); fprintf(stderr, "too many PPS sources\n");
ts2phc_cleanup(&priv); ts2phc_cleanup(&priv);
return -1; 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 { } else {
if (ts2phc_slave_add(&priv, interface_name(iface))) { if (ts2phc_slave_add(&priv, interface_name(iface))) {
fprintf(stderr, "failed to add slave\n"); fprintf(stderr, "failed to add slave\n");

View File

@ -44,6 +44,7 @@ struct ts2phc_private {
STAILQ_HEAD(slave_ifaces_head, ts2phc_slave) slaves; STAILQ_HEAD(slave_ifaces_head, ts2phc_slave) slaves;
unsigned int n_slaves; unsigned int n_slaves;
struct ts2phc_slave_array *polling_array; struct ts2phc_slave_array *polling_array;
tmv_t perout_phase;
struct config *cfg; struct config *cfg;
struct pmc_node node; struct pmc_node node;
int state_changed; int state_changed;

View File

@ -27,6 +27,8 @@ static int ts2phc_phc_master_activate(struct config *cfg, const char *dev,
{ {
struct ptp_perout_request perout_request; struct ptp_perout_request perout_request;
struct ptp_pin_desc desc; struct ptp_pin_desc desc;
int32_t perout_phase;
int32_t pulsewidth;
struct timespec ts; struct timespec ts;
memset(&desc, 0, sizeof(desc)); memset(&desc, 0, sizeof(desc));
@ -44,12 +46,26 @@ static int ts2phc_phc_master_activate(struct config *cfg, const char *dev,
perror("clock_gettime"); perror("clock_gettime");
return -1; return -1;
} }
perout_phase = config_get_int(cfg, dev, "ts2phc.perout_phase");
memset(&perout_request, 0, sizeof(perout_request)); memset(&perout_request, 0, sizeof(perout_request));
perout_request.index = master->channel; perout_request.index = master->channel;
perout_request.start.sec = ts.tv_sec + 2;
perout_request.start.nsec = 0;
perout_request.period.sec = 1; perout_request.period.sec = 1;
perout_request.period.nsec = 0; 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(master->clock->clkid), PTP_PEROUT_REQUEST2, if (ioctl(CLOCKID_TO_FD(master->clock->clkid), PTP_PEROUT_REQUEST2,
&perout_request)) { &perout_request)) {

View File

@ -241,6 +241,19 @@ static void ts2phc_slave_destroy(struct ts2phc_slave *slave)
free(slave); free(slave);
} }
static bool ts2phc_slave_ignore(struct ts2phc_private *priv,
struct ts2phc_slave *slave,
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 > slave->ignore_lower &&
source_ts.tv_nsec < slave->ignore_upper;
}
static enum extts_result ts2phc_slave_event(struct ts2phc_private *priv, static enum extts_result ts2phc_slave_event(struct ts2phc_private *priv,
struct ts2phc_slave *slave) struct ts2phc_slave *slave)
{ {
@ -269,8 +282,7 @@ static enum extts_result ts2phc_slave_event(struct ts2phc_private *priv,
} }
if (slave->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE) && if (slave->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE) &&
source_ts.tv_nsec > slave->ignore_lower && ts2phc_slave_ignore(priv, slave, source_ts)) {
source_ts.tv_nsec < slave->ignore_upper) {
pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld", pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld",
slave->name, event.index, event.t.sec, event.t.nsec, slave->name, event.index, event.t.sec, event.t.nsec,