phc2sys: Handle leap seconds.
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 <mlichvar@redhat.com>master
parent
a29b1a4105
commit
eb93926bc9
24
clockadj.c
24
clockadj.c
|
@ -70,3 +70,27 @@ void clockadj_step(clockid_t clkid, int64_t step)
|
||||||
if (clock_adjtime(clkid, &tx) < 0)
|
if (clock_adjtime(clkid, &tx) < 0)
|
||||||
pr_err("failed to step clock: %m");
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -44,4 +44,11 @@ double clockadj_get_freq(clockid_t clkid);
|
||||||
*/
|
*/
|
||||||
void clockadj_step(clockid_t clkid, int64_t step);
|
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
|
#endif
|
||||||
|
|
75
phc2sys.c
75
phc2sys.c
|
@ -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,9 +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;
|
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)
|
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 {
|
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;
|
||||||
|
@ -119,9 +122,12 @@ struct clock {
|
||||||
unsigned int stats_max_count;
|
unsigned int stats_max_count;
|
||||||
int sync_offset;
|
int sync_offset;
|
||||||
int sync_offset_direction;
|
int sync_offset_direction;
|
||||||
|
int leap;
|
||||||
|
int leap_set;
|
||||||
struct pmc *pmc;
|
struct pmc *pmc;
|
||||||
int pmc_ds_idx;
|
int pmc_ds_idx;
|
||||||
int pmc_ds_requested;
|
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,
|
||||||
|
@ -165,7 +171,7 @@ static void update_clock(struct clock *clock,
|
||||||
enum servo_state state;
|
enum servo_state state;
|
||||||
double ppb;
|
double ppb;
|
||||||
|
|
||||||
if (update_sync_offset(clock))
|
if (update_sync_offset(clock, offset, ts))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (clock->sync_offset_direction)
|
if (clock->sync_offset_direction)
|
||||||
|
@ -173,6 +179,7 @@ static void update_clock(struct clock *clock,
|
||||||
clock->sync_offset_direction;
|
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:
|
||||||
|
@ -419,6 +426,12 @@ static int run_pmc(struct clock *clock, int timeout,
|
||||||
case TIME_PROPERTIES_DATA_SET:
|
case TIME_PROPERTIES_DATA_SET:
|
||||||
clock->sync_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;
|
||||||
}
|
}
|
||||||
|
@ -441,11 +454,57 @@ static void close_pmc(struct clock *clock)
|
||||||
clock->pmc = NULL;
|
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) {
|
int clock_leap;
|
||||||
run_pmc(clock, 0, 0, 1);
|
|
||||||
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,7 +539,10 @@ int main(int argc, char *argv[])
|
||||||
int r, 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,
|
||||||
|
};
|
||||||
|
|
||||||
configured_pi_kp = KP;
|
configured_pi_kp = KP;
|
||||||
configured_pi_ki = KI;
|
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
|
/* 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. */
|
||||||
clockadj_set_freq(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);
|
||||||
|
|
||||||
|
|
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