Merge branch 'mlichvar_leap'

Fixed up trivial conflict in the makefile.

Conflicts:
	makefile
master
Richard Cochran 2013-03-20 19:51:46 +01:00
commit bd28acffb3
14 changed files with 512 additions and 181 deletions

150
clock.c
View File

@ -24,6 +24,7 @@
#include "bmc.h" #include "bmc.h"
#include "clock.h" #include "clock.h"
#include "clockadj.h"
#include "foreign.h" #include "foreign.h"
#include "mave.h" #include "mave.h"
#include "missing.h" #include "missing.h"
@ -76,6 +77,9 @@ struct clock {
int free_running; int free_running;
int freq_est_interval; int freq_est_interval;
int utc_timescale; int utc_timescale;
int leap_set;
int kernel_leap;
enum servo_state servo_state;
tmv_t master_offset; tmv_t master_offset;
tmv_t path_delay; tmv_t path_delay;
struct mave *avg_delay; struct mave *avg_delay;
@ -389,11 +393,13 @@ static enum servo_state clock_no_adjust(struct clock *c)
freq = (1.0 - ratio) * 1e9; freq = (1.0 - ratio) * 1e9;
if (c->stats.max_count > 1) { if (c->stats.max_count > 1) {
clock_stats_update(&c->stats, c->master_offset, freq); clock_stats_update(&c->stats,
tmv_to_nanoseconds(c->master_offset), freq);
} else { } else {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f " pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"path delay %9" PRId64, "path delay %9" PRId64,
c->master_offset, state, freq, c->path_delay); tmv_to_nanoseconds(c->master_offset), state, freq,
tmv_to_nanoseconds(c->path_delay));
} }
fui = 1.0 + (c->status.cumulativeScaledRateOffset + 0.0) / POW2_41; fui = 1.0 + (c->status.cumulativeScaledRateOffset + 0.0) / POW2_41;
@ -412,52 +418,6 @@ static enum servo_state clock_no_adjust(struct clock *c)
return state; return state;
} }
static void clock_ppb(clockid_t clkid, double ppb)
{
struct timex tx;
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_FREQUENCY;
tx.freq = (long) (ppb * 65.536);
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to adjust the clock: %m");
}
static double clock_ppb_read(clockid_t clkid)
{
double f = 0.0;
struct timex tx;
memset(&tx, 0, sizeof(tx));
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to read out the clock frequency adjustment: %m");
else
f = tx.freq / 65.536;
return f;
}
static void clock_step(clockid_t clkid, int64_t ns)
{
struct timex tx;
int sign = 1;
if (ns < 0) {
sign = -1;
ns *= -1;
}
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_SETOFFSET | ADJ_NANO;
tx.time.tv_sec = sign * (ns / NS_PER_SEC);
tx.time.tv_usec = sign * (ns % NS_PER_SEC);
/*
* The value of a timeval is the sum of its fields, but the
* field tv_usec must always be non-negative.
*/
if (tx.time.tv_usec < 0) {
tx.time.tv_sec -= 1;
tx.time.tv_usec += 1000000000;
}
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to step clock: %m");
}
static void clock_update_grandmaster(struct clock *c) static void clock_update_grandmaster(struct clock *c)
{ {
struct parentDS *pds = &c->dad.pds; struct parentDS *pds = &c->dad.pds;
@ -500,23 +460,67 @@ static void clock_update_slave(struct clock *c)
} }
} }
static void clock_utc_correct(struct clock *c) static int clock_utc_correct(struct clock *c, tmv_t ingress)
{ {
struct timespec offset; struct timespec offset;
int utc_offset, leap, clock_leap;
uint64_t ts;
if (!c->utc_timescale) if (!c->utc_timescale)
return; return 0;
if (!(c->tds.flags & PTP_TIMESCALE))
return;
if (c->tds.flags & UTC_OFF_VALID && c->tds.flags & TIME_TRACEABLE) { if (c->tds.flags & UTC_OFF_VALID && c->tds.flags & TIME_TRACEABLE) {
offset.tv_sec = c->tds.currentUtcOffset; utc_offset = c->tds.currentUtcOffset;
} else if (c->tds.currentUtcOffset > CURRENT_UTC_OFFSET) { } else if (c->tds.currentUtcOffset > CURRENT_UTC_OFFSET) {
offset.tv_sec = c->tds.currentUtcOffset; utc_offset = c->tds.currentUtcOffset;
} else { } else {
offset.tv_sec = CURRENT_UTC_OFFSET; utc_offset = CURRENT_UTC_OFFSET;
} }
if (c->tds.flags & LEAP_61) {
leap = 1;
} else if (c->tds.flags & LEAP_59) {
leap = -1;
} else {
leap = 0;
}
/* Handle leap seconds. */
if ((leap || c->leap_set) && c->clkid == CLOCK_REALTIME) {
/* If the clock will be stepped, the time stamp has to be the
target time. Ignore possible 1 second error in utc_offset. */
if (c->servo_state == SERVO_UNLOCKED) {
ts = tmv_to_nanoseconds(tmv_sub(ingress,
c->master_offset));
if (c->tds.flags & PTP_TIMESCALE)
ts -= utc_offset * NS_PER_SEC;
} else {
ts = tmv_to_nanoseconds(ingress);
}
/* Suspend clock updates in the last second before midnight. */
if (is_utc_ambiguous(ts)) {
pr_info("clock update suspended due to leap second");
return -1;
}
clock_leap = leap_second_status(ts, c->leap_set,
&leap, &utc_offset);
if (c->leap_set != clock_leap) {
if (c->kernel_leap)
clockadj_set_leap(c->clkid, clock_leap);
c->leap_set = clock_leap;
}
}
if (!(c->tds.flags & PTP_TIMESCALE))
return 0;
offset.tv_sec = utc_offset;
offset.tv_nsec = 0; offset.tv_nsec = 0;
/* Local clock is UTC, but master is TAI. */ /* Local clock is UTC, but master is TAI. */
c->master_offset = tmv_add(c->master_offset, timespec_to_tmv(offset)); c->master_offset = tmv_add(c->master_offset, timespec_to_tmv(offset));
return 0;
} }
static int forwarding(struct clock *c, struct port *p) static int forwarding(struct clock *c, struct port *p)
@ -565,6 +569,7 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
c->free_running = dds->free_running; c->free_running = dds->free_running;
c->freq_est_interval = dds->freq_est_interval; c->freq_est_interval = dds->freq_est_interval;
c->kernel_leap = dds->kernel_leap;
c->desc = dds->clock_desc; c->desc = dds->clock_desc;
if (c->free_running) { if (c->free_running) {
@ -585,16 +590,20 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
c->clkid = CLOCK_REALTIME; c->clkid = CLOCK_REALTIME;
c->utc_timescale = 1; c->utc_timescale = 1;
max_adj = 512000; max_adj = 512000;
clockadj_set_leap(c->clkid, 0);
} }
c->leap_set = 0;
c->kernel_leap = dds->kernel_leap;
if (c->clkid != CLOCK_INVALID) { if (c->clkid != CLOCK_INVALID) {
fadj = (int) clock_ppb_read(c->clkid); fadj = (int) clockadj_get_freq(c->clkid);
} }
c->servo = servo_create(servo, -fadj, max_adj, sw_ts); c->servo = servo_create(servo, -fadj, max_adj, sw_ts);
if (!c->servo) { if (!c->servo) {
pr_err("Failed to create clock servo"); pr_err("Failed to create clock servo");
return NULL; return NULL;
} }
c->servo_state = SERVO_UNLOCKED;
c->avg_delay = mave_create(MAVE_LENGTH); c->avg_delay = mave_create(MAVE_LENGTH);
if (!c->avg_delay) { if (!c->avg_delay) {
pr_err("Failed to create moving average"); pr_err("Failed to create moving average");
@ -957,7 +966,7 @@ void clock_path_delay(struct clock *c, struct timespec req, struct timestamp rx,
pr_debug("path delay %10lld %10lld", c->path_delay, pd); pr_debug("path delay %10lld %10lld", c->path_delay, pd);
if (c->stats.delay) if (c->stats.delay)
stats_add_value(c->stats.delay, pd); stats_add_value(c->stats.delay, tmv_to_nanoseconds(pd));
} }
void clock_peer_delay(struct clock *c, tmv_t ppd, double nrr) void clock_peer_delay(struct clock *c, tmv_t ppd, double nrr)
@ -966,7 +975,7 @@ void clock_peer_delay(struct clock *c, tmv_t ppd, double nrr)
c->nrr = nrr; c->nrr = nrr;
if (c->stats.delay) if (c->stats.delay)
stats_add_value(c->stats.delay, ppd); stats_add_value(c->stats.delay, tmv_to_nanoseconds(ppd));
} }
void clock_remove_fda(struct clock *c, struct port *p, struct fdarray fda) void clock_remove_fda(struct clock *c, struct port *p, struct fdarray fda)
@ -1018,37 +1027,42 @@ enum servo_state clock_synchronize(struct clock *c,
c->master_offset = tmv_sub(ingress, c->master_offset = tmv_sub(ingress,
tmv_add(origin, tmv_add(c->path_delay, tmv_add(c->c1, c->c2)))); tmv_add(origin, tmv_add(c->path_delay, tmv_add(c->c1, c->c2))));
clock_utc_correct(c);
c->cur.offsetFromMaster = tmv_to_TimeInterval(c->master_offset);
if (!c->path_delay) if (!c->path_delay)
return state; return state;
if (clock_utc_correct(c, ingress))
return c->servo_state;
c->cur.offsetFromMaster = tmv_to_TimeInterval(c->master_offset);
if (c->free_running) if (c->free_running)
return clock_no_adjust(c); return clock_no_adjust(c);
adj = servo_sample(c->servo, c->master_offset, ingress, &state); adj = servo_sample(c->servo, tmv_to_nanoseconds(c->master_offset),
tmv_to_nanoseconds(ingress), &state);
c->servo_state = state;
if (c->stats.max_count > 1) { if (c->stats.max_count > 1) {
clock_stats_update(&c->stats, c->master_offset, adj); clock_stats_update(&c->stats,
tmv_to_nanoseconds(c->master_offset), adj);
} else { } else {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f " pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"path delay %9" PRId64, "path delay %9" PRId64,
c->master_offset, state, adj, c->path_delay); tmv_to_nanoseconds(c->master_offset), state, adj,
tmv_to_nanoseconds(c->path_delay));
} }
switch (state) { switch (state) {
case SERVO_UNLOCKED: case SERVO_UNLOCKED:
break; break;
case SERVO_JUMP: case SERVO_JUMP:
clock_ppb(c->clkid, -adj); clockadj_set_freq(c->clkid, -adj);
clock_step(c->clkid, -c->master_offset); clockadj_step(c->clkid, -tmv_to_nanoseconds(c->master_offset));
c->t1 = tmv_zero(); c->t1 = tmv_zero();
c->t2 = tmv_zero(); c->t2 = tmv_zero();
break; break;
case SERVO_LOCKED: case SERVO_LOCKED:
clock_ppb(c->clkid, -adj); clockadj_set_freq(c->clkid, -adj);
break; break;
} }
return state; return state;

96
clockadj.c 100644
View File

@ -0,0 +1,96 @@
/**
* @file clockadj.c
* @note Copyright (C) 2013 Richard Cochran <richardcochran@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 <string.h>
#include "clockadj.h"
#include "missing.h"
#include "print.h"
#define NS_PER_SEC 1000000000LL
void clockadj_set_freq(clockid_t clkid, double freq)
{
struct timex tx;
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_FREQUENCY;
tx.freq = (long) (freq * 65.536);
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to adjust the clock: %m");
}
double clockadj_get_freq(clockid_t clkid)
{
double f = 0.0;
struct timex tx;
memset(&tx, 0, sizeof(tx));
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to read out the clock frequency adjustment: %m");
else
f = tx.freq / 65.536;
return f;
}
void clockadj_step(clockid_t clkid, int64_t step)
{
struct timex tx;
int sign = 1;
if (step < 0) {
sign = -1;
step *= -1;
}
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_SETOFFSET | ADJ_NANO;
tx.time.tv_sec = sign * (step / NS_PER_SEC);
tx.time.tv_usec = sign * (step % NS_PER_SEC);
/*
* The value of a timeval is the sum of its fields, but the
* field tv_usec must always be non-negative.
*/
if (tx.time.tv_usec < 0) {
tx.time.tv_sec -= 1;
tx.time.tv_usec += 1000000000;
}
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to step clock: %m");
}
void clockadj_set_leap(clockid_t clkid, int leap)
{
struct timex tx;
const char *m = NULL;
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_STATUS;
switch (leap) {
case -1:
tx.status = STA_DEL;
m = "clock set to delete leap second at midnight (UTC)";
break;
case 1:
tx.status = STA_INS;
m = "clock set to insert leap second at midnight (UTC)";
break;
default:
tx.status = 0;
}
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to set the clock status: %m");
else if (m)
pr_notice(m);
}

54
clockadj.h 100644
View File

@ -0,0 +1,54 @@
/**
* @file clockadj.h
* @brief Wraps clock_adjtime functionality.
* @note Copyright (C) 2013 Miroslav Lichvar <mlichvar@redhat.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.
*/
#ifndef HAVE_CLOCKADJ_H
#define HAVE_CLOCKADJ_H
#include <inttypes.h>
#include <time.h>
/*
* Set clock's frequency offset.
* @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME.
* @param freq The frequency offset in parts per billion (ppb).
*/
void clockadj_set_freq(clockid_t clkid, double freq);
/*
* Read clock's frequency offset.
* @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME.
* @return The frequency offset in parts per billion (ppb).
*/
double clockadj_get_freq(clockid_t clkid);
/*
* Step clock's time.
* @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME.
* @param step The time step in nanoseconds.
*/
void clockadj_step(clockid_t clkid, int64_t step);
/*
* Insert/delete leap second at midnight.
* @param clkid CLOCK_REALTIME.
* @param leap +1 to insert leap second, -1 to delete leap second,
* 0 to reset the leap state.
*/
void clockadj_set_leap(clockid_t clkid, int leap);
#endif

View File

@ -399,6 +399,11 @@ static enum parser_result parse_global_setting(const char *option,
return BAD_VALUE; return BAD_VALUE;
cfg->dds.stats_interval = val; cfg->dds.stats_interval = val;
} else if (!strcmp(option, "kernel_leap")) {
if (1 != sscanf(value, "%d", &val))
return BAD_VALUE;
cfg->dds.kernel_leap = val;
} else } else
return NOT_PARSED; return NOT_PARSED;

View File

@ -34,6 +34,7 @@ tx_timestamp_retries 100
use_syslog 1 use_syslog 1
verbose 0 verbose 0
summary_interval 0 summary_interval 0
kernel_leap 1
# #
# Servo Options # Servo Options
# #

1
ds.h
View File

@ -53,6 +53,7 @@ struct default_ds {
int free_running; int free_running;
int freq_est_interval; /*log seconds*/ int freq_est_interval; /*log seconds*/
int stats_interval; /*log seconds*/ int stats_interval; /*log seconds*/
int kernel_leap;
struct clock_description clock_desc; struct clock_description clock_desc;
}; };

View File

@ -33,6 +33,7 @@ tx_timestamp_retries 100
use_syslog 1 use_syslog 1
verbose 0 verbose 0
summary_interval 0 summary_interval 0
kernel_leap 1
# #
# Servo options # Servo options
# #

View File

