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