2019-07-08 07:47:48 +08:00
|
|
|
/**
|
|
|
|
* @file ts2phc.c
|
|
|
|
* @brief Utility program to synchronize the PHC clock to external events
|
|
|
|
* @note Copyright (C) 2013 Balint Ferencz <fernya@sch.bme.hu>
|
|
|
|
* @note Based on the phc2sys utility
|
|
|
|
* @note Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com>
|
|
|
|
* @note SPDX-License-Identifier: GPL-2.0+
|
|
|
|
*/
|
|
|
|
#include <stdlib.h>
|
2020-07-28 07:51:28 +08:00
|
|
|
#include <net/if.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
2019-07-08 07:47:48 +08:00
|
|
|
|
2020-07-28 07:51:28 +08:00
|
|
|
#include "clockadj.h"
|
2019-07-08 07:47:48 +08:00
|
|
|
#include "config.h"
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
#include "contain.h"
|
2019-07-08 07:47:48 +08:00
|
|
|
#include "interface.h"
|
2020-07-28 07:51:28 +08:00
|
|
|
#include "phc.h"
|
2019-07-08 07:47:48 +08:00
|
|
|
#include "print.h"
|
2020-06-29 03:02:38 +08:00
|
|
|
#include "ts2phc.h"
|
2019-07-08 07:47:48 +08:00
|
|
|
#include "version.h"
|
|
|
|
|
2020-08-01 22:45:00 +08:00
|
|
|
#define NS_PER_SEC 1000000000LL
|
|
|
|
#define SAMPLE_WEIGHT 1.0
|
|
|
|
|
2019-07-08 07:47:48 +08:00
|
|
|
struct interface {
|
|
|
|
STAILQ_ENTRY(interface) list;
|
|
|
|
};
|
|
|
|
|
2020-06-29 03:02:38 +08:00
|
|
|
static void ts2phc_cleanup(struct ts2phc_private *priv)
|
2019-07-08 07:47:48 +08:00
|
|
|
{
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
struct port *p, *tmp;
|
|
|
|
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_slave_cleanup(priv);
|
|
|
|
if (priv->master)
|
|
|
|
ts2phc_master_destroy(priv->master);
|
|
|
|
if (priv->cfg)
|
|
|
|
config_destroy(priv->cfg);
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
|
|
|
|
close_pmc_node(&priv->node);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clocks are destroyed by the cleanup methods of the individual
|
|
|
|
* master and slave PHC modules.
|
|
|
|
*/
|
|
|
|
LIST_FOREACH_SAFE(p, &priv->ports, list, tmp)
|
|
|
|
free(p);
|
|
|
|
|
|
|
|
msg_cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Copied from phc2sys */
|
|
|
|
static int normalize_state(int state)
|
|
|
|
{
|
|
|
|
if (state != PS_MASTER && state != PS_SLAVE &&
|
|
|
|
state != PS_PRE_MASTER && state != PS_UNCALIBRATED) {
|
|
|
|
/* treat any other state as "not a master nor a slave" */
|
|
|
|
state = PS_DISABLED;
|
|
|
|
}
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Copied from phc2sys */
|
|
|
|
static struct port *port_get(struct ts2phc_private *priv, unsigned int number)
|
|
|
|
{
|
|
|
|
struct port *p;
|
|
|
|
|
|
|
|
LIST_FOREACH(p, &priv->ports, list) {
|
|
|
|
if (p->number == number)
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Copied from phc2sys */
|
|
|
|
static int clock_compute_state(struct ts2phc_private *priv,
|
|
|
|
struct clock *clock)
|
|
|
|
{
|
|
|
|
int state = PS_DISABLED;
|
|
|
|
struct port *p;
|
|
|
|
|
|
|
|
LIST_FOREACH(p, &priv->ports, list) {
|
|
|
|
if (p->clock != clock)
|
|
|
|
continue;
|
|
|
|
/* PS_SLAVE takes the highest precedence, PS_UNCALIBRATED
|
|
|
|
* after that, PS_MASTER is third, PS_PRE_MASTER fourth and
|
|
|
|
* all of that overrides PS_DISABLED, which corresponds
|
|
|
|
* nicely with the numerical values */
|
|
|
|
if (p->state > state)
|
|
|
|
state = p->state;
|
|
|
|
}
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define node_to_ts2phc(node) \
|
|
|
|
container_of(node, struct ts2phc_private, node)
|
|
|
|
|
|
|
|
static int ts2phc_recv_subscribed(struct pmc_node *node,
|
|
|
|
struct ptp_message *msg, int excluded)
|
|
|
|
{
|
|
|
|
struct ts2phc_private *priv = node_to_ts2phc(node);
|
|
|
|
int mgt_id, state;
|
|
|
|
struct portDS *pds;
|
|
|
|
struct port *port;
|
|
|
|
struct clock *clock;
|
|
|
|
|
|
|
|
mgt_id = get_mgt_id(msg);
|
|
|
|
if (mgt_id == excluded)
|
|
|
|
return 0;
|
|
|
|
switch (mgt_id) {
|
|
|
|
case TLV_PORT_DATA_SET:
|
|
|
|
pds = get_mgt_data(msg);
|
|
|
|
port = port_get(priv, pds->portIdentity.portNumber);
|
|
|
|
if (!port) {
|
|
|
|
pr_info("received data for unknown port %s",
|
|
|
|
pid2str(&pds->portIdentity));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
state = normalize_state(pds->portState);
|
|
|
|
if (port->state != state) {
|
|
|
|
pr_info("port %s changed state",
|
|
|
|
pid2str(&pds->portIdentity));
|
|
|
|
port->state = state;
|
|
|
|
clock = port->clock;
|
|
|
|
state = clock_compute_state(priv, clock);
|
|
|
|
if (clock->state != state || clock->new_state) {
|
|
|
|
clock->new_state = state;
|
|
|
|
priv->state_changed = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2019-07-08 07:47:48 +08:00
|
|
|
}
|
|
|
|
|
2020-07-28 07:51:28 +08:00
|
|
|
struct servo *servo_add(struct ts2phc_private *priv, struct clock *clock)
|
|
|
|
{
|
|
|
|
enum servo_type type = config_get_int(priv->cfg, NULL, "clock_servo");
|
|
|
|
struct servo *servo;
|
|
|
|
int fadj, max_adj;
|
|
|
|
|
|
|
|
fadj = (int) clockadj_get_freq(clock->clkid);
|
|
|
|
/* Due to a bug in older kernels, the reading may silently fail
|
|
|
|
and return 0. Set the frequency back to make sure fadj is
|
|
|
|
the actual frequency of the clock. */
|
|
|
|
clockadj_set_freq(clock->clkid, fadj);
|
|
|
|
|
|
|
|
max_adj = phc_max_adj(clock->clkid);
|
|
|
|
|
|
|
|
servo = servo_create(priv->cfg, type, -fadj, max_adj, 0);
|
|
|
|
if (!servo)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
servo_sync_interval(servo, SERVO_SYNC_INTERVAL);
|
|
|
|
|
|
|
|
return servo;
|
|
|
|
}
|
|
|
|
|
2020-08-01 22:45:00 +08:00
|
|
|
void clock_add_tstamp(struct clock *clock, tmv_t t)
|
|
|
|
{
|
|
|
|
struct timespec ts = tmv_to_timespec(t);
|
|
|
|
|
|
|
|
pr_debug("adding tstamp %ld.%09ld to clock %s",
|
|
|
|
ts.tv_sec, ts.tv_nsec, clock->name);
|
|
|
|
clock->last_ts = t;
|
|
|
|
clock->is_ts_available = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int clock_get_tstamp(struct clock *clock, tmv_t *ts)
|
|
|
|
{
|
|
|
|
if (!clock->is_ts_available)
|
|
|
|
return 0;
|
|
|
|
clock->is_ts_available = 0;
|
|
|
|
*ts = clock->last_ts;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void clock_flush_tstamp(struct clock *clock)
|
|
|
|
{
|
|
|
|
clock->is_ts_available = 0;
|
|
|
|
}
|
|
|
|
|
2020-07-28 07:51:28 +08:00
|
|
|
struct clock *clock_add(struct ts2phc_private *priv, const char *device)
|
|
|
|
{
|
|
|
|
clockid_t clkid = CLOCK_INVALID;
|
|
|
|
int phc_index = -1;
|
|
|
|
struct clock *c;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
clkid = posix_clock_open(device, &phc_index);
|
|
|
|
if (clkid == CLOCK_INVALID)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
LIST_FOREACH(c, &priv->clocks, list) {
|
|
|
|
if (c->phc_index == phc_index) {
|
|
|
|
/* Already have the clock, don't add it again */
|
|
|
|
posix_clock_close(clkid);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c = calloc(1, sizeof(*c));
|
|
|
|
if (!c) {
|
|
|
|
pr_err("failed to allocate memory for a clock");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
c->clkid = clkid;
|
|
|
|
c->phc_index = phc_index;
|
|
|
|
c->servo_state = SERVO_UNLOCKED;
|
|
|
|
c->servo = servo_add(priv, c);
|
|
|
|
c->no_adj = config_get_int(priv->cfg, NULL, "free_running");
|
|
|
|
err = asprintf(&c->name, "/dev/ptp%d", phc_index);
|
|
|
|
if (err < 0) {
|
|
|
|
free(c);
|
|
|
|
posix_clock_close(clkid);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
LIST_INSERT_HEAD(&priv->clocks, c, list);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
|
2020-07-28 07:51:28 +08:00
|
|
|
void clock_destroy(struct clock *c)
|
|
|
|
{
|
|
|
|
servo_destroy(c->servo);
|
|
|
|
posix_clock_close(c->clkid);
|
|
|
|
free(c->name);
|
|
|
|
free(c);
|
|
|
|
}
|
|
|
|
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
/* FIXME: Copied from phc2sys */
|
|
|
|
static struct port *port_add(struct ts2phc_private *priv, unsigned int number,
|
|
|
|
char *device)
|
|
|
|
{
|
|
|
|
struct clock *c = NULL;
|
|
|
|
struct port *p, *tmp;
|
|
|
|
|
|
|
|
p = port_get(priv, number);
|
|
|
|
if (p)
|
|
|
|
return p;
|
|
|
|
/* port is a new one, look whether we have the device already on
|
|
|
|
* a different port */
|
|
|
|
LIST_FOREACH(tmp, &priv->ports, list) {
|
|
|
|
if (tmp->number == number) {
|
|
|
|
c = tmp->clock;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!c) {
|
|
|
|
c = clock_add(priv, device);
|
|
|
|
if (!c)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
p = malloc(sizeof(*p));
|
|
|
|
if (!p) {
|
|
|
|
pr_err("failed to allocate memory for a port");
|
|
|
|
clock_destroy(c);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
p->number = number;
|
|
|
|
p->clock = c;
|
|
|
|
LIST_INSERT_HEAD(&priv->ports, p, list);
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int auto_init_ports(struct ts2phc_private *priv)
|
|
|
|
{
|
|
|
|
int state, timestamping;
|
|
|
|
int number_ports, res;
|
|
|
|
char iface[IFNAMSIZ];
|
|
|
|
struct clock *clock;
|
|
|
|
struct port *port;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
if (!is_running())
|
|
|
|
return -1;
|
|
|
|
res = run_pmc_clock_identity(&priv->node, 1000);
|
|
|
|
if (res < 0)
|
|
|
|
return -1;
|
|
|
|
if (res > 0)
|
|
|
|
break;
|
|
|
|
/* res == 0, timeout */
|
|
|
|
pr_notice("Waiting for ptp4l...");
|
|
|
|
}
|
|
|
|
|
|
|
|
number_ports = run_pmc_get_number_ports(&priv->node, 1000);
|
|
|
|
if (number_ports <= 0) {
|
|
|
|
pr_err("failed to get number of ports");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = run_pmc_subscribe(&priv->node, 1000);
|
|
|
|
if (res <= 0) {
|
|
|
|
pr_err("failed to subscribe");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 1; i <= number_ports; i++) {
|
|
|
|
res = run_pmc_port_properties(&priv->node, 1000, i, &state,
|
|
|
|
×tamping, iface);
|
|
|
|
if (res == -1) {
|
|
|
|
/* port does not exist, ignore the port */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (res <= 0) {
|
|
|
|
pr_err("failed to get port properties");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (timestamping == TS_SOFTWARE) {
|
|
|
|
/* ignore ports with software time stamping */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
port = port_add(priv, i, iface);
|
|
|
|
if (!port)
|
|
|
|
return -1;
|
|
|
|
port->state = normalize_state(state);
|
|
|
|
}
|
|
|
|
if (LIST_EMPTY(&priv->clocks)) {
|
|
|
|
pr_err("no suitable ports available");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
LIST_FOREACH(clock, &priv->clocks, list) {
|
|
|
|
clock->new_state = clock_compute_state(priv, clock);
|
|
|
|
}
|
|
|
|
priv->state_changed = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:01:16 +08:00
|
|
|
static void ts2phc_reconfigure(struct ts2phc_private *priv)
|
|
|
|
{
|
|
|
|
struct clock *c, *src = NULL, *last = NULL;
|
|
|
|
int src_cnt = 0, dst_cnt = 0;
|
|
|
|
|
|
|
|
pr_info("reconfiguring after port state change");
|
|
|
|
priv->state_changed = 0;
|
|
|
|
|
|
|
|
LIST_FOREACH(c, &priv->clocks, list) {
|
|
|
|
if (c->new_state) {
|
|
|
|
c->state = c->new_state;
|
|
|
|
c->new_state = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (c->state) {
|
|
|
|
case PS_FAULTY:
|
|
|
|
case PS_DISABLED:
|
|
|
|
case PS_LISTENING:
|
|
|
|
case PS_PRE_MASTER:
|
|
|
|
case PS_MASTER:
|
|
|
|
case PS_PASSIVE:
|
|
|
|
if (!c->is_destination) {
|
|
|
|
pr_info("selecting %s for synchronization",
|
|
|
|
c->name);
|
|
|
|
c->is_destination = 1;
|
|
|
|
}
|
|
|
|
dst_cnt++;
|
|
|
|
break;
|
|
|
|
case PS_UNCALIBRATED:
|
|
|
|
src_cnt++;
|
|
|
|
break;
|
|
|
|
case PS_SLAVE:
|
|
|
|
src = c;
|
|
|
|
src_cnt++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
last = c;
|
|
|
|
}
|
|
|
|
if (dst_cnt >= 1 && !src) {
|
|
|
|
priv->source = last;
|
|
|
|
priv->source->is_destination = 0;
|
|
|
|
/* Reset to original state in next reconfiguration. */
|
|
|
|
priv->source->new_state = priv->source->state;
|
|
|
|
priv->source->state = PS_SLAVE;
|
|
|
|
pr_info("no source, selecting %s as the default clock",
|
|
|
|
last->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (src_cnt > 1) {
|
|
|
|
pr_info("multiple source clocks available, postponing sync...");
|
|
|
|
priv->source = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (src_cnt > 0 && !src) {
|
|
|
|
pr_info("source clock not ready, waiting...");
|
|
|
|
priv->source = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!src_cnt && !dst_cnt) {
|
|
|
|
pr_info("no PHC ready, waiting...");
|
|
|
|
priv->source = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!src_cnt) {
|
|
|
|
pr_info("nothing to synchronize");
|
|
|
|
priv->source = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
src->is_destination = 0;
|
|
|
|
priv->source = src;
|
|
|
|
pr_info("selecting %s as the source clock", src->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
|
2020-08-01 22:45:00 +08:00
|
|
|
{
|
|
|
|
tmv_t source_tmv;
|
|
|
|
struct clock *c;
|
|
|
|
int valid, err;
|
|
|
|
|
2020-07-28 08:01:16 +08:00
|
|
|
if (autocfg) {
|
|
|
|
if (!priv->source) {
|
|
|
|
pr_debug("no source, skipping");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
valid = clock_get_tstamp(priv->source, &source_tmv);
|
|
|
|
if (!valid) {
|
|
|
|
pr_err("source clock (%s) timestamp not valid, skipping",
|
|
|
|
priv->source->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
struct timespec source_ts;
|
2020-08-01 22:45:00 +08:00
|
|
|
|
2020-07-28 08:01:16 +08:00
|
|
|
err = ts2phc_master_getppstime(priv->master, &source_ts);
|
|
|
|
if (err < 0) {
|
|
|
|
pr_err("source ts not valid");
|
|
|
|
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);
|
|
|
|
}
|
2020-08-01 22:45:00 +08:00
|
|
|
|
|
|
|
LIST_FOREACH(c, &priv->clocks, list) {
|
|
|
|
int64_t offset;
|
|
|
|
double adj;
|
|
|
|
tmv_t ts;
|
|
|
|
|
2020-07-28 08:01:16 +08:00
|
|
|
if (!c->is_destination)
|
|
|
|
continue;
|
|
|
|
|
2020-08-01 22:45:00 +08:00
|
|
|
valid = clock_get_tstamp(c, &ts);
|
|
|
|
if (!valid) {
|
|
|
|
pr_debug("%s timestamp not valid, skipping", c->name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = tmv_to_nanoseconds(tmv_sub(ts, source_tmv));
|
|
|
|
|
|
|
|
if (c->no_adj) {
|
|
|
|
pr_info("%s offset %10" PRId64, c->name,
|
|
|
|
offset);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
switch (c->servo_state) {
|
|
|
|
case SERVO_UNLOCKED:
|
|
|
|
break;
|
|
|
|
case SERVO_JUMP:
|
|
|
|
clockadj_set_freq(c->clkid, -adj);
|
|
|
|
clockadj_step(c->clkid, -offset);
|
|
|
|
break;
|
|
|
|
case SERVO_LOCKED:
|
|
|
|
case SERVO_LOCKED_STABLE:
|
|
|
|
clockadj_set_freq(c->clkid, -adj);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-01 19:28:50 +08:00
|
|
|
static int ts2phc_collect_master_tstamp(struct ts2phc_private *priv)
|
|
|
|
{
|
|
|
|
struct clock *master_clock;
|
|
|
|
struct timespec master_ts;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
master_clock = ts2phc_master_get_clock(priv->master);
|
|
|
|
/*
|
|
|
|
* Master isn't a PHC (it may be a generic or a GPS master),
|
|
|
|
* don't error out, just don't do anything. If it doesn't have a PHC,
|
|
|
|
* there is nothing to synchronize, which is the only point of
|
|
|
|
* collecting its perout timestamp in the first place.
|
|
|
|
*/
|
|
|
|
if (!master_clock)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err = ts2phc_master_getppstime(priv->master, &master_ts);
|
|
|
|
if (err < 0) {
|
|
|
|
pr_err("source ts not valid");
|
|
|
|
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 (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;
|
|
|
|
}
|
|
|
|
|
2019-07-08 07:47:48 +08:00
|
|
|
static void usage(char *progname)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"\n"
|
|
|
|
"usage: %s [options]\n\n"
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
" -a turn on autoconfiguration\n"
|
2019-07-08 07:47:48 +08:00
|
|
|
" -c [dev|name] phc slave clock (like /dev/ptp0 or eth0)\n"
|
|
|
|
" (may be specified multiple times)\n"
|
|
|
|
" -f [file] read configuration from 'file'\n"
|
|
|
|
" -h prints this message and exits\n"
|
|
|
|
" -l [num] set the logging level to 'num'\n"
|
|
|
|
" -m print messages to stdout\n"
|
|
|
|
" -q do not print messages to the syslog\n"
|
|
|
|
" -s [dev|name] source of the PPS signal\n"
|
|
|
|
" may take any of the following forms:\n"
|
|
|
|
" generic - an external 1-PPS without ToD information\n"
|
2019-12-23 04:51:43 +08:00
|
|
|
" /dev/ptp0 - a local PTP Hardware Clock (PHC)\n"
|
|
|
|
" eth0 - a local PTP Hardware Clock (PHC)\n"
|
2019-12-30 03:12:32 +08:00
|
|
|
" nmea - a gps device connected by serial port or network\n"
|
2019-07-08 07:47:48 +08:00
|
|
|
" -v prints the software version and exits\n"
|
|
|
|
"\n",
|
|
|
|
progname);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int c, err = 0, have_slave = 0, index, print_level;
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
char uds_local[MAX_IFNAME_SIZE + 1];
|
2019-07-08 07:47:48 +08:00
|
|
|
enum ts2phc_master_type pps_type;
|
2020-06-29 03:02:38 +08:00
|
|
|
struct ts2phc_private priv = {0};
|
2019-07-08 07:47:48 +08:00
|
|
|
char *config = NULL, *progname;
|
|
|
|
const char *pps_source = NULL;
|
|
|
|
struct config *cfg = NULL;
|
|
|
|
struct interface *iface;
|
|
|
|
struct option *opts;
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
int autocfg = 0;
|
2019-07-08 07:47:48 +08:00
|
|
|
|
|
|
|
handle_term_signals();
|
|
|
|
|
|
|
|
cfg = config_create();
|
|
|
|
if (!cfg) {
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = config_long_options(cfg);
|
|
|
|
|
|
|
|
/* Process the command line arguments. */
|
|
|
|
progname = strrchr(argv[0], '/');
|
|
|
|
progname = progname ? 1 + progname : argv[0];
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
while (EOF != (c = getopt_long(argc, argv, "ac:f:hi:l:mqs:v", opts, &index))) {
|
2019-07-08 07:47:48 +08:00
|
|
|
switch (c) {
|
|
|
|
case 0:
|
|
|
|
if (config_parse_option(cfg, opts[index].name, optarg)) {
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
case 'a':
|
|
|
|
autocfg = 1;
|
|
|
|
break;
|
2019-07-08 07:47:48 +08:00
|
|
|
case 'c':
|
|
|
|
if (!config_create_interface(optarg, cfg)) {
|
|
|
|
fprintf(stderr, "failed to add slave\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
have_slave = 1;
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
config = optarg;
|
|
|
|
break;
|
|
|
|
case 'l':
|
|
|
|
if (get_arg_val_i(c, optarg, &print_level,
|
|
|
|
PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) {
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
config_set_int(cfg, "logging_level", print_level);
|
|
|
|
print_set_level(print_level);
|
|
|
|
break;
|
|
|
|
case 'm':
|
|
|
|
config_set_int(cfg, "verbose", 1);
|
|
|
|
print_set_verbose(1);
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
config_set_int(cfg, "use_syslog", 0);
|
|
|
|
print_set_syslog(0);
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
if (pps_source) {
|
|
|
|
fprintf(stderr, "too many PPS sources\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pps_source = optarg;
|
|
|
|
break;
|
|
|
|
case 'v':
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
version_show(stdout);
|
|
|
|
return 0;
|
|
|
|
case 'h':
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
usage(progname);
|
|
|
|
return -1;
|
|
|
|
case '?':
|
|
|
|
default:
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
usage(progname);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (config && (c = config_read(config, cfg))) {
|
|
|
|
fprintf(stderr, "failed to read config\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
print_set_progname(progname);
|
|
|
|
print_set_tag(config_get_string(cfg, NULL, "message_tag"));
|
|
|
|
print_set_verbose(config_get_int(cfg, NULL, "verbose"));
|
|
|
|
print_set_syslog(config_get_int(cfg, NULL, "use_syslog"));
|
|
|
|
print_set_level(config_get_int(cfg, NULL, "logging_level"));
|
|
|
|
|
2020-06-29 03:02:38 +08:00
|
|
|
STAILQ_INIT(&priv.slaves);
|
|
|
|
priv.cfg = cfg;
|
|
|
|
|
ts2phc: instantiate a pmc node
This introduces the '-a' option in ts2phc, an option inspired from
phc2sys that puts the clocks in "automatic" mode. In this mode, ts2phc
listens, as a PMC, to port state change events from ptp4l, and detects
which port state machine, if any, has transitioned to PS_SLAVE. That
port's clock will become the synchronization master for the hierarchy
described by ts2phc.
The use case is a multi-switch DSA setup with boundary_clock_jbod, where
there is only one grandmaster, connected to one switch's port. The other
switches, connected together through a PPS signal, must adapt themselves
to this new source of time, while the switch connected to the GM must
not be synchronized by ts2phc because it is already synchronized by
ptp4l.
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:48:06 +08:00
|
|
|
snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc.%d",
|
|
|
|
getpid());
|
|
|
|
|
|
|
|
if (autocfg) {
|
|
|
|
err = init_pmc_node(cfg, &priv.node, uds_local,
|
|
|
|
ts2phc_recv_subscribed);
|
|
|
|
if (err) {
|
|
|
|
ts2phc_cleanup(&priv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
err = auto_init_ports(&priv);
|
|
|
|
if (err) {
|
|
|
|
ts2phc_cleanup(&priv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-08 07:47:48 +08:00
|
|
|
STAILQ_FOREACH(iface, &cfg->interfaces, list) {
|
|
|
|
if (1 == config_get_int(cfg, interface_name(iface), "ts2phc.master")) {
|
|
|
|
if (pps_source) {
|
|
|
|
fprintf(stderr, "too many PPS sources\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pps_source = interface_name(iface);
|
|
|
|
} else {
|
2020-06-29 03:02:38 +08:00
|
|
|
if (ts2phc_slave_add(&priv, interface_name(iface))) {
|
2019-07-08 07:47:48 +08:00
|
|
|
fprintf(stderr, "failed to add slave\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
have_slave = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!have_slave) {
|
|
|
|
fprintf(stderr, "no slave clocks specified\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
usage(progname);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!pps_source) {
|
|
|
|
fprintf(stderr, "no PPS source specified\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
usage(progname);
|
|
|
|
return -1;
|
|
|
|
}
|
2020-06-29 03:02:38 +08:00
|
|
|
if (ts2phc_slaves_init(&priv)) {
|
|
|
|
fprintf(stderr, "failed to initialize slaves\n");
|
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-12-23 04:51:43 +08:00
|
|
|
if (!strcasecmp(pps_source, "generic")) {
|
|
|
|
pps_type = TS2PHC_MASTER_GENERIC;
|
2019-12-30 03:12:32 +08:00
|
|
|
} else if (!strcasecmp(pps_source, "nmea")) {
|
|
|
|
pps_type = TS2PHC_MASTER_NMEA;
|
2019-12-23 04:51:43 +08:00
|
|
|
} else {
|
|
|
|
pps_type = TS2PHC_MASTER_PHC;
|
|
|
|
}
|
ts2phc: instantiate a full clock structure for every PHC master
This propagates the use of "struct ts2phc_private" all the way into the
master API, in preparation of a new use case that will be supported
soon: some PPS masters (to be precise, the "PHC" kind) instantiate a
struct clock which could be disciplined by ts2phc.
When a PHC A emits a pulse and another PHC B timestamps it, the offset
between their precise timestamps can be used to synchronize either one
of them. So far in ts2phc, only the slave PHC (the one using extts) has
been synchronized to the master (the one using perout).
This is partly because there is no proper kernel API to report the
precise timestamp of a perout pulse. We only have the periodic API, and
that doesn't report precise timestamps either; we just use vague
approximations of what the PPS master PHC's time was, based on reading
that PHC immediately after a slave extts event was received by the
application. While this is far from ideal, it does work, and does allow
PHC A to be synchronized to B.
This is particularly useful with the yet-to-be-introduced "automatic"
mode of ts2phc (similar to '-a' of phc2sys), and the PPS distribution
tree is fixed in hardware (as opposed to port states, which in
"automatic" mode are dynamic, as the name suggests).
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
2020-07-28 07:52:16 +08:00
|
|
|
priv.master = ts2phc_master_create(&priv, pps_source, pps_type);
|
2020-06-29 03:02:38 +08:00
|
|
|
if (!priv.master) {
|
2019-07-08 07:47:48 +08:00
|
|
|
fprintf(stderr, "failed to create master\n");
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (is_running()) {
|
2020-08-01 22:45:00 +08:00
|
|
|
struct clock *c;
|
|
|
|
|
2020-07-28 08:01:16 +08:00
|
|
|
if (autocfg) {
|
|
|
|
/*
|
|
|
|
* Make sure ptp4l sees us as alive and doesn't prune
|
|
|
|
* us from the list of subscribers
|
|
|
|
*/
|
|
|
|
err = update_pmc_node(&priv.node, 1);
|
|
|
|
if (err < 0) {
|
|
|
|
pr_err("update_pmc_node returned %d", err);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
run_pmc_events(&priv.node);
|
|
|
|
if (priv.state_changed)
|
|
|
|
ts2phc_reconfigure(&priv);
|
|
|
|
}
|
|
|
|
|
2020-08-01 22:45:00 +08:00
|
|
|
LIST_FOREACH(c, &priv.clocks, list)
|
|
|
|
clock_flush_tstamp(c);
|
|
|
|
|
2020-06-29 03:02:38 +08:00
|
|
|
err = ts2phc_slave_poll(&priv);
|
2020-08-01 22:45:00 +08:00
|
|
|
if (err < 0) {
|
2019-07-08 07:47:48 +08:00
|
|
|
pr_err("poll failed");
|
|
|
|
break;
|
|
|
|
}
|
2020-08-01 19:28:50 +08:00
|
|
|
if (err > 0) {
|
|
|
|
err = ts2phc_collect_master_tstamp(&priv);
|
|
|
|
if (err) {
|
|
|
|
pr_err("failed to collect master tstamp");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:01:16 +08:00
|
|
|
ts2phc_synchronize_clocks(&priv, autocfg);
|
2020-08-01 19:28:50 +08:00
|
|
|
}
|
2019-07-08 07:47:48 +08:00
|
|
|
}
|
|
|
|
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return err;
|
|
|
|
}
|