@ -29,9 +29,9 @@ VER = -DVER=$(version)
CFLAGS = -Wall $(VER) $(INC) $(DEBUG) $(FEAT_CFLAGS) $(EXTRA_CFLAGS) CFLAGS = -Wall $(VER) $(INC) $(DEBUG) $(FEAT_CFLAGS) $(EXTRA_CFLAGS)
LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) LDLIBS = -lm -lrt $(EXTRA_LDFLAGS)
PRG = ptp4l pmc phc2sys hwstamp_ctl PRG = ptp4l pmc phc2sys hwstamp_ctl
OBJ = bmc.o clock.o config.o fault.o fsm.o ptp4l.o mave.o msg.o phc.o pi.o \ OBJ = bmc.o clock.o clockadj.o config.o fault.o fsm.o ptp4l.o mave.o \
port.o print.o raw.o servo.o sk.o stats.o tlv.o tmtab.o transport.o udp.o \ msg.o phc.o pi.o port.o print.o raw.o servo.o sk.o stats.o tlv.o tmtab.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
SRC = $(OBJECTS:.o=.c) SRC = $(OBJECTS:.o=.c)
@ -52,8 +52,8 @@ ptp4l: $(OBJ)
pmc: msg.o pmc.o pmc_common.o print.o raw.o sk.o tlv.o transport.o udp.o \ pmc: msg.o pmc.o pmc_common.o print.o raw.o sk.o tlv.o transport.o udp.o \
udp6.o uds.o util.o version.o udp6.o uds.o util.o version.o
phc2sys: msg.o phc2sys.o pmc_common.o print.o pi.o servo.o raw.o sk.o stats.o \ phc2sys: clockadj.o msg.o phc2sys.o pmc_common.o print.o pi.o servo.o raw.o \
sysoff.o tlv.o transport.o udp.o udp6.o uds.o util.o version.o sk.o stats.o sysoff.o tlv.o transport.o udp.o udp6.o uds.o util.o version.o
hwstamp_ctl: hwstamp_ctl.o version.o hwstamp_ctl: hwstamp_ctl.o version.o

View File

