diff --git a/clock.c b/clock.c index 50b6a15..641b882 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" @@ -76,6 +77,9 @@ struct clock { int free_running; 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; struct mave *avg_delay; @@ -389,11 +393,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; @@ -412,52 +418,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; @@ -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; + 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) { + 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; /* 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) @@ -565,6 +569,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) { @@ -585,16 +590,20 @@ 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) 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) { 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"); @@ -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); 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) @@ -966,7 +975,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) @@ -1018,37 +1027,42 @@ 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, 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) { - 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) { 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, -tmv_to_nanoseconds(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..31fc765 --- /dev/null +++ b/clockadj.c @@ -0,0 +1,96 @@ +/** + * @file clockadj.c + * @note Copyright (C) 2013 Richard Cochran + * + * 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 + +#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); +} diff --git a/clockadj.h b/clockadj.h new file mode 100644 index 0000000..6e76e0f --- /dev/null +++ b/clockadj.h @@ -0,0 +1,54 @@ +/** + * @file clockadj.h + * @brief Wraps clock_adjtime functionality. + * @note Copyright (C) 2013 Miroslav Lichvar + * + * 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 +#include + +/* + * 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 diff --git a/config.c b/config.c index afb2e6b..4727942 100644 --- a/config.c +++ b/config.c @@ -399,6 +399,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 84a009c..84cc0df 100644 --- a/default.cfg +++ b/default.cfg @@ -34,6 +34,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 757e1be..f653aee 100644 --- a/ds.h +++ b/ds.h @@ -53,6 +53,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 186a003..f14990b 100644 --- a/gPTP.cfg +++ b/gPTP.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/makefile b/makefile index 14760bb..f30d836 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 fault.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 fault.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.8 b/phc2sys.8 index 0542ba7..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 @@ -106,13 +108,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 +123,18 @@ 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 +.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 diff --git a/phc2sys.c b/phc2sys.c index 582492d..1f851f7 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -27,13 +27,13 @@ #include #include #include -#include #include #include #include #include +#include "clockadj.h" #include "ds.h" #include "fsm.h" #include "missing.h" @@ -45,6 +45,7 @@ #include "stats.h" #include "sysoff.h" #include "tlv.h" +#include "util.h" #include "version.h" #define KP 0.7 @@ -54,6 +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, int64_t offset, uint64_t ts); static clockid_t clock_open(char *device) { @@ -75,52 +80,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) { @@ -155,11 +114,21 @@ 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; struct stats *delay_stats; 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, @@ -203,16 +172,24 @@ static void update_clock(struct clock *clock, enum servo_state state; 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); + clock->servo_state = state; switch (state) { 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; } @@ -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, - 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 +265,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 +274,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 +288,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 +307,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 +338,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 +416,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,21 +425,86 @@ 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; + 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; } 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, 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; } @@ -469,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", @@ -479,11 +537,15 @@ 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 }; + struct clock dst_clock = { + .clkid = CLOCK_REALTIME, + .servo_state = SERVO_UNLOCKED, + .kernel_leap = 1, + }; configured_pi_kp = KP; configured_pi_ki = KI; @@ -492,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); @@ -524,7 +586,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': @@ -536,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; @@ -596,36 +662,49 @@ 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); + 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); + clockadj_set_leap(dst_clock.clkid, 0); 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); } diff --git a/ptp4l.8 b/ptp4l.8 index 98a7674..45ef6f3 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -351,6 +351,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 e2d64bb..1312cd6 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, 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