linuxptp: add phc_ctl program to help debug PHC devices
This is an updated version of a script I wrote a couple years ago for debugging the PHC when writing a new driver. I figured that it might be handy for the LinuxPTP project to include, as it can give some insight into the PHC directly. I have updated it to make use of the shared code here, in order to reduce duplication. Hopefully this is of some use to everyone. Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>master
parent
3e858ba17b
commit
bdb6a35883
|
@ -5,3 +5,4 @@
|
||||||
/phc2sys
|
/phc2sys
|
||||||
/pmc
|
/pmc
|
||||||
/ptp4l
|
/ptp4l
|
||||||
|
/phc_ctl
|
||||||
|
|
6
makefile
6
makefile
|
@ -22,13 +22,13 @@ CC = $(CROSS_COMPILE)gcc
|
||||||
VER = -DVER=$(version)
|
VER = -DVER=$(version)
|
||||||
CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS)
|
CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS)
|
||||||
LDLIBS = -lm -lrt $(EXTRA_LDFLAGS)
|
LDLIBS = -lm -lrt $(EXTRA_LDFLAGS)
|
||||||
PRG = ptp4l pmc phc2sys hwstamp_ctl
|
PRG = ptp4l pmc phc2sys hwstamp_ctl phc_ctl
|
||||||
OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \
|
OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \
|
||||||
filter.o fsm.o linreg.o mave.o mmedian.o msg.o ntpshm.o phc.o \
|
filter.o fsm.o linreg.o mave.o mmedian.o msg.o ntpshm.o phc.o \
|
||||||
pi.o port.o print.o ptp4l.o raw.o servo.o sk.o stats.o tlv.o \
|
pi.o port.o print.o ptp4l.o raw.o servo.o sk.o stats.o tlv.o \
|
||||||
transport.o udp.o udp6.o uds.o util.o version.o
|
transport.o udp.o udp6.o uds.o util.o version.o
|
||||||
|
|
||||||
OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o
|
OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o phc_ctl.o
|
||||||
SRC = $(OBJECTS:.o=.c)
|
SRC = $(OBJECTS:.o=.c)
|
||||||
DEPEND = $(OBJECTS:.o=.d)
|
DEPEND = $(OBJECTS:.o=.d)
|
||||||
srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
|
srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
|
||||||
|
@ -54,6 +54,8 @@ phc2sys: clockadj.o clockcheck.o linreg.o msg.o ntpshm.o phc.o phc2sys.o pi.o \
|
||||||
|
|
||||||
hwstamp_ctl: hwstamp_ctl.o version.o
|
hwstamp_ctl: hwstamp_ctl.o version.o
|
||||||
|
|
||||||
|
phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o
|
||||||
|
|
||||||
version.o: .version version.sh $(filter-out version.d,$(DEPEND))
|
version.o: .version version.sh $(filter-out version.d,$(DEPEND))
|
||||||
|
|
||||||
.version: force
|
.version: force
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
.TH PHC_CTL 8 "June 2014" "linuxptp"
|
||||||
|
.SH NAME
|
||||||
|
phc_ctl \- directly control PHC device clock
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B phc_ctl
|
||||||
|
[ options ] <device> [ commands ]
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B phc_ctl
|
||||||
|
is a program which can be used to directly control a PHC clock device.
|
||||||
|
Typically, it is used for debugging purposes, and has little use for general
|
||||||
|
control of the device. For general control of PHC clock devices,
|
||||||
|
.B phc2sys (8)
|
||||||
|
should be preferred.
|
||||||
|
|
||||||
|
<device> may be either CLOCK_REALTIME, any /dev/ptpX device, or any ethernet
|
||||||
|
device which supports ethtool's get_ts_info ioctl.
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.BI \-l " print-level"
|
||||||
|
Set the maximum syslog level of messages which should be printed or sent to the
|
||||||
|
system logger. The default is 6 (LOG_INFO).
|
||||||
|
.TP
|
||||||
|
.BI \-q
|
||||||
|
Do not send messages to syslog. By default messages will be sent.
|
||||||
|
.TP
|
||||||
|
.BI \-Q
|
||||||
|
Do not print messages to standard output. By default messages will be printed.
|
||||||
|
.TP
|
||||||
|
.BI \-h
|
||||||
|
Display a help message.
|
||||||
|
.TP
|
||||||
|
.B \-v
|
||||||
|
Prints the software version and exits.
|
||||||
|
|
||||||
|
.SH COMMANDS
|
||||||
|
|
||||||
|
.B phc_ctl
|
||||||
|
is controlled by passing commands which take either an optional or required
|
||||||
|
parameter. The commands (outlined below) will control aspects of the PHC clock
|
||||||
|
device. These commands may be useful for inspecting or debugging the PHC
|
||||||
|
driver, but may have adverse side effects on running instances of
|
||||||
|
.B ptp4l (8)
|
||||||
|
or
|
||||||
|
.B phc2sys (8)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI set " seconds"
|
||||||
|
Set the PHC clock time to the value specified in seconds. Defaults to reading
|
||||||
|
CLOCK_REALTIME if no value is provided.
|
||||||
|
.TP
|
||||||
|
.BI get
|
||||||
|
Get the current time of the PHC clock device.
|
||||||
|
.TP
|
||||||
|
.BI adj " seconds"
|
||||||
|
Adjust the PHC clock by an amount of seconds provided. This argument is required.
|
||||||
|
.TP
|
||||||
|
.BI freq " ppb"
|
||||||
|
Adjust the frequency of the PHC clock by the specified parts per billion. If no
|
||||||
|
argument is provided, it will attempt to read the current frequency and report
|
||||||
|
it.
|
||||||
|
.TP
|
||||||
|
.BI cmp
|
||||||
|
Compare the PHC clock device to CLOCK_REALTIME, using the best method available.
|
||||||
|
.TP
|
||||||
|
.BI caps
|
||||||
|
Display the device capabiltiies. This is the default command if no commands are
|
||||||
|
provided.
|
||||||
|
.TP
|
||||||
|
.BI wait " seconds"
|
||||||
|
Sleep the process for the specified period of time, waking up and resuming
|
||||||
|
afterwards. This command may be useful for sanity checking whether the PHC
|
||||||
|
clock is running as expected.
|
||||||
|
|
||||||
|
The arguments specified in seconds are read as double precision floating point
|
||||||
|
values, and will scale to nanoseconds. This means providing a value of 5.5
|
||||||
|
means 5 and one half seconds. This allows specifying fairly precise values for time.
|
||||||
|
|
||||||
|
.SH EXAMPLES
|
||||||
|
|
||||||
|
Read the current clock time from the device
|
||||||
|
.RS
|
||||||
|
\f(CWphc_ctl /dev/ptp0 get\fP
|
||||||
|
.RE
|
||||||
|
|
||||||
|
Set the PHC clock time to CLOCK_REALTIME
|
||||||
|
.RS
|
||||||
|
\f(CWphc_ctl /dev/ptp0 set\fP
|
||||||
|
.RE
|
||||||
|
|
||||||
|
Set PHC clock time to 0 (seconds since Epoch)
|
||||||
|
.RS
|
||||||
|
\f(CWphc_ctl /dev/ptp0 set 0.0\fP
|
||||||
|
.RE
|
||||||
|
|
||||||
|
Quickly sanity check frequency slewing by setting slewing frequency by positive
|
||||||
|
10%, resetting clock to 0.0 time, waiting for 10 seconds, and then reading
|
||||||
|
time. The time read back should be (roughly) 11 seconds, since the clock was
|
||||||
|
slewed 10% faster.
|
||||||
|
.RS
|
||||||
|
\f(CWphc_ctl /dev/ptp0 freq 100000000 set 0.0 wait 10.0 get
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR ptp4l (8)
|
||||||
|
.BR phc2sys (8)
|
|
@ -0,0 +1,571 @@
|
||||||
|
/*
|
||||||
|
* @file phc_ctl.c
|
||||||
|
* @brief Utility program to directly control and debug a PHC device.
|
||||||
|
* @note Copyright (C) 2014 Jacob Keller <jacob.keller@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <float.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <linux/pps.h>
|
||||||
|
#include <linux/ptp_clock.h>
|
||||||
|
|
||||||
|
#include "clockadj.h"
|
||||||
|
#include "missing.h"
|
||||||
|
#include "phc.h"
|
||||||
|
#include "print.h"
|
||||||
|
#include "sysoff.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
#define NSEC2SEC 1000000000.0
|
||||||
|
|
||||||
|
/* trap the alarm signal so that pause() will wake up on receipt */
|
||||||
|
static void handle_alarm(int s)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void double_to_timespec(double d, struct timespec *ts)
|
||||||
|
{
|
||||||
|
double fraction, whole;
|
||||||
|
|
||||||
|
fraction = modf(d, &whole);
|
||||||
|
|
||||||
|
/* cast the whole value to a time_t to store as seconds */
|
||||||
|
ts->tv_sec = (time_t)whole;
|
||||||
|
/* tv_nsec is a long, so we multiply the nanoseconds per second double
|
||||||
|
* value by our fractional component. This results in a correct
|
||||||
|
* timespec from the double representing seconds.
|
||||||
|
*/
|
||||||
|
ts->tv_nsec = (long)(NSEC2SEC * fraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int install_handler(int signum, void(*handler)(int))
|
||||||
|
{
|
||||||
|
struct sigaction action;
|
||||||
|
sigset_t mask;
|
||||||
|
|
||||||
|
/* Unblock the signal */
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigaddset(&mask, signum);
|
||||||
|
sigprocmask(SIG_UNBLOCK, &mask, NULL);
|
||||||
|
|
||||||
|
/* Install the signal handler */
|
||||||
|
action.sa_handler = handler;
|
||||||
|
action.sa_flags = 0;
|
||||||
|
sigemptyset(&action.sa_mask);
|
||||||
|
sigaction(signum, &action, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t calculate_offset(struct timespec *ts1,
|
||||||
|
struct timespec *rt,
|
||||||
|
struct timespec *ts2)
|
||||||
|
{
|
||||||
|
int64_t interval;
|
||||||
|
int64_t offset;
|
||||||
|
|
||||||
|
#define NSEC_PER_SEC 1000000000ULL
|
||||||
|
/* calculate interval between clock realtime */
|
||||||
|
interval = (ts2->tv_sec - ts1->tv_sec) * NSEC_PER_SEC;
|
||||||
|
interval += ts2->tv_nsec - ts1->tv_nsec;
|
||||||
|
|
||||||
|
/* assume PHC read occured half way between CLOCK_REALTIME reads */
|
||||||
|
|
||||||
|
offset = (rt->tv_sec - ts1->tv_sec) * NSEC_PER_SEC;
|
||||||
|
offset += (rt->tv_nsec - ts1->tv_nsec) - (interval / 2);
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static clockid_t clock_open(char *device)
|
||||||
|
{
|
||||||
|
struct sk_ts_info ts_info;
|
||||||
|
char phc_device[16];
|
||||||
|
int clkid;
|
||||||
|
|
||||||
|
/* check if device is CLOCK_REALTIME */
|
||||||
|
if (!strcasecmp(device, "CLOCK_REALTIME"))
|
||||||
|
return CLOCK_REALTIME;
|
||||||
|
|
||||||
|
/* check if device is valid phc device */
|
||||||
|
clkid = phc_open(device);
|
||||||
|
if (clkid != CLOCK_INVALID)
|
||||||
|
return clkid;
|
||||||
|
|
||||||
|
/* check if device is a valid ethernet device */
|
||||||
|
if (sk_get_ts_info(device, &ts_info) || !ts_info.valid) {
|
||||||
|
pr_err("unknown clock %s: %m", device);
|
||||||
|
return CLOCK_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ts_info.phc_index < 0) {
|
||||||
|
pr_err("interface %s does not have a PHC", device);
|
||||||
|
return CLOCK_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(phc_device, "/dev/ptp%d", ts_info.phc_index);
|
||||||
|
clkid = phc_open(phc_device);
|
||||||
|
if (clkid == CLOCK_INVALID)
|
||||||
|
pr_err("cannot open %s for %s: %m", phc_device, device);
|
||||||
|
return clkid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const char *progname)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"\n"
|
||||||
|
"usage: %s [options] <device> -- [command]\n\n"
|
||||||
|
" device ethernet or ptp clock device"
|
||||||
|
"\n"
|
||||||
|
" options\n"
|
||||||
|
" -l [num] set the logging level to 'num'\n"
|
||||||
|
" -q do not print messages to the syslog\n"
|
||||||
|
" -Q do not print messages to stdout\n"
|
||||||
|
" -v prints the software version and exits\n"
|
||||||
|
" -h prints this message and exits\n"
|
||||||
|
"\n"
|
||||||
|
" commands\n"
|
||||||
|
" specify commands with arguments. Can specify multiple\n"
|
||||||
|
" commands to be executed in order. Seconds are read as\n"
|
||||||
|
" double precision floating point values.\n"
|
||||||
|
" set [seconds] set PHC time (defaults to time on CLOCK_REALTIME)\n"
|
||||||
|
" get get PHC time\n"
|
||||||
|
" adj <seconds> adjust PHC time by offset\n"
|
||||||
|
" freq [ppb] adjust PHC frequency (default returns current offset)\n"
|
||||||
|
" cmp compare PHC offset to CLOCK_REALTIME\n"
|
||||||
|
" caps display device capabilities (default if no command given)\n"
|
||||||
|
" wait <seconds> pause between commands\n"
|
||||||
|
"\n",
|
||||||
|
progname);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef int (*cmd_func_t)(clockid_t, int, char *[]);
|
||||||
|
struct cmd_t {
|
||||||
|
const char *name;
|
||||||
|
const cmd_func_t function;
|
||||||
|
};
|
||||||
|
|
||||||
|
static cmd_func_t get_command_function(const char *name);
|
||||||
|
static inline int name_is_a_command(const char *name);
|
||||||
|
|
||||||
|
static int do_set(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
double time_arg = 0;
|
||||||
|
int args_to_eat = 0;
|
||||||
|
|
||||||
|
enum parser_result r;
|
||||||
|
|
||||||
|
memset(&ts, 0, sizeof(ts));
|
||||||
|
|
||||||
|
/* if we have no more arguments, or the next argument is the ";"
|
||||||
|
* separator, then we run set as default parameter mode */
|
||||||
|
if (cmdc < 1 || name_is_a_command(cmdv[0])) {
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
|
||||||
|
/* since we aren't using the options, we can simply ensure
|
||||||
|
* that we don't eat any arguments
|
||||||
|
*/
|
||||||
|
args_to_eat = 0;
|
||||||
|
} else {
|
||||||
|
/* parse the double */
|
||||||
|
r = get_ranged_double(cmdv[0], &time_arg, 0.0, DBL_MAX);
|
||||||
|
switch (r) {
|
||||||
|
case PARSED_OK:
|
||||||
|
break;
|
||||||
|
case MALFORMED:
|
||||||
|
pr_err("set: '%s' is not a valid double", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
case OUT_OF_RANGE:
|
||||||
|
pr_err("set: '%s' is out of range", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
default:
|
||||||
|
pr_err("set: couldn't process '%s'", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double_to_timespec(time_arg, &ts);
|
||||||
|
|
||||||
|
/* in order for processing to work, we need to ensure the
|
||||||
|
* run_cmds loop eats the optional set argument
|
||||||
|
*/
|
||||||
|
args_to_eat = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock_settime(clkid, &ts)) {
|
||||||
|
pr_err("set: failed to set clock time: %s",
|
||||||
|
strerror(errno));
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
pr_notice("set clock time to %ld.%09ld or %s",
|
||||||
|
ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec));
|
||||||
|
}
|
||||||
|
|
||||||
|
return args_to_eat;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_get(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
memset(&ts, 0, sizeof(ts));
|
||||||
|
if (clock_gettime(clkid, &ts)) {
|
||||||
|
pr_err("get: failed to get clock time: %s",
|
||||||
|
strerror(errno));
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
pr_notice("clock time is %ld.%09lu or %s",
|
||||||
|
ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get operation does not require any arguments */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_adj(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
double time_arg;
|
||||||
|
int64_t nsecs;
|
||||||
|
enum parser_result r;
|
||||||
|
|
||||||
|
if (cmdc < 1 || name_is_a_command(cmdv[0])) {
|
||||||
|
pr_err("adj: missing required time argument");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse the double time offset argument */
|
||||||
|
r = get_ranged_double(cmdv[0], &time_arg, DBL_MIN, DBL_MAX);
|
||||||
|
switch (r) {
|
||||||
|
case PARSED_OK:
|
||||||
|
break;
|
||||||
|
case MALFORMED:
|
||||||
|
pr_err("adj: '%s' is not a valid double", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
case OUT_OF_RANGE:
|
||||||
|
pr_err("adj: '%s' is out of range.", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
default:
|
||||||
|
pr_err("adj: couldn't process '%s'", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsecs = (int64_t)(NSEC2SEC * time_arg);
|
||||||
|
|
||||||
|
clockadj_init(clkid);
|
||||||
|
clockadj_step(clkid, nsecs);
|
||||||
|
|
||||||
|
pr_notice("adjusted clock by %lf seconds", time_arg);
|
||||||
|
|
||||||
|
/* adjustment always consumes one argument */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_freq(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
double ppb;
|
||||||
|
enum parser_result r;
|
||||||
|
|
||||||
|
clockadj_init(clkid);
|
||||||
|
|
||||||
|
if (cmdc < 1 || name_is_a_command(cmdv[0])) {
|
||||||
|
ppb = clockadj_get_freq(clkid);
|
||||||
|
pr_err("clock frequency offset is %lfppb", ppb);
|
||||||
|
|
||||||
|
/* no argument was used */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse the double ppb argument */
|
||||||
|
r = get_ranged_double(cmdv[0], &ppb, -NSEC2SEC, NSEC2SEC);
|
||||||
|
switch (r) {
|
||||||
|
case PARSED_OK:
|
||||||
|
break;
|
||||||
|
case MALFORMED:
|
||||||
|
pr_err("freq: '%s' is not a valid double", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
case OUT_OF_RANGE:
|
||||||
|
pr_err("freq: '%s' is out of range.", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
default:
|
||||||
|
pr_err("freq: couldn't process '%s'", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
clockadj_set_freq(clkid, ppb);
|
||||||
|
pr_err("adjusted clock frequency offset to %lfppb", ppb);
|
||||||
|
|
||||||
|
/* consumed one argument to determine the frequency adjustment value */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_caps(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
struct ptp_clock_caps caps;
|
||||||
|
|
||||||
|
if (clkid == CLOCK_REALTIME) {
|
||||||
|
pr_warning("CLOCK_REALTIME is not a PHC device.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl(CLOCKID_TO_FD(clkid), PTP_CLOCK_GETCAPS, &caps)) {
|
||||||
|
pr_err("get capabilities failed: %s",
|
||||||
|
strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_notice("\n"
|
||||||
|
"capabilities:\n"
|
||||||
|
" %d maximum frequency adjustment (ppb)\n"
|
||||||
|
" %d programable alarms\n"
|
||||||
|
" %d external time stamp channels\n"
|
||||||
|
" %d programmable periodic signals\n"
|
||||||
|
" %s pulse per second support",
|
||||||
|
caps.max_adj,
|
||||||
|
caps.n_alarm,
|
||||||
|
caps.n_ext_ts,
|
||||||
|
caps.n_per_out,
|
||||||
|
caps.pps ? "has" : "doesn't have");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_cmp(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
struct timespec ts, rta, rtb;
|
||||||
|
int64_t sys_offset, delay = 0, offset;
|
||||||
|
uint64_t sys_ts;
|
||||||
|
|
||||||
|
if (SYSOFF_SUPPORTED ==
|
||||||
|
sysoff_measure(CLOCKID_TO_FD(clkid),
|
||||||
|
9, &sys_offset, &sys_ts, &delay)) {
|
||||||
|
pr_notice( "offset from CLOCK_REALTIME is %"PRId64"ns\n",
|
||||||
|
sys_offset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&ts, 0, sizeof(ts));
|
||||||
|
memset(&ts, 0, sizeof(rta));
|
||||||
|
memset(&ts, 0, sizeof(rtb));
|
||||||
|
if (clock_gettime(CLOCK_REALTIME, &rta) ||
|
||||||
|
clock_gettime(clkid, &ts) ||
|
||||||
|
clock_gettime(CLOCK_REALTIME, &rtb)) {
|
||||||
|
pr_err("cmp: failed clock reads: %s\n",
|
||||||
|
strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = calculate_offset(&rta, &ts, &rtb);
|
||||||
|
pr_notice( "offset from CLOCK_REALTIME is approximately %"PRId64"ns\n",
|
||||||
|
offset);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_wait(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
double time_arg;
|
||||||
|
struct timespec ts;
|
||||||
|
struct itimerval timer;
|
||||||
|
enum parser_result r;
|
||||||
|
|
||||||
|
if (cmdc < 1 || name_is_a_command(cmdv[0])) {
|
||||||
|
pr_err("wait: requires sleep duration argument\n");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&timer, 0, sizeof(timer));
|
||||||
|
|
||||||
|
/* parse the double time offset argument */
|
||||||
|
r = get_ranged_double(cmdv[0], &time_arg, 0.0, DBL_MAX);
|
||||||
|
switch (r) {
|
||||||
|
case PARSED_OK:
|
||||||
|
break;
|
||||||
|
case MALFORMED:
|
||||||
|
pr_err("wait: '%s' is not a valid double", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
case OUT_OF_RANGE:
|
||||||
|
pr_err("wait: '%s' is out of range.", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
default:
|
||||||
|
pr_err("wait: couldn't process '%s'", cmdv[0]);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double_to_timespec(time_arg, &ts);
|
||||||
|
timer.it_value.tv_sec = ts.tv_sec;
|
||||||
|
timer.it_value.tv_usec = ts.tv_nsec / 1000;
|
||||||
|
setitimer(ITIMER_REAL, &timer, NULL);
|
||||||
|
pause();
|
||||||
|
|
||||||
|
/* the SIGALRM is already trapped during initialization, so we will
|
||||||
|
* wake up here once the alarm is handled.
|
||||||
|
*/
|
||||||
|
pr_notice( "process slept for %lf seconds\n", time_arg);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct cmd_t all_commands[] = {
|
||||||
|
{ "set", &do_set },
|
||||||
|
{ "get", &do_get },
|
||||||
|
{ "adj", &do_adj },
|
||||||
|
{ "freq", &do_freq },
|
||||||
|
{ "cmp", &do_cmp },
|
||||||
|
{ "caps", &do_caps },
|
||||||
|
{ "wait", &do_wait },
|
||||||
|
{ 0, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static cmd_func_t get_command_function(const char *name)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
cmd_func_t cmd = NULL;
|
||||||
|
|
||||||
|
for (i = 0; all_commands[i].name != NULL; i++) {
|
||||||
|
if (!strncmp(name,
|
||||||
|
all_commands[i].name,
|
||||||
|
strlen(all_commands[i].name)))
|
||||||
|
cmd = all_commands[i].function;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int name_is_a_command(const char *name)
|
||||||
|
{
|
||||||
|
return get_command_function(name) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int run_cmds(clockid_t clkid, int cmdc, char *cmdv[])
|
||||||
|
{
|
||||||
|
int i = 0, result = 0;
|
||||||
|
cmd_func_t action = NULL;
|
||||||
|
|
||||||
|
while (i < cmdc) {
|
||||||
|
char *arg = cmdv[i];
|
||||||
|
|
||||||
|
/* increment now to remove the command argument */
|
||||||
|
i++;
|
||||||
|
|
||||||
|
action = get_command_function(arg);
|
||||||
|
if (action)
|
||||||
|
result = action(clkid, cmdc - i, &cmdv[i]);
|
||||||
|
else
|
||||||
|
pr_err("unknown command %s.", arg);
|
||||||
|
|
||||||
|
/* result is how many arguments were used up by the command,
|
||||||
|
* not including the ";". We will increment the loop counter
|
||||||
|
* to avoid processing the arguments as commands.
|
||||||
|
*/
|
||||||
|
if (result < 0)
|
||||||
|
return result;
|
||||||
|
else
|
||||||
|
i += result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
const char *progname;
|
||||||
|
char **cmdv, *default_cmdv[] = { "caps" };
|
||||||
|
int c, result, cmdc;
|
||||||
|
int print_level = LOG_INFO, verbose = 1, use_syslog = 1;
|
||||||
|
clockid_t clkid;
|
||||||
|
|
||||||
|
install_handler(SIGALRM, handle_alarm);
|
||||||
|
|
||||||
|
/* Process the command line arguments. */
|
||||||
|
progname = strrchr(argv[0], '/');
|
||||||
|
progname = progname ? 1+progname : argv[0];
|
||||||
|
while (EOF != (c = getopt(argc, argv,
|
||||||
|
"l:qQvh"))) {
|
||||||
|
switch (c) {
|
||||||
|
case 'l':
|
||||||
|
if (get_arg_val_i(c, optarg, &print_level,
|
||||||
|
PRINT_LEVEL_MIN, PRINT_LEVEL_MAX))
|
||||||
|
return -1;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
use_syslog = 0;
|
||||||
|
break;
|
||||||
|
case 'Q':
|
||||||
|
verbose = 0;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
version_show(stdout);
|
||||||
|
return 0;
|
||||||
|
case 'h':
|
||||||
|
usage(progname);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
usage(progname);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print_set_progname(progname);
|
||||||
|
print_set_verbose(verbose);
|
||||||
|
print_set_syslog(use_syslog);
|
||||||
|
print_set_level(print_level);
|
||||||
|
|
||||||
|
if ((argc - optind) < 1) {
|
||||||
|
usage(progname);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((argc - optind) == 1) {
|
||||||
|
cmdv = default_cmdv;
|
||||||
|
cmdc = 1;
|
||||||
|
} else {
|
||||||
|
cmdv = &argv[optind+1];
|
||||||
|
cmdc = argc - optind - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
clkid = clock_open(argv[optind]);
|
||||||
|
if (clkid == CLOCK_INVALID)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* pass the remaining arguments to the run_cmds loop */
|
||||||
|
result = run_cmds(clkid, cmdc, cmdv);
|
||||||
|
if (result < -1) {
|
||||||
|
/* show usage when command fails */
|
||||||
|
usage(progname);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue