Early beta releases of macOS Big Sur had a signed/unsigned error in Apple's implementation of ntp_adjtime. Apple have since fixed this error and the workaround is no longer required.
276 lines
6.9 KiB
C
276 lines
6.9 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Richard P. Curnow 1997-2003
|
|
* Copyright (C) Miroslav Lichvar 2009-2012, 2014-2015, 2017
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
Driver for systems that implement the adjtimex()/ntp_adjtime() system call
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "conf.h"
|
|
#include "privops.h"
|
|
#include "sys_generic.h"
|
|
#include "sys_timex.h"
|
|
#include "logging.h"
|
|
|
|
#ifdef PRIVOPS_ADJUSTTIMEX
|
|
#define NTP_ADJTIME PRV_AdjustTimex
|
|
#define NTP_ADJTIME_NAME "ntp_adjtime"
|
|
#else
|
|
#ifdef LINUX
|
|
#define NTP_ADJTIME adjtimex
|
|
#define NTP_ADJTIME_NAME "adjtimex"
|
|
#else
|
|
#define NTP_ADJTIME ntp_adjtime
|
|
#define NTP_ADJTIME_NAME "ntp_adjtime"
|
|
#endif
|
|
#endif
|
|
|
|
/* Maximum frequency offset accepted by the kernel (in ppm) */
|
|
#define MAX_FREQ 500.0
|
|
|
|
/* Frequency scale to convert from ppm to the timex freq */
|
|
#define FREQ_SCALE (double)(1 << 16)
|
|
|
|
/* Threshold for the timex maxerror when the kernel sets the UNSYNC flag */
|
|
#define MAX_SYNC_ERROR 16.0
|
|
|
|
/* Minimum assumed rate at which the kernel updates the clock frequency */
|
|
#define MIN_TICK_RATE 100
|
|
|
|
/* Saved timex status */
|
|
static int sys_status;
|
|
|
|
/* Saved TAI-UTC offset */
|
|
static int sys_tai_offset;
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
convert_timex_frequency(const struct timex *txc)
|
|
{
|
|
double freq_ppm;
|
|
|
|
freq_ppm = txc->freq / FREQ_SCALE;
|
|
|
|
return -freq_ppm;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
read_frequency(void)
|
|
{
|
|
struct timex txc;
|
|
|
|
txc.modes = 0;
|
|
|
|
SYS_Timex_Adjust(&txc, 0);
|
|
|
|
return convert_timex_frequency(&txc);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
set_frequency(double freq_ppm)
|
|
{
|
|
struct timex txc;
|
|
|
|
txc.modes = MOD_FREQUENCY;
|
|
txc.freq = freq_ppm * -FREQ_SCALE;
|
|
|
|
SYS_Timex_Adjust(&txc, 0);
|
|
|
|
return convert_timex_frequency(&txc);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
set_leap(int leap, int tai_offset)
|
|
{
|
|
struct timex txc;
|
|
int applied, prev_status;
|
|
|
|
txc.modes = 0;
|
|
applied = SYS_Timex_Adjust(&txc, 0) == TIME_WAIT;
|
|
|
|
prev_status = sys_status;
|
|
sys_status &= ~(STA_INS | STA_DEL);
|
|
|
|
if (leap > 0)
|
|
sys_status |= STA_INS;
|
|
else if (leap < 0)
|
|
sys_status |= STA_DEL;
|
|
|
|
txc.modes = MOD_STATUS;
|
|
txc.status = sys_status;
|
|
|
|
#ifdef MOD_TAI
|
|
if (tai_offset) {
|
|
txc.modes |= MOD_TAI;
|
|
txc.constant = tai_offset;
|
|
|
|
if (applied && !(sys_status & (STA_INS | STA_DEL)))
|
|
sys_tai_offset += prev_status & STA_INS ? 1 : -1;
|
|
|
|
if (sys_tai_offset != tai_offset) {
|
|
sys_tai_offset = tai_offset;
|
|
LOG(LOGS_INFO, "System clock TAI offset set to %d seconds", tai_offset);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SYS_Timex_Adjust(&txc, 0);
|
|
|
|
if (prev_status != sys_status) {
|
|
LOG(LOGS_INFO, "System clock status %s leap second",
|
|
leap ? (leap > 0 ? "set to insert" : "set to delete") :
|
|
(applied ? "reset after" : "set to not insert/delete"));
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
set_sync_status(int synchronised, double est_error, double max_error)
|
|
{
|
|
struct timex txc;
|
|
|
|
if (synchronised) {
|
|
if (est_error > MAX_SYNC_ERROR)
|
|
est_error = MAX_SYNC_ERROR;
|
|
if (max_error >= MAX_SYNC_ERROR) {
|
|
max_error = MAX_SYNC_ERROR;
|
|
synchronised = 0;
|
|
}
|
|
} else {
|
|
est_error = max_error = MAX_SYNC_ERROR;
|
|
}
|
|
|
|
#ifdef LINUX
|
|
/* On Linux clear the UNSYNC flag only if rtcsync is enabled */
|
|
if (!CNF_GetRtcSync())
|
|
synchronised = 0;
|
|
#endif
|
|
|
|
if (synchronised)
|
|
sys_status &= ~STA_UNSYNC;
|
|
else
|
|
sys_status |= STA_UNSYNC;
|
|
|
|
txc.modes = MOD_STATUS | MOD_ESTERROR | MOD_MAXERROR;
|
|
txc.status = sys_status;
|
|
txc.esterror = est_error * 1.0e6;
|
|
txc.maxerror = max_error * 1.0e6;
|
|
|
|
if (SYS_Timex_Adjust(&txc, 1) < 0)
|
|
;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
initialise_timex(void)
|
|
{
|
|
struct timex txc;
|
|
|
|
sys_status = STA_UNSYNC;
|
|
sys_tai_offset = 0;
|
|
|
|
/* Reset PLL offset */
|
|
txc.modes = MOD_OFFSET | MOD_STATUS;
|
|
txc.status = STA_PLL | sys_status;
|
|
txc.offset = 0;
|
|
SYS_Timex_Adjust(&txc, 0);
|
|
|
|
/* Turn PLL off */
|
|
txc.modes = MOD_STATUS;
|
|
txc.status = sys_status;
|
|
SYS_Timex_Adjust(&txc, 0);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
SYS_Timex_Initialise(void)
|
|
{
|
|
SYS_Timex_InitialiseWithFunctions(MAX_FREQ, 1.0 / MIN_TICK_RATE, NULL, NULL, NULL,
|
|
0.0, 0.0, NULL, NULL);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
SYS_Timex_InitialiseWithFunctions(double max_set_freq_ppm, double max_set_freq_delay,
|
|
lcl_ReadFrequencyDriver sys_read_freq,
|
|
lcl_SetFrequencyDriver sys_set_freq,
|
|
lcl_ApplyStepOffsetDriver sys_apply_step_offset,
|
|
double min_fastslew_offset, double max_fastslew_rate,
|
|
lcl_AccrueOffsetDriver sys_accrue_offset,
|
|
lcl_OffsetCorrectionDriver sys_get_offset_correction)
|
|
{
|
|
initialise_timex();
|
|
|
|
SYS_Generic_CompleteFreqDriver(max_set_freq_ppm, max_set_freq_delay,
|
|
sys_read_freq ? sys_read_freq : read_frequency,
|
|
sys_set_freq ? sys_set_freq : set_frequency,
|
|
sys_apply_step_offset,
|
|
min_fastslew_offset, max_fastslew_rate,
|
|
sys_accrue_offset, sys_get_offset_correction,
|
|
set_leap, set_sync_status);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
SYS_Timex_Finalise(void)
|
|
{
|
|
SYS_Generic_Finalise();
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
SYS_Timex_Adjust(struct timex *txc, int ignore_error)
|
|
{
|
|
int state;
|
|
|
|
#ifdef SOLARIS
|
|
/* The kernel seems to check the constant even when it's not being set */
|
|
if (!(txc->modes & MOD_TIMECONST))
|
|
txc->constant = 10;
|
|
#endif
|
|
|
|
state = NTP_ADJTIME(txc);
|
|
|
|
if (state < 0) {
|
|
LOG(ignore_error ? LOGS_DEBUG : LOGS_FATAL,
|
|
NTP_ADJTIME_NAME"(0x%x) failed : %s", txc->modes, strerror(errno));
|
|
}
|
|
|
|
return state;
|
|
}
|