@ -31,6 +31,8 @@ phc2sys \- synchronize two clocks
] [ ] [
.B \-w .B \-w
] [ ] [
.B \-x
] [
.BI \-l " print-level" .BI \-l " print-level"
] [ ] [
.B \-m .B \-m
@ -106,13 +108,10 @@ minimize the error caused by random delays in scheduling and bus utilization.
The default is 5. The default is 5.
.TP .TP
.BI \-O " offset" .BI \-O " offset"
Specify the offset between the slave and master times in seconds. With the Specify the offset between the slave and master times in seconds. The default
is set automatically with the
.B \-w .B \-w
option the default value is set automatically according to the currentUtcOffset option, 0 otherwise.
value obtained from ptp4l and the direction of the clock synchronization.
Without
.B \-w
the default is 0.
.TP .TP
.BI \-u " summary-updates" .BI \-u " summary-updates"
Specify the number of clock updates included in summary statistics. The Specify the number of clock updates included in summary statistics. The
@ -124,7 +123,18 @@ statistics. The messages are printed at the LOG_INFO level.
The default is 0 (disabled). The default is 0 (disabled).
.TP .TP
.B \-w .B \-w
Wait until ptp4l is in a synchronized state. Wait until ptp4l is in a synchronized state. If the
.B \-O
option is not used, also keep the offset between the slave and master
times updated according to the currentUtcOffset value obtained from ptp4l and
the direction of the clock synchronization.
.TP
.B \-x
When a leap second is announced, don't apply it in the kernel by stepping the
clock, but let the servo correct the one-second offset slowly by changing the
clock frequency (unless the
.B \-S
option is used).
.TP .TP
.BI \-l " print-level" .BI \-l " print-level"
Set the maximum syslog level of messages which should be printed or sent to Set the maximum syslog level of messages which should be printed or sent to

281
phc2sys.c
View File

@ -27,13 +27,13 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <inttypes.h> #include <inttypes.h>
#include <linux/pps.h> #include <linux/pps.h>
#include <linux/ptp_clock.h> #include <linux/ptp_clock.h>
#include "clockadj.h"
#include "ds.h" #include "ds.h"
#include "fsm.h" #include "fsm.h"
#include "missing.h" #include "missing.h"
@ -45,6 +45,7 @@
#include "stats.h" #include "stats.h"
#include "sysoff.h" #include "sysoff.h"
#include "tlv.h" #include "tlv.h"
#include "util.h"
#include "version.h" #include "version.h"
#define KP 0.7 #define KP 0.7
@ -54,6 +55,10 @@
#define max_ppb 512000 #define max_ppb 512000
#define PHC_PPS_OFFSET_LIMIT 10000000 #define PHC_PPS_OFFSET_LIMIT 10000000
#define PMC_UPDATE_INTERVAL (60 * NS_PER_SEC)
struct clock;
static int update_sync_offset(struct clock *clock, int64_t offset, uint64_t ts);
static clockid_t clock_open(char *device) static clockid_t clock_open(char *device)
{ {
@ -75,52 +80,6 @@ static clockid_t clock_open(char *device)
return FD_TO_CLOCKID(fd); return FD_TO_CLOCKID(fd);
} }
static void clock_ppb(clockid_t clkid, double ppb)
{
struct timex tx;
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_FREQUENCY;
tx.freq = (long) (ppb * 65.536);
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to adjust the clock: %m");
}
static double clock_ppb_read(clockid_t clkid)
{
double f = 0.0;
struct timex tx;
memset(&tx, 0, sizeof(tx));
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to read out the clock frequency adjustment: %m");
else
f = tx.freq / 65.536;
return f;
}
static void clock_step(clockid_t clkid, int64_t ns)
{
struct timex tx;
int sign = 1;
if (ns < 0) {
sign = -1;
ns *= -1;
}
memset(&tx, 0, sizeof(tx));
tx.modes = ADJ_SETOFFSET | ADJ_NANO;
tx.time.tv_sec = sign * (ns / NS_PER_SEC);
tx.time.tv_usec = sign * (ns % NS_PER_SEC);
/*
* The value of a timeval is the sum of its fields, but the
* field tv_usec must always be non-negative.
*/
if (tx.time.tv_usec < 0) {
tx.time.tv_sec -= 1;
tx.time.tv_usec += 1000000000;
}
if (clock_adjtime(clkid, &tx) < 0)
pr_err("failed to step clock: %m");
}
static int read_phc(clockid_t clkid, clockid_t sysclk, int readings, static int read_phc(clockid_t clkid, clockid_t sysclk, int readings,
int64_t *offset, uint64_t *ts, int64_t *delay) int64_t *offset, uint64_t *ts, int64_t *delay)
{ {
@ -155,11 +114,21 @@ static int read_phc(clockid_t clkid, clockid_t sysclk, int readings,
struct clock { struct clock {
clockid_t clkid; clockid_t clkid;
struct servo *servo; struct servo *servo;
enum servo_state servo_state;
const char *source_label; const char *source_label;
struct stats *offset_stats; struct stats *offset_stats;
struct stats *freq_stats; struct stats *freq_stats;
struct stats *delay_stats; struct stats *delay_stats;
unsigned int stats_max_count; unsigned int stats_max_count;
int sync_offset;
int sync_offset_direction;
int leap;
int leap_set;
int kernel_leap;
struct pmc *pmc;
int pmc_ds_idx;
int pmc_ds_requested;
uint64_t pmc_last_update;
}; };
static void update_clock_stats(struct clock *clock, static void update_clock_stats(struct clock *clock,
@ -203,16 +172,24 @@ static void update_clock(struct clock *clock,
enum servo_state state; enum servo_state state;
double ppb; double ppb;
if (update_sync_offset(clock, offset, ts))
return;
if (clock->sync_offset_direction)
offset += clock->sync_offset * NS_PER_SEC *
clock->sync_offset_direction;
ppb = servo_sample(clock->servo, offset, ts, &state); ppb = servo_sample(clock->servo, offset, ts, &state);
clock->servo_state = state;
switch (state) { switch (state) {
case SERVO_UNLOCKED: case SERVO_UNLOCKED:
break; break;
case SERVO_JUMP: case SERVO_JUMP:
clock_step(clock->clkid, -offset); clockadj_step(clock->clkid, -offset);
/* Fall through. */ /* Fall through. */
case SERVO_LOCKED: case SERVO_LOCKED:
clock_ppb(clock->clkid, -ppb); clockadj_set_freq(clock->clkid, -ppb);
break; break;
} }
@ -253,13 +230,17 @@ static int read_pps(int fd, int64_t *offset, uint64_t *ts)
} }
static int do_pps_loop(struct clock *clock, int fd, static int do_pps_loop(struct clock *clock, int fd,
clockid_t src, int n_readings, int sync_offset) clockid_t src, int n_readings)
{ {
int64_t pps_offset, phc_offset, phc_delay; int64_t pps_offset, phc_offset, phc_delay;
uint64_t pps_ts, phc_ts; uint64_t pps_ts, phc_ts;
clock->source_label = "pps"; clock->source_label = "pps";
/* The sync offset can't be applied with PPS alone. */
if (src == CLOCK_INVALID)
clock->sync_offset_direction = 0;
while (1) { while (1) {
if (!read_pps(fd, &pps_offset, &pps_ts)) { if (!read_pps(fd, &pps_offset, &pps_ts)) {
continue; continue;
@ -284,7 +265,6 @@ static int do_pps_loop(struct clock *clock, int fd,
phc_ts = phc_ts / NS_PER_SEC * NS_PER_SEC; phc_ts = phc_ts / NS_PER_SEC * NS_PER_SEC;
pps_offset = pps_ts - phc_ts; pps_offset = pps_ts - phc_ts;
pps_offset -= sync_offset * NS_PER_SEC;
} }
update_clock(clock, pps_offset, pps_ts, -1); update_clock(clock, pps_offset, pps_ts, -1);
@ -294,7 +274,7 @@ static int do_pps_loop(struct clock *clock, int fd,
} }
static int do_sysoff_loop(struct clock *clock, clockid_t src, static int do_sysoff_loop(struct clock *clock, clockid_t src,
int rate, int n_readings, int sync_offset) int rate, int n_readings)
{ {
uint64_t ts; uint64_t ts;
int64_t offset, delay; int64_t offset, delay;
@ -308,14 +288,13 @@ static int do_sysoff_loop(struct clock *clock, clockid_t src,
err = -1; err = -1;
break; break;
} }
offset -= sync_offset * NS_PER_SEC;
update_clock(clock, offset, ts, delay); update_clock(clock, offset, ts, delay);
} }
return err; return err;
} }
static int do_phc_loop(struct clock *clock, clockid_t src, static int do_phc_loop(struct clock *clock, clockid_t src,
int rate, int n_readings, int sync_offset) int rate, int n_readings)
{ {
uint64_t ts; uint64_t ts;
int64_t offset, delay; int64_t offset, delay;
@ -328,7 +307,6 @@ static int do_phc_loop(struct clock *clock, clockid_t src,
&offset, &ts, &delay)) { &offset, &ts, &delay)) {
continue; continue;
} }
offset -= sync_offset * NS_PER_SEC;
update_clock(clock, offset, ts, delay); update_clock(clock, offset, ts, delay);
} }
return 0; return 0;
@ -360,58 +338,75 @@ static void *get_mgt_data(struct ptp_message *msg)
return ((struct management_tlv *) msg->management.suffix)->data; return ((struct management_tlv *) msg->management.suffix)->data;
} }
static int run_pmc(int wait_sync, int *utc_offset) static int init_pmc(struct clock *clock)
{
clock->pmc = pmc_create(TRANS_UDS, "/var/run/phc2sys", 0, 0, 0);
if (!clock->pmc) {
pr_err("failed to create pmc");
return -1;
}
return 0;
}
static int run_pmc(struct clock *clock, int timeout,
int wait_sync, int get_utc_offset)
{ {
struct ptp_message *msg; struct ptp_message *msg;
struct pmc *pmc;
void *data; void *data;
#define N_FD 1 #define N_FD 1
struct pollfd pollfd[N_FD]; struct pollfd pollfd[N_FD];
int cnt, ds_done;
#define N_ID 2 #define N_ID 2
int cnt, i = 0, ds_done, ds_requested = 0;
int ds_ids[N_ID] = { int ds_ids[N_ID] = {
PORT_DATA_SET, PORT_DATA_SET,
TIME_PROPERTIES_DATA_SET TIME_PROPERTIES_DATA_SET
}; };
pmc = pmc_create(TRANS_UDS, "/var/run/phc2sys", 0, 0, 0); while (clock->pmc_ds_idx < N_ID) {
if (!pmc) { /* Check if the data set is really needed. */
pr_err("failed to create pmc"); if ((ds_ids[clock->pmc_ds_idx] == PORT_DATA_SET &&
return -1; !wait_sync) ||
} (ds_ids[clock->pmc_ds_idx] == TIME_PROPERTIES_DATA_SET &&
!get_utc_offset)) {
clock->pmc_ds_idx++;
continue;
}
while (i < N_ID) { pollfd[0].fd = pmc_get_transport_fd(clock->pmc);
pollfd[0].fd = pmc_get_transport_fd(pmc);
pollfd[0].events = POLLIN|POLLPRI; pollfd[0].events = POLLIN|POLLPRI;
if (!ds_requested) if (!clock->pmc_ds_requested)
pollfd[0].events |= POLLOUT; pollfd[0].events |= POLLOUT;
cnt = poll(pollfd, N_FD, 1000); cnt = poll(pollfd, N_FD, timeout);
if (cnt < 0) { if (cnt < 0) {
pr_err("poll failed"); pr_err("poll failed");
return -1; return -1;
} }
if (!cnt) { if (!cnt) {
/* Request the data set again. */ /* Request the data set again in the next run. */
ds_requested = 0; clock->pmc_ds_requested = 0;
pr_notice("Waiting for ptp4l..."); return 0;
continue;
} }
if (pollfd[0].revents & POLLOUT) { /* Send a new request if there are no pending messages. */
pmc_send_get_action(pmc, ds_ids[i]); if ((pollfd[0].revents & POLLOUT) &&
ds_requested = 1; !(pollfd[0].revents & (POLLIN|POLLPRI))) {
pmc_send_get_action(clock->pmc,
ds_ids[clock->pmc_ds_idx]);
clock->pmc_ds_requested = 1;
} }
if (!(pollfd[0].revents & (POLLIN|POLLPRI))) if (!(pollfd[0].revents & (POLLIN|POLLPRI)))
continue; continue;
msg = pmc_recv(pmc); msg = pmc_recv(clock->pmc);
if (!msg) if (!msg)
continue; continue;
if (!is_msg_mgt(msg) || get_mgt_id(msg) != ds_ids[i]) { if (!is_msg_mgt(msg) ||
get_mgt_id(msg) != ds_ids[clock->pmc_ds_idx]) {
msg_put(msg); msg_put(msg);
continue; continue;
} }
@ -421,9 +416,6 @@ static int run_pmc(int wait_sync, int *utc_offset)
switch (get_mgt_id(msg)) { switch (get_mgt_id(msg)) {
case PORT_DATA_SET: case PORT_DATA_SET:
if (!wait_sync)
ds_done = 1;
switch (((struct portDS *)data)->portState) { switch (((struct portDS *)data)->portState) {
case PS_MASTER: case PS_MASTER:
case PS_SLAVE: case PS_SLAVE:
@ -433,21 +425,86 @@ static int run_pmc(int wait_sync, int *utc_offset)
break; break;
case TIME_PROPERTIES_DATA_SET: case TIME_PROPERTIES_DATA_SET:
*utc_offset = ((struct timePropertiesDS *)data)-> clock->sync_offset = ((struct timePropertiesDS *)data)->
currentUtcOffset; currentUtcOffset;
if (((struct timePropertiesDS *)data)->flags & LEAP_61)
clock->leap = 1;
else if (((struct timePropertiesDS *)data)->flags & LEAP_59)
clock->leap = -1;
else
clock->leap = 0;
ds_done = 1; ds_done = 1;
break; break;
} }
if (ds_done) { if (ds_done) {
/* Proceed with the next data set. */ /* Proceed with the next data set. */
i++; clock->pmc_ds_idx++;
ds_requested = 0; clock->pmc_ds_requested = 0;
} }
msg_put(msg); msg_put(msg);
} }
pmc_destroy(pmc); clock->pmc_ds_idx = 0;
return 1;
}
static void close_pmc(struct clock *clock)
{
pmc_destroy(clock->pmc);
clock->pmc = NULL;
}
static int update_sync_offset(struct clock *clock, int64_t offset, uint64_t ts)
{
int clock_leap;
if (clock->pmc &&
!(ts > clock->pmc_last_update &&
ts - clock->pmc_last_update < PMC_UPDATE_INTERVAL)) {
if (run_pmc(clock, 0, 0, 1) > 0)
clock->pmc_last_update = ts;
}
/* Handle leap seconds. */
if (!clock->leap && !clock->leap_set)
return 0;
/* If the system clock is the master clock, get a time stamp from
it, as it is the clock which will include the leap second. */
if (clock->clkid != CLOCK_REALTIME) {
struct timespec tp;
if (clock_gettime(CLOCK_REALTIME, &tp)) {
pr_err("failed to read clock: %m");
return -1;
}
ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec;
}
/* If the clock will be stepped, the time stamp has to be the
target time. Ignore possible 1 second error in UTC offset. */
if (clock->clkid == CLOCK_REALTIME &&
clock->servo_state == SERVO_UNLOCKED) {
ts -= offset + clock->sync_offset * NS_PER_SEC *
clock->sync_offset_direction;
}
/* Suspend clock updates in the last second before midnight. */
if (is_utc_ambiguous(ts)) {
pr_info("clock update suspended due to leap second");
return -1;
}
clock_leap = leap_second_status(ts, clock->leap_set,
&clock->leap, &clock->sync_offset);
if (clock->leap_set != clock_leap) {
/* Only the system clock can leap. */
if (clock->clkid == CLOCK_REALTIME && clock->kernel_leap)
clockadj_set_leap(clock->clkid, clock_leap);
clock->leap_set = clock_leap;
}
return 0; return 0;
} }
@ -469,6 +526,7 @@ static void usage(char *progname)
" -O [offset] slave-master time offset (0)\n" " -O [offset] slave-master time offset (0)\n"
" -u [num] number of clock updates in summary stats (0)\n" " -u [num] number of clock updates in summary stats (0)\n"
" -w wait for ptp4l\n" " -w wait for ptp4l\n"
" -x apply leap seconds by servo instead of kernel\n"
" -h prints this message and exits\n" " -h prints this message and exits\n"
" -v prints the software version and exits\n" " -v prints the software version and exits\n"
"\n", "\n",
@ -479,11 +537,15 @@ int main(int argc, char *argv[])
{ {
char *progname, *ethdev = NULL; char *progname, *ethdev = NULL;
clockid_t src = CLOCK_INVALID; clockid_t src = CLOCK_INVALID;
int c, phc_readings = 5, phc_rate = 1, sync_offset = 0, pps_fd = -1; int c, phc_readings = 5, phc_rate = 1, pps_fd = -1;
int wait_sync = 0, forced_sync_offset = 0; int r, wait_sync = 0, forced_sync_offset = 0;
int print_level = LOG_INFO, use_syslog = 1, verbose = 0; int print_level = LOG_INFO, use_syslog = 1, verbose = 0;
double ppb; double ppb;
struct clock dst_clock = { .clkid = CLOCK_REALTIME }; struct clock dst_clock = {
.clkid = CLOCK_REALTIME,
.servo_state = SERVO_UNLOCKED,
.kernel_leap = 1,
};
configured_pi_kp = KP; configured_pi_kp = KP;
configured_pi_ki = KI; configured_pi_ki = KI;
@ -492,7 +554,7 @@ int main(int argc, char *argv[])
progname = strrchr(argv[0], '/'); progname = strrchr(argv[0], '/');
progname = progname ? 1+progname : argv[0]; progname = progname ? 1+progname : argv[0];
while (EOF != (c = getopt(argc, argv, while (EOF != (c = getopt(argc, argv,
"c:d:hs:P:I:S:R:N:O:i:u:wl:mqv"))) { "c:d:hs:P:I:S:R:N:O:i:u:wxl:mqv"))) {
switch (c) { switch (c) {
case 'c': case 'c':
dst_clock.clkid = clock_open(optarg); dst_clock.clkid = clock_open(optarg);
@ -524,7 +586,8 @@ int main(int argc, char *argv[])
phc_readings = atoi(optarg); phc_readings = atoi(optarg);
break; break;
case 'O': case 'O':
sync_offset = atoi(optarg); dst_clock.sync_offset = atoi(optarg);
dst_clock.sync_offset_direction = -1;
forced_sync_offset = 1; forced_sync_offset = 1;
break; break;
case 'i': case 'i':
@ -536,6 +599,9 @@ int main(int argc, char *argv[])
case 'w': case 'w':
wait_sync = 1; wait_sync = 1;
break; break;
case 'x':
dst_clock.kernel_leap = 0;
break;
case 'l': case 'l':
print_level = atoi(optarg); print_level = atoi(optarg);
break; break;
@ -596,36 +662,49 @@ int main(int argc, char *argv[])
print_set_level(print_level); print_set_level(print_level);
if (wait_sync) { if (wait_sync) {
int ptp_utc_offset; if (init_pmc(&dst_clock))
return -1;
run_pmc(wait_sync, &ptp_utc_offset); while (1) {
r = run_pmc(&dst_clock, 1000,
wait_sync, !forced_sync_offset);
if (r < 0)
return -1;
else if (r > 0)
break;
else
pr_notice("Waiting for ptp4l...");
}
if (!forced_sync_offset) { if (!forced_sync_offset) {
if (src != CLOCK_REALTIME && if (src != CLOCK_REALTIME &&
dst_clock.clkid == CLOCK_REALTIME) dst_clock.clkid == CLOCK_REALTIME)
sync_offset = -ptp_utc_offset; dst_clock.sync_offset_direction = 1;
else if (src == CLOCK_REALTIME && else if (src == CLOCK_REALTIME &&
dst_clock.clkid != CLOCK_REALTIME) dst_clock.clkid != CLOCK_REALTIME)
sync_offset = ptp_utc_offset; dst_clock.sync_offset_direction = -1;
else
dst_clock.sync_offset_direction = 0;
} }
if (forced_sync_offset || !dst_clock.sync_offset_direction)
close_pmc(&dst_clock);
} }
ppb = clock_ppb_read(dst_clock.clkid); ppb = clockadj_get_freq(dst_clock.clkid);
/* The reading may silently fail and return 0, reset the frequency to /* The reading may silently fail and return 0, reset the frequency to
make sure ppb is the actual frequency of the clock. */ make sure ppb is the actual frequency of the clock. */
clock_ppb(dst_clock.clkid, ppb); clockadj_set_freq(dst_clock.clkid, ppb);
clockadj_set_leap(dst_clock.clkid, 0);
dst_clock.servo = servo_create(CLOCK_SERVO_PI, -ppb, max_ppb, 0); dst_clock.servo = servo_create(CLOCK_SERVO_PI, -ppb, max_ppb, 0);
if (pps_fd >= 0) if (pps_fd >= 0)
return do_pps_loop(&dst_clock, pps_fd, src, return do_pps_loop(&dst_clock, pps_fd, src, phc_readings);
phc_readings, sync_offset);
if (dst_clock.clkid == CLOCK_REALTIME && if (dst_clock.clkid == CLOCK_REALTIME &&
SYSOFF_SUPPORTED == sysoff_probe(CLOCKID_TO_FD(src), phc_readings)) SYSOFF_SUPPORTED == sysoff_probe(CLOCKID_TO_FD(src), phc_readings))
return do_sysoff_loop(&dst_clock, src, phc_rate, return do_sysoff_loop(&dst_clock, src, phc_rate, phc_readings);
phc_readings, sync_offset);
return do_phc_loop(&dst_clock, src, phc_rate, return do_phc_loop(&dst_clock, src, phc_rate, phc_readings);
phc_readings, sync_offset);
} }

View File

@ -351,6 +351,14 @@ empty string.
.B manufacturerIdentity .B manufacturerIdentity
The manufacturer id which should be an OUI owned by the manufacturer. The manufacturer id which should be an OUI owned by the manufacturer.
The default is 00:00:00. The default is 00:00:00.
.TP
.B kernel_leap
When a leap second is announced, let the kernel apply it by stepping the clock
instead of correcting the one-second offset with servo, which would correct the
one-second offset slowly by changing the clock frequency (unless the
.B pi_offset_const
option is set to correct such offset by stepping).
Relevant only with software time stamping. The default is 1 (enabled).
.SH SEE ALSO .SH SEE ALSO
.BR pmc (8), .BR pmc (8),

View File

@ -53,6 +53,7 @@ static struct config cfg_settings = {
.free_running = 0, .free_running = 0,
.freq_est_interval = 1, .freq_est_interval = 1,
.stats_interval = 0, .stats_interval = 0,
.kernel_leap = 1,
.clock_desc = { .clock_desc = {
.productDescription = { .productDescription = {
.max_symbols = 64, .max_symbols = 64,

41
util.c
View File

@ -22,6 +22,10 @@
#include "sk.h" #include "sk.h"
#include "util.h" #include "util.h"
#define NS_PER_SEC 1000000000LL
#define NS_PER_HOUR (3600 * NS_PER_SEC)
#define NS_PER_DAY (24 * NS_PER_HOUR)
char *ps_str[] = { char *ps_str[] = {
"NONE", "NONE",
"INITIALIZING", "INITIALIZING",
@ -149,3 +153,40 @@ int static_ptp_text_set(struct static_ptp_text *dst, const char *src)
dst->text[len] = '\0'; dst->text[len] = '\0';
return 0; return 0;
} }
int is_utc_ambiguous(uint64_t ts)
{
/* The Linux kernel inserts leap second by stepping the clock backwards
at 0:00 UTC, the last second before midnight is played twice. */
if (NS_PER_DAY - ts % NS_PER_DAY <= NS_PER_SEC)
return 1;
return 0;
}
int leap_second_status(uint64_t ts, int leap_set, int *leap, int *utc_offset)
{
int leap_status = leap_set;
/* The leap bits obtained by PTP should be set at most 12 hours before
midnight and unset at most 2 announce intervals after midnight.
Split updates which are too early and which are too late at 6 hours
after midnight. */
if (ts % NS_PER_DAY > 6 * NS_PER_HOUR) {
if (!leap_status)
leap_status = *leap;
} else {
if (leap_status)
leap_status = 0;
}
/* Fix early or late update of leap and utc_offset. */
if (!*leap && leap_status) {
*utc_offset -= leap_status;
*leap = leap_status;
} else if (*leap && !leap_status) {
*utc_offset += *leap;
*leap = leap_status;
}
return leap_status;
}

20
util.h
View File

@ -94,4 +94,24 @@ int ptp_text_set(struct PTPText *dst, const char *src);
*/ */
int static_ptp_text_set(struct static_ptp_text *dst, const char *src); int static_ptp_text_set(struct static_ptp_text *dst, const char *src);
/**
* Check if UTC time stamp can be both before and after a leap second.
*
* @param ts UTC time stamp in nanoseconds.
* @return 0 if not, 1 if yes.
*/
int is_utc_ambiguous(uint64_t ts);
/**
* Get leap second status in given time.
*
* @param ts UTC time stamp in nanoseconds.
* @param leap_set Previous leap second status (+1/0/-1).
* @param leap Announced leap second (+1/0/-1), will be corrected if
* early/late.
* @param utc_offset Announced UTC offset, will be corrected if early/late.
* @return 0 if the leap second passed, +1 if leap second will be
* inserted, -1 if leap second will be deleted.
*/
int leap_second_status(uint64_t ts, int leap_set, int *leap, int *utc_offset);
#endif #endif