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"
|
|
|
|
#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"
|
|
|
|
|
|
|
|
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
|
|
|
{
|
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);
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clock_destroy(struct clock *c)
|
|
|
|
{
|
|
|
|
servo_destroy(c->servo);
|
|
|
|
posix_clock_close(c->clkid);
|
|
|
|
free(c->name);
|
|
|
|
free(c);
|
|
|
|
}
|
|
|
|
|
2019-07-08 07:47:48 +08:00
|
|
|
static void usage(char *progname)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"\n"
|
|
|
|
"usage: %s [options]\n\n"
|
|
|
|
" -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;
|
|
|
|
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;
|
|
|
|
|
|
|
|
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];
|
|
|
|
while (EOF != (c = getopt_long(argc, argv, "c:f:hi:l:mqs:v", opts, &index))) {
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
|
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-06-29 03:02:38 +08:00
|
|
|
err = ts2phc_slave_poll(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
if (err) {
|
|
|
|
pr_err("poll failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 03:02:38 +08:00
|
|
|
ts2phc_cleanup(&priv);
|
2019-07-08 07:47:48 +08:00
|
|
|
return err;
|
|
|
|
}
|