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 reference for the clock 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 time reference, while the switch connected to the GM must not be synchronized by ts2phc because it is already synchronized by ptp4l. Graphically, the setup looks like this: +---------------------------------------------------------------+ | LS1028A /dev/ptp0 (unused) | | +------------------------------+ | | | DSA master for Felix | | | |(internal ENETC port 2: eno2))| | | +------------+------------------------------+-------------+ | | | Felix embedded L2 switch /dev/ptp1 | | | | PPS source, sync target | | | | +--------------+ +--------------+ +--------------+ | | | | |DSA master for| |DSA master for| |DSA master for| | | | | | SJA1105 1 | | SJA1105 2 | | SJA1105 3 | | | | | |(Felix port 1)| |(Felix port 2)| |(Felix port 3)| | | +--+-+--------------+---+--------------+---+--------------+--+--+ /dev/ptp2 /dev/ptp3 /dev/ptp4 PPS sink, sync reference PPS sink, sync target PPS sink, sync target +-----------------------+ +-----------------------+ +-----------------------+ | SJA1105 switch 1 | | SJA1105 switch 2 | | SJA1105 switch 3 | +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ |sw1p0|sw1p1|sw1p2|sw1p3| |sw2p0|sw2p1|sw2p2|sw2p3| |sw3p0|sw3p1|sw3p2|sw3p3| +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ ^ | GM connected here In the described state, the Felix embedded L2 switch is statically configured to emit a 1PPS signal on a sort of "simplex bus", and SJA1105 switches 1 to 3 are statically configured to record it in their respective time bases. Because the GM is connected to one of the ports of SJA1105 switch 1, that port is in PS_SLAVE and is the source of ts2phc time. So the embedded L2 switch as well as the other SJA1105 switches synchronize to the SJA1105. When the GM migrates to a port belonging to a different PHC (say sw2p0), the PPS distribution tree remains unchanged but the port reported by ptp4l as being in PS_SLAVE changes. So ts2phc must reconfigure itself to use /dev/ptp3 as time reference, and the other PHC devices turn into target clocks. Signed-off-by: Vladimir Oltean <olte...@gmail.com> --- v5->v6: - use generic port_state_normalize() rather than ts2phc logic duplication - rename auto_init_ports() to ts2phc_auto_init_ports() - adapt to newly added phc_index argument to pmc_agent_query_port_properties v4->v5: - rebased on top of the variable renaming v3->v4: - use the new pmc agent API - add more info to the commit message - priv->state_changed can be delayed to a future patch - use enum port_state instead of int v2->v3: - Added man page and usage verbiage. - Removed some useless curly braces in ts2phc_cleanup(). makefile | 5 +- ts2phc.8 | 15 ++++ ts2phc.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- ts2phc.h | 12 ++++ 4 files changed, 244 insertions(+), 3 deletions(-) diff --git a/makefile b/makefile index 5295b60b5dac..9aed38318803 100644 --- a/makefile +++ b/makefile @@ -68,8 +68,9 @@ phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o timemaster: phc.o print.o rtnl.o sk.o timemaster.o util.o version.o -ts2phc: config.o clockadj.o hash.o interface.o phc.o print.o $(SERVOS) sk.o \ - $(TS2PHC) util.o version.o +ts2phc: config.o clockadj.o hash.o interface.o msg.o phc.o pmc_agent.o \ + pmc_common.o print.o $(SERVOS) sk.o $(TS2PHC) tlv.o transport.o raw.o \ + udp.o udp6.o uds.o util.o version.o version.o: .version version.sh $(filter-out version.d,$(DEPEND)) diff --git a/ts2phc.8 b/ts2phc.8 index 690c4627f9f2..36d56cce0270 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -26,6 +26,21 @@ A single source may be used to distribute time to one or more PHC devices. .SH OPTIONS .TP +.BI \-a +Adjust the direction of synchronization automatically. The program determines +which PHC should the reference clock for time distribution and which should +be the destinations by querying the port states from the running instance of +.B ptp4l. +Note that using this option, the PPS signal distribution hierarchy still +remains fixed as per the configuration file. This implies that using this +option, a PPS source of the PHC kind may become a target clock, and a PPS sink +may become a reference clock. Other, non-PHC types of PPS sources (generic, +NMEA) cannot become target clocks. Clocks which are not part of +.B ptp4l's +list of ports are not synchronized. This option is useful when the +.B boundary_clock_jbod +option of ptp4l is also enabled. +.TP .BI \-c " device|name" Specifies a PHC to be synchronized. The clock may be identified by its character device (like /dev/ptp0) diff --git a/ts2phc.c b/ts2phc.c index 1f30a9616ec4..fc8a00037dcb 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -6,6 +6,7 @@ * @note Copyright (C) 2012 Richard Cochran <richardcoch...@gmail.com> * @note SPDX-License-Identifier: GPL-2.0+ */ +#include <errno.h> #include <net/if.h> #include <stdlib.h> #include <sys/types.h> @@ -13,6 +14,7 @@ #include "clockadj.h" #include "config.h" +#include "contain.h" #include "interface.h" #include "phc.h" #include "print.h" @@ -25,11 +27,94 @@ struct interface { static void ts2phc_cleanup(struct ts2phc_private *priv) { + struct ts2phc_port *p, *tmp; + ts2phc_pps_sink_cleanup(priv); if (priv->src) ts2phc_pps_source_destroy(priv->src); if (priv->cfg) config_destroy(priv->cfg); + + pmc_agent_destroy(priv->agent); + + /* + * Clocks are destroyed by the cleanup methods of the individual + * PPS source and sink modules. + */ + LIST_FOREACH_SAFE(p, &priv->ports, list, tmp) + free(p); + + msg_cleanup(); +} + +static struct ts2phc_port *ts2phc_port_get(struct ts2phc_private *priv, + unsigned int number) +{ + struct ts2phc_port *p; + + LIST_FOREACH(p, &priv->ports, list) + if (p->number == number) + return p; + + return NULL; +} + +static enum port_state ts2phc_clock_compute_state(struct ts2phc_private *priv, + struct ts2phc_clock *clock) +{ + enum port_state state = PS_DISABLED; + struct ts2phc_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; +} + +static int ts2phc_recv_subscribed(void *context, struct ptp_message *msg, + int excluded) +{ + struct ts2phc_private *priv = context; + enum port_state state; + struct ts2phc_clock *clock; + struct portDS *pds; + struct ts2phc_port *port; + int mgt_id; + + mgt_id = management_tlv_id(msg); + if (mgt_id == excluded) + return 0; + + switch (mgt_id) { + case MID_PORT_DATA_SET: + pds = management_tlv_data(msg); + port = ts2phc_port_get(priv, pds->portIdentity.portNumber); + if (!port) { + pr_info("received data for unknown port %s", + pid2str(&pds->portIdentity)); + return 1; + } + state = port_state_normalize(pds->portState); + if (port->state != state) { + pr_info("port %s changed state", + pid2str(&pds->portIdentity)); + port->state = state; + clock = port->clock; + state = ts2phc_clock_compute_state(priv, clock); + if (clock->state != state || clock->new_state) { + clock->new_state = state; + } + } + return 1; + } + return 0; } static struct servo *ts2phc_servo_create(struct ts2phc_private *priv, @@ -109,11 +194,112 @@ void ts2phc_clock_destroy(struct ts2phc_clock *c) free(c); } +static struct ts2phc_port *ts2phc_port_add(struct ts2phc_private *priv, + unsigned int number, char *device) +{ + struct ts2phc_clock *c = NULL; + struct ts2phc_port *p, *tmp; + + p = ts2phc_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 = ts2phc_clock_add(priv, device); + if (!c) + return NULL; + } + p = malloc(sizeof(*p)); + if (!p) { + pr_err("failed to allocate memory for a port"); + ts2phc_clock_destroy(c); + return NULL; + } + p->number = number; + p->clock = c; + LIST_INSERT_HEAD(&priv->ports, p, list); + return p; +} + +static int ts2phc_auto_init_ports(struct ts2phc_private *priv) +{ + int number_ports, timestamping, phc_index, err; + struct ts2phc_clock *clock; + struct ts2phc_port *port; + enum port_state state; + char iface[IFNAMSIZ]; + unsigned int i; + + while (1) { + if (!is_running()) + return -1; + err = pmc_agent_query_dds(priv->agent, 1000); + if (!err) + break; + if (err == -ETIMEDOUT) + pr_notice("Waiting for ptp4l..."); + else + return -1; + } + + number_ports = pmc_agent_get_number_ports(priv->agent); + if (number_ports <= 0) { + pr_err("failed to get number of ports"); + return -1; + } + + err = pmc_agent_subscribe(priv->agent, 1000); + if (err) { + pr_err("failed to subscribe"); + return -1; + } + + for (i = 1; i <= number_ports; i++) { + err = pmc_agent_query_port_properties(priv->agent, 1000, i, + &state, ×tamping, + &phc_index, iface); + if (err == -ENODEV) { + /* port does not exist, ignore the port */ + continue; + } + if (err) { + pr_err("failed to get port properties"); + return -1; + } + if (timestamping == TS_SOFTWARE) { + /* ignore ports with software time stamping */ + continue; + } + port = ts2phc_port_add(priv, i, iface); + if (!port) + return -1; + port->state = port_state_normalize(state); + } + if (LIST_EMPTY(&priv->clocks)) { + pr_err("no suitable ports available"); + return -1; + } + LIST_FOREACH(clock, &priv->clocks, list) { + clock->new_state = ts2phc_clock_compute_state(priv, clock); + } + + return 0; +} + static void usage(char *progname) { fprintf(stderr, "\n" "usage: %s [options]\n\n" + " -a turn on autoconfiguration\n" " -c [dev|name] PHC time sink (like /dev/ptp0 or eth0)\n" " (may be specified multiple times)\n" " -f [file] read configuration from 'file'\n" @@ -135,6 +321,7 @@ static void usage(char *progname) int main(int argc, char *argv[]) { int c, err = 0, have_sink = 0, index, print_level; + char uds_local[MAX_IFNAME_SIZE + 1]; enum ts2phc_pps_source_type pps_type; struct ts2phc_private priv = {0}; char *config = NULL, *progname; @@ -142,6 +329,7 @@ int main(int argc, char *argv[]) struct config *cfg = NULL; struct interface *iface; struct option *opts; + int autocfg = 0; handle_term_signals(); @@ -150,13 +338,18 @@ int main(int argc, char *argv[]) ts2phc_cleanup(&priv); return -1; } + priv.agent = pmc_agent_create(); + if (!priv.agent) { + ts2phc_cleanup(&priv); + return -1; + } opts = config_long_options(cfg); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1 + progname : argv[0]; - while (EOF != (c = getopt_long(argc, argv, "c:f:hi:l:mqs:v", opts, &index))) { + while (EOF != (c = getopt_long(argc, argv, "ac:f:hi:l:mqs:v", opts, &index))) { switch (c) { case 0: if (config_parse_option(cfg, opts[index].name, optarg)) { @@ -164,6 +357,9 @@ int main(int argc, char *argv[]) return -1; } break; + case 'a': + autocfg = 1; + break; case 'c': if (!config_create_interface(optarg, cfg)) { fprintf(stderr, "failed to add PPS sink\n"); @@ -229,6 +425,23 @@ int main(int argc, char *argv[]) STAILQ_INIT(&priv.sinks); priv.cfg = cfg; + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc.%d", + getpid()); + + if (autocfg) { + err = init_pmc_node(cfg, priv.agent, uds_local, + ts2phc_recv_subscribed, &priv); + if (err) { + ts2phc_cleanup(&priv); + return -1; + } + err = ts2phc_auto_init_ports(&priv); + if (err) { + ts2phc_cleanup(&priv); + return -1; + } + } + STAILQ_FOREACH(iface, &cfg->interfaces, list) { if (1 == config_get_int(cfg, interface_name(iface), "ts2phc.master")) { if (pps_source) { diff --git a/ts2phc.h b/ts2phc.h index 3b341419946e..f01bad2c8b4c 100644 --- a/ts2phc.h +++ b/ts2phc.h @@ -11,6 +11,7 @@ #include <sys/queue.h> #include <time.h> +#include "pmc_agent.h" #include "servo.h" #include "ts2phc_pps_source.h" #include "ts2phc_pps_sink.h" @@ -24,18 +25,29 @@ struct ts2phc_clock { clockid_t clkid; int fd; int phc_index; + enum port_state state; + enum port_state new_state; struct servo *servo; enum servo_state servo_state; char *name; bool no_adj; }; +struct ts2phc_port { + LIST_ENTRY(ts2phc_port) list; + unsigned int number; + enum port_state state; + struct ts2phc_clock *clock; +}; + struct ts2phc_private { struct ts2phc_pps_source *src; STAILQ_HEAD(sink_ifaces_head, ts2phc_pps_sink) sinks; unsigned int n_sinks; struct ts2phc_sink_array *polling_array; struct config *cfg; + struct pmc_agent *agent; + LIST_HEAD(port_head, ts2phc_port) ports; LIST_HEAD(clock_head, ts2phc_clock) clocks; }; -- 2.34.1 _______________________________________________ Linuxptp-devel mailing list Linuxptp-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linuxptp-devel