Merge branch 'mlichvar_leap'
Fixed up trivial conflict in the makefile. Conflicts: makefilemaster
commit
bd28acffb3
150
clock.c
150
clock.c
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
5
config.c
5
config.c
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
1
ds.h
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
1
gPTP.cfg
1
gPTP.cfg
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
10
makefile
10
makefile
|
@ -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
|
||||||
|
|
||||||
|
|
24
phc2sys.8
24
phc2sys.8
|
@ -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
281
phc2sys.c
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
8
ptp4l.8
8
ptp4l.8
|
@ -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),
|
||||||
|
|
1
ptp4l.c
1
ptp4l.c
|
@ -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
41
util.c
|
@ -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
20
util.h
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue