Discussion:
[Linuxptp-devel] [PATCH 0/6] Leap second support
Miroslav Lichvar
2013-03-07 16:27:27 UTC
Permalink
This set adds support for handling leap seconds announced in the time
properties data set. It either sets the kernel leap bits or lets the
servo slowly correct the offset instead of stepping the clock.

I have used this patch to test the new code:

--- a/clock.c
+++ b/clock.c
@@ -1020,6 +1020,36 @@ enum servo_state clock_synchronize(struct clock *c,
c->master_offset = tmv_sub(ingress,
tmv_add(origin, tmv_add(c->path_delay, tmv_add(c->c1, c->c2))));

+#if 1
+ static int leaptest_offset = 0;
+ time_t t = ingress_ts.tv_sec - 35;
+
+ if (!leaptest_offset) {
+#define SEC_PER_DAY (24 * 3600)
+ leaptest_offset = SEC_PER_DAY - 30 - (t - c->master_offset / NS_PER_SEC) % SEC_PER_DAY;
+ pr_info("leaptest offset: %f hours", leaptest_offset / 3600.0);
+ }
+ c->master_offset -= leaptest_offset * NS_PER_SEC;
+ t -= c->master_offset / NS_PER_SEC;
+
+ if (SEC_PER_DAY - (t + 15) % SEC_PER_DAY < 3600) {
+#if 1
+ c->tds.flags |= LEAP_61;
+ c->tds.currentUtcOffset = 35;
+ } else {
+ c->tds.flags &= ~LEAP_61;
+ c->tds.currentUtcOffset = 36;
+#else
+ c->tds.flags |= LEAP_59;
+ c->tds.currentUtcOffset = 35;
+ } else {
+ c->tds.flags &= ~LEAP_59;
+ c->tds.currentUtcOffset = 34;
+#endif
+ }
+ pr_info("flags: %d currentUtcOffset: %d", c->tds.flags & (LEAP_61|LEAP_59), c->tds.currentUtcOffset);
+#endif
+
if (!c->path_delay)
return state;



Miroslav Lichvar (6):
phc2sys: Update sync offset periodically with -w.
Move clock_adjtime wrapping to clockadj.c.
phc2sys: Handle leap seconds.
Add missing conversions from tmv_t to nanoseconds.
ptp4l: Handle leap seconds.
Add options to not apply leap seconds in kernel.

clock.c | 150 +++++++++++++++++---------------
clockadj.c | 96 +++++++++++++++++++++
clockadj.h | 54 ++++++++++++
config.c | 5 ++
default.cfg | 1 +
ds.h | 1 +
gPTP.cfg | 1 +
makefile | 10 +--
phc2sys.8 | 24 ++++--
phc2sys.c | 280 ++++++++++++++++++++++++++++++++++++++----------------------
ptp4l.8 | 8 ++
ptp4l.c | 1 +
util.c | 41 +++++++++
util.h | 20 +++++
14 files changed, 511 insertions(+), 181 deletions(-)
create mode 100644 clockadj.c
create mode 100644 clockadj.h
--
1.8.1.2
Miroslav Lichvar
2013-03-07 16:27:30 UTC
Permalink
Update the currentUtcOffset and leap61/59 values at one minute
interval. When a leap second is detected, set the STA_INS/STA_DEL
bit for the system clock.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
clockadj.c | 24 ++++++++++++++++++++
clockadj.h | 7 ++++++
phc2sys.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
util.c | 41 ++++++++++++++++++++++++++++++++++
util.h | 20 +++++++++++++++++
5 files changed, 161 insertions(+), 6 deletions(-)

diff --git a/clockadj.c b/clockadj.c
index a2b55fd..31fc765 100644
--- a/clockadj.c
+++ b/clockadj.c
@@ -70,3 +70,27 @@ void clockadj_step(clockid_t clkid, int64_t step)
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);
+}
diff --git a/clockadj.h b/clockadj.h
index b40d7a2..6e76e0f 100644
--- a/clockadj.h
+++ b/clockadj.h
@@ -44,4 +44,11 @@ double clockadj_get_freq(clockid_t clkid);
*/
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
diff --git a/phc2sys.c b/phc2sys.c
index ac036dd..678b0f3 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -45,6 +45,7 @@
#include "stats.h"
#include "sysoff.h"
#include "tlv.h"
+#include "util.h"
#include "version.h"

#define KP 0.7
@@ -54,9 +55,10 @@
#define max_ppb 512000

#define PHC_PPS_OFFSET_LIMIT 10000000
+#define PMC_UPDATE_INTERVAL (60 * NS_PER_SEC)

struct clock;
-static int update_sync_offset(struct clock *clock);
+static int update_sync_offset(struct clock *clock, int64_t offset, uint64_t ts);

static clockid_t clock_open(char *device)
{
@@ -112,6 +114,7 @@ static int read_phc(clockid_t clkid, clockid_t sysclk, int readings,
struct clock {
clockid_t clkid;
struct servo *servo;
+ enum servo_state servo_state;
const char *source_label;
struct stats *offset_stats;
struct stats *freq_stats;
@@ -119,9 +122,12 @@ struct clock {
unsigned int stats_max_count;
int sync_offset;
int sync_offset_direction;
+ int leap;
+ int leap_set;
struct pmc *pmc;
int pmc_ds_idx;
int pmc_ds_requested;
+ uint64_t pmc_last_update;
};

static void update_clock_stats(struct clock *clock,
@@ -165,7 +171,7 @@ static void update_clock(struct clock *clock,
enum servo_state state;
double ppb;

- if (update_sync_offset(clock))
+ if (update_sync_offset(clock, offset, ts))
return;

if (clock->sync_offset_direction)
@@ -173,6 +179,7 @@ static void update_clock(struct clock *clock,
clock->sync_offset_direction;

ppb = servo_sample(clock->servo, offset, ts, &state);
+ clock->servo_state = state;

switch (state) {
case SERVO_UNLOCKED:
@@ -419,6 +426,12 @@ static int run_pmc(struct clock *clock, int timeout,
case TIME_PROPERTIES_DATA_SET:
clock->sync_offset = ((struct timePropertiesDS *)data)->
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;
break;
}
@@ -441,11 +454,57 @@ static void close_pmc(struct clock *clock)
clock->pmc = NULL;
}

-static int update_sync_offset(struct clock *clock)
+static int update_sync_offset(struct clock *clock, int64_t offset, uint64_t ts)
{
- if (clock->pmc) {
- run_pmc(clock, 0, 0, 1);
+ 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)
+ clockadj_set_leap(clock->clkid, clock_leap);
+ clock->leap_set = clock_leap;
+ }
+
return 0;
}

@@ -480,7 +539,10 @@ int main(int argc, char *argv[])
int r, wait_sync = 0, forced_sync_offset = 0;
int print_level = LOG_INFO, use_syslog = 1, verbose = 0;
double ppb;
- struct clock dst_clock = { .clkid = CLOCK_REALTIME };
+ struct clock dst_clock = {
+ .clkid = CLOCK_REALTIME,
+ .servo_state = SERVO_UNLOCKED,
+ };

configured_pi_kp = KP;
configured_pi_ki = KI;
@@ -627,6 +689,7 @@ int main(int argc, char *argv[])
/* The reading may silently fail and return 0, reset the frequency to
make sure ppb is the actual frequency of the clock. */
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);

diff --git a/util.c b/util.c
index 7699fac..55ec49f 100644
--- a/util.c
+++ b/util.c
@@ -22,6 +22,10 @@
#include "sk.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[] = {
"NONE",
"INITIALIZING",
@@ -149,3 +153,40 @@ int static_ptp_text_set(struct static_ptp_text *dst, const char *src)
dst->text[len] = '\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;
+}
diff --git a/util.h b/util.h
index a2d7089..d2734d1 100644
--- a/util.h
+++ b/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);

+/**
+ * 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
--
1.8.1.2
Miroslav Lichvar
2013-03-07 16:27:28 UTC
Permalink
Modify the pmc to allow non-blocking operation. Run it on each clock
update to have the sync offset updated from currentUtcOffset with every
other call.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
phc2sys.8 | 15 ++++---
phc2sys.c | 152 ++++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 112 insertions(+), 55 deletions(-)

diff --git a/phc2sys.8 b/phc2sys.8
index 0542ba7..10faea9 100644
--- a/phc2sys.8
+++ b/phc2sys.8
@@ -106,13 +106,10 @@ minimize the error caused by random delays in scheduling and bus utilization.
The default is 5.
.TP
.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
-option the default value is set automatically according to the currentUtcOffset
-value obtained from ptp4l and the direction of the clock synchronization.
-Without
-.B \-w
-the default is 0.
+option, 0 otherwise.
.TP
.BI \-u " summary-updates"
Specify the number of clock updates included in summary statistics. The
@@ -124,7 +121,11 @@ statistics. The messages are printed at the LOG_INFO level.
The default is 0 (disabled).
.TP
.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
.BI \-l " print-level"
Set the maximum syslog level of messages which should be printed or sent to
diff --git a/phc2sys.c b/phc2sys.c
index 582492d..a600709 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -55,6 +55,9 @@

#define PHC_PPS_OFFSET_LIMIT 10000000

+struct clock;
+static int update_sync_offset(struct clock *clock);
+
static clockid_t clock_open(char *device)
{
int fd;
@@ -160,6 +163,11 @@ struct clock {
struct stats *freq_stats;
struct stats *delay_stats;
unsigned int stats_max_count;
+ int sync_offset;
+ int sync_offset_direction;
+ struct pmc *pmc;
+ int pmc_ds_idx;
+ int pmc_ds_requested;
};

static void update_clock_stats(struct clock *clock,
@@ -203,6 +211,13 @@ static void update_clock(struct clock *clock,
enum servo_state state;
double ppb;

+ if (update_sync_offset(clock))
+ 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);

switch (state) {
@@ -253,13 +268,17 @@ static int read_pps(int fd, int64_t *offset, uint64_t *ts)
}

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;
uint64_t pps_ts, phc_ts;

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) {
if (!read_pps(fd, &pps_offset, &pps_ts)) {
continue;
@@ -284,7 +303,6 @@ static int do_pps_loop(struct clock *clock, int fd,

phc_ts = phc_ts / NS_PER_SEC * NS_PER_SEC;
pps_offset = pps_ts - phc_ts;
- pps_offset -= sync_offset * NS_PER_SEC;
}

update_clock(clock, pps_offset, pps_ts, -1);
@@ -294,7 +312,7 @@ static int do_pps_loop(struct clock *clock, int fd,
}

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;
int64_t offset, delay;
@@ -308,14 +326,13 @@ static int do_sysoff_loop(struct clock *clock, clockid_t src,
err = -1;
break;
}
- offset -= sync_offset * NS_PER_SEC;
update_clock(clock, offset, ts, delay);
}
return err;
}

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;
int64_t offset, delay;
@@ -328,7 +345,6 @@ static int do_phc_loop(struct clock *clock, clockid_t src,
&offset, &ts, &delay)) {
continue;
}
- offset -= sync_offset * NS_PER_SEC;
update_clock(clock, offset, ts, delay);
}
return 0;
@@ -360,58 +376,75 @@ static void *get_mgt_data(struct ptp_message *msg)
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 pmc *pmc;
void *data;
#define N_FD 1
struct pollfd pollfd[N_FD];
+ int cnt, ds_done;
#define N_ID 2
- int cnt, i = 0, ds_done, ds_requested = 0;
int ds_ids[N_ID] = {
PORT_DATA_SET,
TIME_PROPERTIES_DATA_SET
};

- pmc = pmc_create(TRANS_UDS, "/var/run/phc2sys", 0, 0, 0);
- if (!pmc) {
- pr_err("failed to create pmc");
- return -1;
- }
+ while (clock->pmc_ds_idx < N_ID) {
+ /* Check if the data set is really needed. */
+ if ((ds_ids[clock->pmc_ds_idx] == PORT_DATA_SET &&
+ !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(pmc);
+ pollfd[0].fd = pmc_get_transport_fd(clock->pmc);
pollfd[0].events = POLLIN|POLLPRI;
- if (!ds_requested)
+ if (!clock->pmc_ds_requested)
pollfd[0].events |= POLLOUT;

- cnt = poll(pollfd, N_FD, 1000);
+ cnt = poll(pollfd, N_FD, timeout);
if (cnt < 0) {
pr_err("poll failed");
return -1;
}
if (!cnt) {
- /* Request the data set again. */
- ds_requested = 0;
- pr_notice("Waiting for ptp4l...");
- continue;
+ /* Request the data set again in the next run. */
+ clock->pmc_ds_requested = 0;
+ return 0;
}

- if (pollfd[0].revents & POLLOUT) {
- pmc_send_get_action(pmc, ds_ids[i]);
- ds_requested = 1;
+ /* Send a new request if there are no pending messages. */
+ if ((pollfd[0].revents & POLLOUT) &&
+ !(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)))
continue;

- msg = pmc_recv(pmc);
+ msg = pmc_recv(clock->pmc);

if (!msg)
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);
continue;
}
@@ -421,9 +454,6 @@ static int run_pmc(int wait_sync, int *utc_offset)

switch (get_mgt_id(msg)) {
case PORT_DATA_SET:
- if (!wait_sync)
- ds_done = 1;
-
switch (((struct portDS *)data)->portState) {
case PS_MASTER:
case PS_SLAVE:
@@ -433,22 +463,35 @@ static int run_pmc(int wait_sync, int *utc_offset)

break;
case TIME_PROPERTIES_DATA_SET:
- *utc_offset = ((struct timePropertiesDS *)data)->
- currentUtcOffset;
+ clock->sync_offset = ((struct timePropertiesDS *)data)->
+ currentUtcOffset;
ds_done = 1;
break;
}

if (ds_done) {
/* Proceed with the next data set. */
- i++;
- ds_requested = 0;
+ clock->pmc_ds_idx++;
+ clock->pmc_ds_requested = 0;
}
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)
+{
+ if (clock->pmc) {
+ run_pmc(clock, 0, 0, 1);
+ }
return 0;
}

@@ -479,8 +522,8 @@ int main(int argc, char *argv[])
{
char *progname, *ethdev = NULL;
clockid_t src = CLOCK_INVALID;
- int c, phc_readings = 5, phc_rate = 1, sync_offset = 0, pps_fd = -1;
- int wait_sync = 0, forced_sync_offset = 0;
+ int c, phc_readings = 5, phc_rate = 1, pps_fd = -1;
+ int r, wait_sync = 0, forced_sync_offset = 0;
int print_level = LOG_INFO, use_syslog = 1, verbose = 0;
double ppb;
struct clock dst_clock = { .clkid = CLOCK_REALTIME };
@@ -524,7 +567,8 @@ int main(int argc, char *argv[])
phc_readings = atoi(optarg);
break;
case 'O':
- sync_offset = atoi(optarg);
+ dst_clock.sync_offset = atoi(optarg);
+ dst_clock.sync_offset_direction = -1;
forced_sync_offset = 1;
break;
case 'i':
@@ -596,18 +640,33 @@ int main(int argc, char *argv[])
print_set_level(print_level);

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 (src != CLOCK_REALTIME &&
dst_clock.clkid == CLOCK_REALTIME)
- sync_offset = -ptp_utc_offset;
+ dst_clock.sync_offset_direction = 1;
else if (src == 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);
@@ -618,14 +677,11 @@ int main(int argc, char *argv[])
dst_clock.servo = servo_create(CLOCK_SERVO_PI, -ppb, max_ppb, 0);

if (pps_fd >= 0)
- return do_pps_loop(&dst_clock, pps_fd, src,
- phc_readings, sync_offset);
+ return do_pps_loop(&dst_clock, pps_fd, src, phc_readings);

if (dst_clock.clkid == CLOCK_REALTIME &&
SYSOFF_SUPPORTED == sysoff_probe(CLOCKID_TO_FD(src), phc_readings))
- return do_sysoff_loop(&dst_clock, src, phc_rate,
- phc_readings, sync_offset);
+ return do_sysoff_loop(&dst_clock, src, phc_rate, phc_readings);

- return do_phc_loop(&dst_clock, src, phc_rate,
- phc_readings, sync_offset);
+ return do_phc_loop(&dst_clock, src, phc_rate, phc_readings);
}
--
1.8.1.2
Miroslav Lichvar
2013-03-07 16:27:29 UTC
Permalink
Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
clock.c | 55 +++++------------------------------------------
clockadj.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
clockadj.h | 47 ++++++++++++++++++++++++++++++++++++++++
makefile | 10 ++++-----
phc2sys.c | 56 +++++-------------------------------------------
5 files changed, 134 insertions(+), 106 deletions(-)
create mode 100644 clockadj.c
create mode 100644 clockadj.h

diff --git a/clock.c b/clock.c
index 96be630..71c4791 100644
--- a/clock.c
+++ b/clock.c
@@ -24,6 +24,7 @@

#include "bmc.h"
#include "clock.h"
+#include "clockadj.h"
#include "foreign.h"
#include "mave.h"
#include "missing.h"
@@ -404,52 +405,6 @@ static enum servo_state clock_no_adjust(struct clock *c)
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)
{
struct parentDS *pds = &c->dad.pds;
@@ -580,7 +535,7 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
}

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);
if (!c->servo) {
@@ -1035,13 +990,13 @@ enum servo_state clock_synchronize(struct clock *c,
case SERVO_UNLOCKED:
break;
case SERVO_JUMP:
- clock_ppb(c->clkid, -adj);
- clock_step(c->clkid, -c->master_offset);
+ clockadj_set_freq(c->clkid, -adj);
+ clockadj_step(c->clkid, -c->master_offset);
c->t1 = tmv_zero();
c->t2 = tmv_zero();
break;
case SERVO_LOCKED:
- clock_ppb(c->clkid, -adj);
+ clockadj_set_freq(c->clkid, -adj);
break;
}
return state;
diff --git a/clockadj.c b/clockadj.c
new file mode 100644
index 0000000..a2b55fd
--- /dev/null
+++ b/clockadj.c
@@ -0,0 +1,72 @@
+/**
+ * @file clockadj.c
+ * @note Copyright (C) 2013 Richard Cochran <***@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");
+}
diff --git a/clockadj.h b/clockadj.h
new file mode 100644
index 0000000..b40d7a2
--- /dev/null
+++ b/clockadj.h
@@ -0,0 +1,47 @@
+/**
+ * @file clockadj.h
+ * @brief Wraps clock_adjtime functionality.
+ * @note Copyright (C) 2013 Miroslav Lichvar <***@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);
+
+#endif
diff --git a/makefile b/makefile
index 671de48..e9ecb03 100644
--- a/makefile
+++ b/makefile
@@ -29,9 +29,9 @@ VER = -DVER=$(version)
CFLAGS = -Wall $(VER) $(INC) $(DEBUG) $(FEAT_CFLAGS) $(EXTRA_CFLAGS)
LDLIBS = -lm -lrt $(EXTRA_LDFLAGS)
PRG = ptp4l pmc phc2sys hwstamp_ctl
-OBJ = bmc.o clock.o config.o fsm.o ptp4l.o mave.o msg.o phc.o pi.o port.o \
- print.o raw.o servo.o sk.o stats.o tlv.o tmtab.o transport.o udp.o udp6.o \
- uds.o util.o version.o
+OBJ = bmc.o clock.o clockadj.o config.o fsm.o ptp4l.o mave.o msg.o phc.o \
+ pi.o port.o print.o raw.o servo.o sk.o stats.o tlv.o tmtab.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
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 \
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 \
- sysoff.o tlv.o transport.o udp.o udp6.o uds.o util.o version.o
+phc2sys: clockadj.o msg.o phc2sys.o pmc_common.o print.o pi.o servo.o raw.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

diff --git a/phc2sys.c b/phc2sys.c
index a600709..ac036dd 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -27,13 +27,13 @@
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <time.h>
#include <unistd.h>
#include <inttypes.h>

#include <linux/pps.h>
#include <linux/ptp_clock.h>

+#include "clockadj.h"
#include "ds.h"
#include "fsm.h"
#include "missing.h"
@@ -78,52 +78,6 @@ static clockid_t clock_open(char *device)
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,
int64_t *offset, uint64_t *ts, int64_t *delay)
{
@@ -224,10 +178,10 @@ static void update_clock(struct clock *clock,
case SERVO_UNLOCKED:
break;
case SERVO_JUMP:
- clock_step(clock->clkid, -offset);
+ clockadj_step(clock->clkid, -offset);
/* Fall through. */
case SERVO_LOCKED:
- clock_ppb(clock->clkid, -ppb);
+ clockadj_set_freq(clock->clkid, -ppb);
break;
}

@@ -669,10 +623,10 @@ int main(int argc, char *argv[])
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
make sure ppb is the actual frequency of the clock. */
- clock_ppb(dst_clock.clkid, ppb);
+ clockadj_set_freq(dst_clock.clkid, ppb);

dst_clock.servo = servo_create(CLOCK_SERVO_PI, -ppb, max_ppb, 0);
--
1.8.1.2
Miroslav Lichvar
2013-03-07 16:27:31 UTC
Permalink
Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
clock.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/clock.c b/clock.c
index 71c4791..9a9e435 100644
--- a/clock.c
+++ b/clock.c
@@ -382,11 +382,13 @@ static enum servo_state clock_no_adjust(struct clock *c)
freq = (1.0 - ratio) * 1e9;

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 {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"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;
@@ -905,7 +907,7 @@ void clock_path_delay(struct clock *c, struct timespec req, struct timestamp rx,
pr_debug("path delay %10lld %10lld", c->path_delay, pd);

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)
@@ -914,7 +916,7 @@ void clock_peer_delay(struct clock *c, tmv_t ppd, double nrr)
c->nrr = nrr;

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)
@@ -976,14 +978,17 @@ enum servo_state clock_synchronize(struct clock *c,
if (c->free_running)
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);

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 {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"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) {
@@ -991,7 +996,7 @@ enum servo_state clock_synchronize(struct clock *c,
break;
case SERVO_JUMP:
clockadj_set_freq(c->clkid, -adj);
- clockadj_step(c->clkid, -c->master_offset);
+ clockadj_step(c->clkid, -tmv_to_nanoseconds(c->master_offset));
c->t1 = tmv_zero();
c->t2 = tmv_zero();
break;
--
1.8.1.2
Miroslav Lichvar
2013-03-07 16:27:32 UTC
Permalink
Extend the clock_utc_correct function to handle leap seconds announced
in the time properties data set. With software time stamping, it sets the
STA_INS/STA_DEL bit for the system clock. Clock updates are suspended
in the last second of the day.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
clock.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 62 insertions(+), 11 deletions(-)

diff --git a/clock.c b/clock.c
index 9a9e435..f608236 100644
--- a/clock.c
+++ b/clock.c
@@ -78,6 +78,8 @@ struct clock {
int free_running;
int freq_est_interval;
int utc_timescale;
+ int leap_set;
+ enum servo_state servo_state;
tmv_t master_offset;
tmv_t path_delay;
struct mave *avg_delay;
@@ -449,23 +451,66 @@ 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;
+ int utc_offset, leap, clock_leap;
+ uint64_t ts;
+
if (!c->utc_timescale)
- return;
- if (!(c->tds.flags & PTP_TIMESCALE))
- return;
+ return 0;
+
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) {
- offset.tv_sec = c->tds.currentUtcOffset;
+ utc_offset = c->tds.currentUtcOffset;
} 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) {
+ 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;
/* Local clock is UTC, but master is TAI. */
c->master_offset = tmv_add(c->master_offset, timespec_to_tmv(offset));
+ return 0;
}

static int forwarding(struct clock *c, struct port *p)
@@ -534,7 +579,10 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
c->clkid = CLOCK_REALTIME;
c->utc_timescale = 1;
max_adj = 512000;
+ clockadj_set_leap(c->clkid, 0);
}
+ c->leap_set = 0;
+ c->kernel_leap = dds->kernel_leap;

if (c->clkid != CLOCK_INVALID) {
fadj = (int) clockadj_get_freq(c->clkid);
@@ -544,6 +592,7 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
pr_err("Failed to create clock servo");
return NULL;
}
+ c->servo_state = SERVO_UNLOCKED;
c->avg_delay = mave_create(MAVE_LENGTH);
if (!c->avg_delay) {
pr_err("Failed to create moving average");
@@ -968,18 +1017,20 @@ enum servo_state clock_synchronize(struct clock *c,
c->master_offset = tmv_sub(ingress,
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)
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)
return clock_no_adjust(c);

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) {
clock_stats_update(&c->stats,
--
1.8.1.2
Miroslav Lichvar
2013-03-07 16:27:33 UTC
Permalink
Add kernel_leap option for ptp4l and -x option for phc2sys to disable
setting of the STA_INS/STA_DEL bit to slowly correct the one-second
offset by servo.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
clock.c | 5 ++++-
config.c | 5 +++++
default.cfg | 1 +
ds.h | 1 +
gPTP.cfg | 1 +
phc2sys.8 | 9 +++++++++
phc2sys.c | 10 ++++++++--
ptp4l.8 | 8 ++++++++
ptp4l.c | 1 +
9 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/clock.c b/clock.c
index f608236..6e0b0d9 100644
--- a/clock.c
+++ b/clock.c
@@ -79,6 +79,7 @@ struct clock {
int freq_est_interval;
int utc_timescale;
int leap_set;
+ int kernel_leap;
enum servo_state servo_state;
tmv_t master_offset;
tmv_t path_delay;
@@ -498,7 +499,8 @@ static int clock_utc_correct(struct clock *c, tmv_t ingress)
clock_leap = leap_second_status(ts, c->leap_set,
&leap, &utc_offset);
if (c->leap_set != clock_leap) {
- clockadj_set_leap(c->clkid, clock_leap);
+ if (c->kernel_leap)
+ clockadj_set_leap(c->clkid, clock_leap);
c->leap_set = clock_leap;
}
}
@@ -559,6 +561,7 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,

c->free_running = dds->free_running;
c->freq_est_interval = dds->freq_est_interval;
+ c->kernel_leap = dds->kernel_leap;
c->desc = dds->clock_desc;

if (c->free_running) {
diff --git a/config.c b/config.c
index dd96fc1..7991378 100644
--- a/config.c
+++ b/config.c
@@ -383,6 +383,11 @@ static enum parser_result parse_global_setting(const char *option,
return BAD_VALUE;
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
return NOT_PARSED;

diff --git a/default.cfg b/default.cfg
index 7b3e2f7..12878c5 100644
--- a/default.cfg
+++ b/default.cfg
@@ -33,6 +33,7 @@ tx_timestamp_retries 100
use_syslog 1
verbose 0
summary_interval 0
+kernel_leap 1
#
# Servo Options
#
diff --git a/ds.h b/ds.h
index 06cb30a..555bc19 100644
--- a/ds.h
+++ b/ds.h
@@ -52,6 +52,7 @@ struct default_ds {
int free_running;
int freq_est_interval; /*log seconds*/
int stats_interval; /*log seconds*/
+ int kernel_leap;
struct clock_description clock_desc;
};

diff --git a/gPTP.cfg b/gPTP.cfg
index ecd5f71..cfb388b 100644
--- a/gPTP.cfg
+++ b/gPTP.cfg
@@ -32,6 +32,7 @@ tx_timestamp_retries 100
use_syslog 1
verbose 0
summary_interval 0
+kernel_leap 1
#
# Servo options
#
diff --git a/phc2sys.8 b/phc2sys.8
index 10faea9..67b2f27 100644
--- a/phc2sys.8
+++ b/phc2sys.8
@@ -31,6 +31,8 @@ phc2sys \- synchronize two clocks
] [
.B \-w
] [
+.B \-x
+] [
.BI \-l " print-level"
] [
.B \-m
@@ -127,6 +129,13 @@ 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
.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).
diff --git a/phc2sys.c b/phc2sys.c
index 678b0f3..1f851f7 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -124,6 +124,7 @@ struct clock {
int sync_offset_direction;
int leap;
int leap_set;
+ int kernel_leap;
struct pmc *pmc;
int pmc_ds_idx;
int pmc_ds_requested;
@@ -500,7 +501,7 @@ static int update_sync_offset(struct clock *clock, int64_t offset, uint64_t ts)

if (clock->leap_set != clock_leap) {
/* Only the system clock can leap. */
- if (clock->clkid == CLOCK_REALTIME)
+ if (clock->clkid == CLOCK_REALTIME && clock->kernel_leap)
clockadj_set_leap(clock->clkid, clock_leap);
clock->leap_set = clock_leap;
}
@@ -525,6 +526,7 @@ static void usage(char *progname)
" -O [offset] slave-master time offset (0)\n"
" -u [num] number of clock updates in summary stats (0)\n"
" -w wait for ptp4l\n"
+ " -x apply leap seconds by servo instead of kernel\n"
" -h prints this message and exits\n"
" -v prints the software version and exits\n"
"\n",
@@ -542,6 +544,7 @@ int main(int argc, char *argv[])
struct clock dst_clock = {
.clkid = CLOCK_REALTIME,
.servo_state = SERVO_UNLOCKED,
+ .kernel_leap = 1,
};

configured_pi_kp = KP;
@@ -551,7 +554,7 @@ int main(int argc, char *argv[])
progname = strrchr(argv[0], '/');
progname = progname ? 1+progname : argv[0];
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) {
case 'c':
dst_clock.clkid = clock_open(optarg);
@@ -596,6 +599,9 @@ int main(int argc, char *argv[])
case 'w':
wait_sync = 1;
break;
+ case 'x':
+ dst_clock.kernel_leap = 0;
+ break;
case 'l':
print_level = atoi(optarg);
break;
diff --git a/ptp4l.8 b/ptp4l.8
index 3ee222d..a0d223d 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -340,6 +340,14 @@ empty string.
.B manufacturerIdentity
The manufacturer id which should be an OUI owned by the manufacturer.
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
.BR pmc (8),
diff --git a/ptp4l.c b/ptp4l.c
index 32b69d0..dffaad0 100644
--- a/ptp4l.c
+++ b/ptp4l.c
@@ -53,6 +53,7 @@ static struct config cfg_settings = {
.free_running = 0,
.freq_est_interval = 1,
.stats_interval = 0,
+ .kernel_leap = 1,
.clock_desc = {
.productDescription = {
.max_symbols = 64,
--
1.8.1.2
Jacob Keller
2013-03-07 21:02:07 UTC
Permalink
Post by Miroslav Lichvar
This set adds support for handling leap seconds announced in the time
properties data set. It either sets the kernel leap bits or lets the
servo slowly correct the offset instead of stepping the clock.
--- a/clock.c
+++ b/clock.c
@@ -1020,6 +1020,36 @@ enum servo_state clock_synchronize(struct clock *c,
c->master_offset = tmv_sub(ingress,
tmv_add(origin, tmv_add(c->path_delay, tmv_add(c->c1, c->c2))));
+#if 1
Curious if these #if's are for RFC, or if you just forgot to remove
them. It looks a bit fishy here.
Post by Miroslav Lichvar
+ static int leaptest_offset = 0;
+ time_t t = ingress_ts.tv_sec - 35;
+
+ if (!leaptest_offset) {
+#define SEC_PER_DAY (24 * 3600)
+ leaptest_offset = SEC_PER_DAY - 30 - (t - c->master_offset / NS_PER_SEC) % SEC_PER_DAY;
+ pr_info("leaptest offset: %f hours", leaptest_offset / 3600.0);
+ }
+ c->master_offset -= leaptest_offset * NS_PER_SEC;
+ t -= c->master_offset / NS_PER_SEC;
+
+ if (SEC_PER_DAY - (t + 15) % SEC_PER_DAY < 3600) {
+#if 1
Again here.
Post by Miroslav Lichvar
+ c->tds.flags |= LEAP_61;
+ c->tds.currentUtcOffset = 35;
+ } else {
+ c->tds.flags &= ~LEAP_61;
+ c->tds.currentUtcOffset = 36;
+#else
+ c->tds.flags |= LEAP_59;
+ c->tds.currentUtcOffset = 35;
+ } else {
+ c->tds.flags &= ~LEAP_59;
+ c->tds.currentUtcOffset = 34;
+#endif
+ }
+ pr_info("flags: %d currentUtcOffset: %d", c->tds.flags & (LEAP_61|LEAP_59), c->tds.currentUtcOffset);
+#endif
+
if (!c->path_delay)
return state;
If these aren't just artifacts of forgetting to remove, could you please
explain? Thanks

- Jake
Miroslav Lichvar
2013-03-08 09:57:41 UTC
Permalink
Post by Jacob Keller
Post by Miroslav Lichvar
--- a/clock.c
+++ b/clock.c
@@ -1020,6 +1020,36 @@ enum servo_state clock_synchronize(struct clock *c,
c->master_offset = tmv_sub(ingress,
tmv_add(origin, tmv_add(c->path_delay, tmv_add(c->c1, c->c2))));
+#if 1
Curious if these #if's are for RFC, or if you just forgot to remove
them. It looks a bit fishy here.
That is the code I used to test the new leap second functionality, it
should not be applied to the repository. I thought it could be useful
for reviewers of the patches.

The patched ptp4l modifies the master offset so it starts 30 seconds
before midnight with a pending leap second. 15 seconds before midnight
the leap bit is cleared and currentUtcOffset is updated to test early
updates. The specification allows the update to happen in +/- 2
announce intervals around midnight.

If everything goes well, in the ptp4l (with software time stamping) or
phc2sys output there shouldn't be any large jumps in the observed
offset past midnight (unless the kernel leap application is disabled).
When a leap second is inserted and the sync interval is 1 second,
there should be two messages saying clock updates were suspended. A
test with late currentUtcOffset update should pass too.

Thanks,
--
Miroslav Lichvar
Richard Cochran
2013-03-12 19:33:53 UTC
Permalink
Post by Miroslav Lichvar
This set adds support for handling leap seconds announced in the time
properties data set. It either sets the kernel leap bits or lets the
servo slowly correct the offset instead of stepping the clock.
I am glad to see you taking on this issue, but it will take some time
for me to digest.

Thanks,
Richard
Richard Cochran
2013-03-20 19:01:10 UTC
Permalink
Post by Miroslav Lichvar
This set adds support for handling leap seconds announced in the time
properties data set. It either sets the kernel leap bits or lets the
servo slowly correct the offset instead of stepping the clock.
Series applied.

I did not review every last line in detail, but the changes look
reasonable, and we still have until the next leap second to get any
bugs out ;)

Thanks,
Richard
Miroslav Lichvar
2013-03-25 18:23:42 UTC
Permalink
Post by Richard Cochran
I did not review every last line in detail, but the changes look
reasonable, and we still have until the next leap second to get any
bugs out ;)
I have found one :). In a test with two ptp4l instances I've noticed
that the tds.flags field is not periodically updated from announce
messages, only on state decision events, which currently seem to
happen only when some of the fields included in announce_compare()
changes. This means the new leap second code won't be activated unless
there is a sde in the last 12 hours before the leap second.

Do you have a suggestion on how to fix it? Check if the flags field
changed in announce_compare() or update_current_master() and trigger
the sde? Or introduce a new event? The spec says that sde should occur
at least once per announce interval.

Thanks,
--
Miroslav Lichvar
Richard Cochran
2013-03-25 19:04:04 UTC
Permalink
Post by Miroslav Lichvar
Do you have a suggestion on how to fix it? Check if the flags field
changed in announce_compare() or update_current_master() and trigger
the sde? Or introduce a new event? The spec says that sde should occur
at least once per announce interval.
I don't have an answer for your questions ATM, but I wanted to explain
the SDE-every-second thing. This is one of those places where you get
the impression that the authors weren't sure any more how their BMC
algorithm would work. They hedge their bets by saying, let's re-run
the election every second, just in case.

I always thought that was silly. If the inputs haven't changed, then
the result of the BMC cannot have changed, either.

Just my 2 cents.

Thanks,
Richard
Richard Cochran
2013-04-02 08:27:21 UTC
Permalink
Post by Miroslav Lichvar
Do you have a suggestion on how to fix it? Check if the flags field
changed in announce_compare() or update_current_master() and trigger
the sde? Or introduce a new event? The spec says that sde should occur
at least once per announce interval.
Can you try the following patch?

Thanks,
Richard

---
diff --git a/clock.c b/clock.c
index 641b882..1f55276 100644
--- a/clock.c
+++ b/clock.c
@@ -1096,6 +1096,11 @@ struct timePropertiesDS *clock_time_properties(struct clock *c)
return &c->tds;
}

+void clock_update_time_properties(struct clock *c, struct timePropertiesDS tds)
+{
+ c->tds = tds;
+}
+
static void handle_state_decision_event(struct clock *c)
{
struct foreign_clock *best = NULL, *fc;
diff --git a/clock.h b/clock.h
index 7b4bfb0..6fafc08 100644
--- a/clock.h
+++ b/clock.h
@@ -216,6 +216,13 @@ void clock_sync_interval(struct clock *c, int n);
struct timePropertiesDS *clock_time_properties(struct clock *c);

/**
+ * Update a clock's time properties data set.
+ * @param c The clock instance.
+ * @param tds The new time properties data set for the clock.
+ */
+void clock_update_time_properties(struct clock *c, struct timePropertiesDS tds);
+
+/**
* Obtain a clock's description.
* @param c The clock instance.
* @return A pointer to the clock_description of the clock.
diff --git a/port.c b/port.c
index 9bfbf33..4346422 100644
--- a/port.c
+++ b/port.c
@@ -1231,10 +1231,16 @@ static int update_current_master(struct port *p, struct ptp_message *m)
struct ptp_message *tmp;
struct parent_ds *dad;
struct path_trace_tlv *ptt;
+ struct timePropertiesDS tds;

if (!msg_source_equal(m, fc))
return add_foreign_master(p, m);

+ tds.currentUtcOffset = m->announce.currentUtcOffset;
+ tds.flags = m->header.flagField[1];
+ tds.timeSource = m->announce.timeSource;
+ clock_update_time_properties(p->clock, tds);
+
if (p->pod.path_trace_enabled) {
ptt = (struct path_trace_tlv *) m->announce.suffix;
dad = clock_parent_ds(p->clock);
Miroslav Lichvar
2013-04-02 14:24:28 UTC
Permalink
Post by Richard Cochran
Post by Miroslav Lichvar
Do you have a suggestion on how to fix it? Check if the flags field
changed in announce_compare() or update_current_master() and trigger
the sde? Or introduce a new event? The spec says that sde should occur
at least once per announce interval.
Can you try the following patch?
The patch fixes the problem, please push it to git.

Thanks!
--
Miroslav Lichvar
Loading...