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;
|
|
|
|
}
|
2020-06-29 03:02:38 +08:00
|
|
|
priv.master = ts2phc_master_create(cfg, pps_source, pps_type);
|
|
|
|
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;
|
|
|
|
}
|