#include <sys/cdefs.h>
#include "opt_ntp.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysproto.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <sys/timetc.h>
#include <sys/timepps.h>
#include <sys/syscallsubr.h>
#include <sys/sysctl.h>
#ifdef PPS_SYNC
FEATURE(pps_sync, "Support usage of external PPS signal by kernel PLL");
#endif
typedef int64_t l_fp;
#define L_ADD(v, u) ((v) += (u))
#define L_SUB(v, u) ((v) -= (u))
#define L_ADDHI(v, a) ((v) += (int64_t)(a) << 32)
#define L_NEG(v) ((v) = -(v))
#define L_RSHIFT(v, n) \
do { \
if ((v) < 0) \
(v) = -(-(v) >> (n)); \
else \
(v) = (v) >> (n); \
} while (0)
#define L_MPY(v, a) ((v) *= (a))
#define L_CLR(v) ((v) = 0)
#define L_ISNEG(v) ((v) < 0)
#define L_LINT(v, a) \
do { \
if ((a) < 0) \
((v) = -((int64_t)(-(a)) << 32)); \
else \
((v) = (int64_t)(a) << 32); \
} while (0)
#define L_GINT(v) ((v) < 0 ? -(-(v) >> 32) : (v) >> 32)
#define SHIFT_PLL 4
#define SHIFT_FLL 2
static int time_state = TIME_OK;
int time_status = STA_UNSYNC;
static long time_tai;
static long time_monitor;
static long time_constant;
static long time_precision = 1;
static long time_maxerror = MAXPHASE / 1000;
long time_esterror = MAXPHASE / 1000;
static long time_reftime;
static l_fp time_offset;
static l_fp time_freq;
static l_fp time_adj;
static int64_t time_adjtime;
static struct mtx ntp_lock;
MTX_SYSINIT(ntp, &ntp_lock, "ntp", MTX_SPIN);
#define NTP_LOCK() mtx_lock_spin(&ntp_lock)
#define NTP_UNLOCK() mtx_unlock_spin(&ntp_lock)
#define NTP_ASSERT_LOCKED() mtx_assert(&ntp_lock, MA_OWNED)
#ifdef PPS_SYNC
#define PPS_FAVG 2
#define PPS_FAVGDEF 8
#define PPS_FAVGMAX 15
#define PPS_PAVG 4
#define PPS_VALID 120
#define PPS_MAXWANDER 100000
#define PPS_POPCORN 2
static struct timespec pps_tf[3];
static l_fp pps_freq;
static long pps_fcount;
static long pps_jitter;
static long pps_stabil;
static time_t pps_lastsec;
static int pps_valid;
static int pps_shift = PPS_FAVG;
static int pps_shiftmax = PPS_FAVGDEF;
static int pps_intcnt;
static long pps_calcnt;
static long pps_jitcnt;
static long pps_stbcnt;
static long pps_errcnt;
#endif
static void hardupdate(long offset);
static void ntp_gettime1(struct ntptimeval *ntvp);
static bool ntp_is_time_error(int tsl);
static bool
ntp_is_time_error(int tsl)
{
if ((tsl & (STA_UNSYNC | STA_CLOCKERR)) ||
(tsl & (STA_PPSFREQ | STA_PPSTIME) &&
!(tsl & STA_PPSSIGNAL)) ||
(tsl & STA_PPSTIME && tsl & STA_PPSJITTER) ||
(tsl & STA_PPSFREQ &&
tsl & (STA_PPSWANDER | STA_PPSERROR)))
return (true);
return (false);
}
static void
ntp_gettime1(struct ntptimeval *ntvp)
{
struct timespec atv;
NTP_ASSERT_LOCKED();
nanotime(&atv);
ntvp->time.tv_sec = atv.tv_sec;
ntvp->time.tv_nsec = atv.tv_nsec;
ntvp->maxerror = time_maxerror;
ntvp->esterror = time_esterror;
ntvp->tai = time_tai;
ntvp->time_state = time_state;
if (ntp_is_time_error(time_status))
ntvp->time_state = TIME_ERROR;
}
#ifndef _SYS_SYSPROTO_H_
struct ntp_gettime_args {
struct ntptimeval *ntvp;
};
#endif
int
sys_ntp_gettime(struct thread *td, struct ntp_gettime_args *uap)
{
struct ntptimeval ntv;
memset(&ntv, 0, sizeof(ntv));
NTP_LOCK();
ntp_gettime1(&ntv);
NTP_UNLOCK();
td->td_retval[0] = ntv.time_state;
return (copyout(&ntv, uap->ntvp, sizeof(ntv)));
}
static int
ntp_sysctl(SYSCTL_HANDLER_ARGS)
{
struct ntptimeval ntv;
memset(&ntv, 0, sizeof(ntv));
NTP_LOCK();
ntp_gettime1(&ntv);
NTP_UNLOCK();
return (sysctl_handle_opaque(oidp, &ntv, sizeof(ntv), req));
}
SYSCTL_NODE(_kern, OID_AUTO, ntp_pll, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
"");
SYSCTL_PROC(_kern_ntp_pll, OID_AUTO, gettime, CTLTYPE_OPAQUE | CTLFLAG_RD |
CTLFLAG_MPSAFE, 0, sizeof(struct ntptimeval) , ntp_sysctl, "S,ntptimeval",
"");
#ifdef PPS_SYNC
SYSCTL_INT(_kern_ntp_pll, OID_AUTO, pps_shiftmax, CTLFLAG_RW,
&pps_shiftmax, 0, "Max interval duration (sec) (shift)");
SYSCTL_INT(_kern_ntp_pll, OID_AUTO, pps_shift, CTLFLAG_RW,
&pps_shift, 0, "Interval duration (sec) (shift)");
SYSCTL_LONG(_kern_ntp_pll, OID_AUTO, time_monitor, CTLFLAG_RD,
&time_monitor, 0, "Last time offset scaled (ns)");
SYSCTL_S64(_kern_ntp_pll, OID_AUTO, pps_freq, CTLFLAG_RD | CTLFLAG_MPSAFE,
&pps_freq, 0,
"Scaled frequency offset (ns/sec)");
SYSCTL_S64(_kern_ntp_pll, OID_AUTO, time_freq, CTLFLAG_RD | CTLFLAG_MPSAFE,
&time_freq, 0,
"Frequency offset (ns/sec)");
#endif
int
kern_ntp_adjtime(struct thread *td, struct timex *ntv, int *retvalp)
{
long freq;
int modes;
int error, retval;
modes = ntv->modes;
error = 0;
if (modes)
error = priv_check(td, PRIV_NTP_ADJTIME);
if (error != 0)
return (error);
NTP_LOCK();
if (modes & MOD_MAXERROR)
time_maxerror = ntv->maxerror;
if (modes & MOD_ESTERROR)
time_esterror = ntv->esterror;
if (modes & MOD_STATUS) {
if (time_status & STA_PLL && !(ntv->status & STA_PLL)) {
time_state = TIME_OK;
time_status = STA_UNSYNC;
#ifdef PPS_SYNC
pps_shift = PPS_FAVG;
#endif
}
time_status &= STA_RONLY;
time_status |= ntv->status & ~STA_RONLY;
}
if (modes & MOD_TIMECONST) {
if (ntv->constant < 0)
time_constant = 0;
else if (ntv->constant > MAXTC)
time_constant = MAXTC;
else
time_constant = ntv->constant;
}
if (modes & MOD_TAI) {
if (ntv->constant > 0)
time_tai = ntv->constant;
}
#ifdef PPS_SYNC
if (modes & MOD_PPSMAX) {
if (ntv->shift < PPS_FAVG)
pps_shiftmax = PPS_FAVG;
else if (ntv->shift > PPS_FAVGMAX)
pps_shiftmax = PPS_FAVGMAX;
else
pps_shiftmax = ntv->shift;
}
#endif
if (modes & MOD_NANO)
time_status |= STA_NANO;
if (modes & MOD_MICRO)
time_status &= ~STA_NANO;
if (modes & MOD_CLKB)
time_status |= STA_CLK;
if (modes & MOD_CLKA)
time_status &= ~STA_CLK;
if (modes & MOD_FREQUENCY) {
freq = (ntv->freq * 1000LL) >> 16;
if (freq > MAXFREQ)
L_LINT(time_freq, MAXFREQ);
else if (freq < -MAXFREQ)
L_LINT(time_freq, -MAXFREQ);
else {
time_freq = ntv->freq * 1000LL * 65536LL;
}
#ifdef PPS_SYNC
pps_freq = time_freq;
#endif
}
if (modes & MOD_OFFSET) {
if (time_status & STA_NANO)
hardupdate(ntv->offset);
else
hardupdate(ntv->offset * 1000);
}
if (time_status & STA_NANO)
ntv->offset = L_GINT(time_offset);
else
ntv->offset = L_GINT(time_offset) / 1000;
ntv->freq = L_GINT((time_freq / 1000LL) << 16);
ntv->maxerror = time_maxerror;
ntv->esterror = time_esterror;
ntv->status = time_status;
ntv->constant = time_constant;
if (time_status & STA_NANO)
ntv->precision = time_precision;
else
ntv->precision = time_precision / 1000;
ntv->tolerance = MAXFREQ * SCALE_PPM;
#ifdef PPS_SYNC
ntv->shift = pps_shift;
ntv->ppsfreq = L_GINT((pps_freq / 1000LL) << 16);
if (time_status & STA_NANO)
ntv->jitter = pps_jitter;
else
ntv->jitter = pps_jitter / 1000;
ntv->stabil = pps_stabil;
ntv->calcnt = pps_calcnt;
ntv->errcnt = pps_errcnt;
ntv->jitcnt = pps_jitcnt;
ntv->stbcnt = pps_stbcnt;
#endif
retval = ntp_is_time_error(time_status) ? TIME_ERROR : time_state;
NTP_UNLOCK();
*retvalp = retval;
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct ntp_adjtime_args {
struct timex *tp;
};
#endif
int
sys_ntp_adjtime(struct thread *td, struct ntp_adjtime_args *uap)
{
struct timex ntv;
int error, retval;
error = copyin(uap->tp, &ntv, sizeof(ntv));
if (error == 0) {
error = kern_ntp_adjtime(td, &ntv, &retval);
if (error == 0) {
error = copyout(&ntv, uap->tp, sizeof(ntv));
if (error == 0)
td->td_retval[0] = retval;
}
}
return (error);
}
void
ntp_update_second(int64_t *adjustment, time_t *newsec, long *tai_off)
{
int tickrate;
l_fp ftemp;
NTP_LOCK();
time_maxerror += MAXFREQ / 1000;
switch (time_state) {
case TIME_OK:
if (time_status & STA_INS)
time_state = TIME_INS;
else if (time_status & STA_DEL)
time_state = TIME_DEL;
break;
case TIME_INS:
if (!(time_status & STA_INS))
time_state = TIME_OK;
else if ((*newsec) % 86400 == 0) {
(*newsec)--;
time_state = TIME_OOP;
time_tai++;
}
break;
case TIME_DEL:
if (!(time_status & STA_DEL))
time_state = TIME_OK;
else if (((*newsec) + 1) % 86400 == 0) {
(*newsec)++;
time_tai--;
time_state = TIME_WAIT;
}
break;
case TIME_OOP:
time_state = TIME_WAIT;
break;
case TIME_WAIT:
if (!(time_status & (STA_INS | STA_DEL)))
time_state = TIME_OK;
}
ftemp = time_offset;
#ifdef PPS_SYNC
if (time_status & STA_PPSTIME && time_status &
STA_PPSSIGNAL)
L_RSHIFT(ftemp, pps_shift);
else
L_RSHIFT(ftemp, SHIFT_PLL + time_constant);
#else
L_RSHIFT(ftemp, SHIFT_PLL + time_constant);
#endif
time_adj = ftemp;
L_SUB(time_offset, ftemp);
L_ADD(time_adj, time_freq);
if (time_adjtime != 0) {
if (time_adjtime > 1000000)
tickrate = 5000;
else if (time_adjtime < -1000000)
tickrate = -5000;
else if (time_adjtime > 500)
tickrate = 500;
else if (time_adjtime < -500)
tickrate = -500;
else
tickrate = time_adjtime;
time_adjtime -= tickrate;
L_LINT(ftemp, tickrate * 1000);
L_ADD(time_adj, ftemp);
}
*adjustment = time_adj;
*tai_off = time_tai;
#ifdef PPS_SYNC
if (pps_valid > 0)
pps_valid--;
else
time_status &= ~STA_PPSSIGNAL;
#endif
NTP_UNLOCK();
}
static void
hardupdate(long offset )
{
long mtemp;
l_fp ftemp;
NTP_ASSERT_LOCKED();
if (!(time_status & STA_PLL))
return;
if (!(time_status & STA_PPSTIME && time_status &
STA_PPSSIGNAL)) {
if (offset > MAXPHASE)
time_monitor = MAXPHASE;
else if (offset < -MAXPHASE)
time_monitor = -MAXPHASE;
else
time_monitor = offset;
L_LINT(time_offset, time_monitor);
}
if (time_status & STA_PPSFREQ && time_status & STA_PPSSIGNAL) {
time_reftime = time_uptime;
return;
}
if (time_status & STA_FREQHOLD || time_reftime == 0)
time_reftime = time_uptime;
mtemp = time_uptime - time_reftime;
L_LINT(ftemp, time_monitor);
L_RSHIFT(ftemp, (SHIFT_PLL + 2 + time_constant) << 1);
L_MPY(ftemp, mtemp);
L_ADD(time_freq, ftemp);
time_status &= ~STA_MODE;
if (mtemp >= MINSEC && (time_status & STA_FLL || mtemp >
MAXSEC)) {
L_LINT(ftemp, (time_monitor << 4) / mtemp);
L_RSHIFT(ftemp, SHIFT_FLL + 4);
L_ADD(time_freq, ftemp);
time_status |= STA_MODE;
}
time_reftime = time_uptime;
if (L_GINT(time_freq) > MAXFREQ)
L_LINT(time_freq, MAXFREQ);
else if (L_GINT(time_freq) < -MAXFREQ)
L_LINT(time_freq, -MAXFREQ);
}
#ifdef PPS_SYNC
void
hardpps(struct timespec *tsp, long delta_nsec)
{
long u_nsec, v_nsec;
time_t u_sec;
l_fp ftemp;
NTP_LOCK();
time_status |= STA_PPSSIGNAL | STA_PPSJITTER;
time_status &= ~(STA_PPSWANDER | STA_PPSERROR);
pps_valid = PPS_VALID;
u_sec = tsp->tv_sec;
u_nsec = tsp->tv_nsec;
if (u_nsec >= (NANOSECOND >> 1)) {
u_nsec -= NANOSECOND;
u_sec++;
}
v_nsec = u_nsec - pps_tf[0].tv_nsec;
if (u_sec == pps_tf[0].tv_sec && v_nsec < NANOSECOND - MAXFREQ)
goto out;
pps_tf[2] = pps_tf[1];
pps_tf[1] = pps_tf[0];
pps_tf[0].tv_sec = u_sec;
pps_tf[0].tv_nsec = u_nsec;
pps_fcount += delta_nsec - NANOSECOND;
if (v_nsec > MAXFREQ || v_nsec < -MAXFREQ)
goto out;
time_status &= ~STA_PPSJITTER;
if (pps_tf[0].tv_nsec > pps_tf[1].tv_nsec) {
if (pps_tf[1].tv_nsec > pps_tf[2].tv_nsec) {
v_nsec = pps_tf[1].tv_nsec;
u_nsec = pps_tf[0].tv_nsec - pps_tf[2].tv_nsec;
} else if (pps_tf[2].tv_nsec > pps_tf[0].tv_nsec) {
v_nsec = pps_tf[0].tv_nsec;
u_nsec = pps_tf[2].tv_nsec - pps_tf[1].tv_nsec;
} else {
v_nsec = pps_tf[2].tv_nsec;
u_nsec = pps_tf[0].tv_nsec - pps_tf[1].tv_nsec;
}
} else {
if (pps_tf[1].tv_nsec < pps_tf[2].tv_nsec) {
v_nsec = pps_tf[1].tv_nsec;
u_nsec = pps_tf[2].tv_nsec - pps_tf[0].tv_nsec;
} else if (pps_tf[2].tv_nsec < pps_tf[0].tv_nsec) {
v_nsec = pps_tf[0].tv_nsec;
u_nsec = pps_tf[1].tv_nsec - pps_tf[2].tv_nsec;
} else {
v_nsec = pps_tf[2].tv_nsec;
u_nsec = pps_tf[1].tv_nsec - pps_tf[0].tv_nsec;
}
}
if (u_nsec > lmax(pps_jitter << PPS_POPCORN,
2 * (NANOSECOND / (long)qmin(NANOSECOND, tc_getfrequency())))) {
time_status |= STA_PPSJITTER;
pps_jitcnt++;
} else if (time_status & STA_PPSTIME) {
time_monitor = -v_nsec;
L_LINT(time_offset, time_monitor);
}
pps_jitter += (u_nsec - pps_jitter) >> PPS_FAVG;
u_sec = pps_tf[0].tv_sec - pps_lastsec;
if (u_sec < (1 << pps_shift))
goto out;
pps_calcnt++;
v_nsec = -pps_fcount;
pps_lastsec = pps_tf[0].tv_sec;
pps_fcount = 0;
u_nsec = MAXFREQ << pps_shift;
if (v_nsec > u_nsec || v_nsec < -u_nsec || u_sec != (1 << pps_shift)) {
time_status |= STA_PPSERROR;
pps_errcnt++;
goto out;
}
L_LINT(ftemp, v_nsec);
L_RSHIFT(ftemp, pps_shift);
L_SUB(ftemp, pps_freq);
u_nsec = L_GINT(ftemp);
if (u_nsec > PPS_MAXWANDER) {
L_LINT(ftemp, PPS_MAXWANDER);
pps_intcnt--;
time_status |= STA_PPSWANDER;
pps_stbcnt++;
} else if (u_nsec < -PPS_MAXWANDER) {
L_LINT(ftemp, -PPS_MAXWANDER);
pps_intcnt--;
time_status |= STA_PPSWANDER;
pps_stbcnt++;
} else {
pps_intcnt++;
}
if (pps_intcnt >= 4) {
pps_intcnt = 4;
if (pps_shift < pps_shiftmax) {
pps_shift++;
pps_intcnt = 0;
}
} else if (pps_intcnt <= -4 || pps_shift > pps_shiftmax) {
pps_intcnt = -4;
if (pps_shift > PPS_FAVG) {
pps_shift--;
pps_intcnt = 0;
}
}
if (u_nsec < 0)
u_nsec = -u_nsec;
pps_stabil += (u_nsec * SCALE_PPM - pps_stabil) >> PPS_FAVG;
L_ADD(pps_freq, ftemp);
u_nsec = L_GINT(pps_freq);
if (u_nsec > MAXFREQ)
L_LINT(pps_freq, MAXFREQ);
else if (u_nsec < -MAXFREQ)
L_LINT(pps_freq, -MAXFREQ);
if (time_status & STA_PPSFREQ)
time_freq = pps_freq;
out:
NTP_UNLOCK();
}
#endif
#ifndef _SYS_SYSPROTO_H_
struct adjtime_args {
struct timeval *delta;
struct timeval *olddelta;
};
#endif
int
sys_adjtime(struct thread *td, struct adjtime_args *uap)
{
struct timeval delta, olddelta, *deltap;
int error;
if (uap->delta) {
error = copyin(uap->delta, &delta, sizeof(delta));
if (error)
return (error);
deltap = δ
} else
deltap = NULL;
error = kern_adjtime(td, deltap, &olddelta);
if (uap->olddelta && error == 0)
error = copyout(&olddelta, uap->olddelta, sizeof(olddelta));
return (error);
}
int
kern_adjtime(struct thread *td, struct timeval *delta, struct timeval *olddelta)
{
struct timeval atv;
int64_t ltr, ltw;
int error;
if (delta != NULL) {
error = priv_check(td, PRIV_ADJTIME);
if (error != 0)
return (error);
ltw = (int64_t)delta->tv_sec * 1000000 + delta->tv_usec;
}
NTP_LOCK();
ltr = time_adjtime;
if (delta != NULL)
time_adjtime = ltw;
NTP_UNLOCK();
if (olddelta != NULL) {
atv.tv_sec = ltr / 1000000;
atv.tv_usec = ltr % 1000000;
if (atv.tv_usec < 0) {
atv.tv_usec += 1000000;
atv.tv_sec--;
}
*olddelta = atv;
}
return (0);
}
static struct callout resettodr_callout;
static int resettodr_period = 1800;
static void
periodic_resettodr(void *arg __unused)
{
if (!ntp_is_time_error(time_status))
resettodr();
if (resettodr_period > 0)
callout_schedule(&resettodr_callout, resettodr_period * hz);
}
static void
shutdown_resettodr(void *arg __unused, int howto __unused)
{
callout_drain(&resettodr_callout);
if (resettodr_period > 0 && !ntp_is_time_error(time_status))
resettodr();
}
static int
sysctl_resettodr_period(SYSCTL_HANDLER_ARGS)
{
int error;
error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req);
if (error || !req->newptr)
return (error);
if (cold)
goto done;
if (resettodr_period == 0)
callout_stop(&resettodr_callout);
else
callout_reset(&resettodr_callout, resettodr_period * hz,
periodic_resettodr, NULL);
done:
return (0);
}
SYSCTL_PROC(_machdep, OID_AUTO, rtc_save_period, CTLTYPE_INT | CTLFLAG_RWTUN |
CTLFLAG_MPSAFE, &resettodr_period, 1800, sysctl_resettodr_period, "I",
"Save system time to RTC with this period (in seconds)");
static void
start_periodic_resettodr(void *arg __unused)
{
EVENTHANDLER_REGISTER(shutdown_pre_sync, shutdown_resettodr, NULL,
SHUTDOWN_PRI_FIRST);
callout_init(&resettodr_callout, 1);
if (resettodr_period == 0)
return;
callout_reset(&resettodr_callout, resettodr_period * hz,
periodic_resettodr, NULL);
}
SYSINIT(periodic_resettodr, SI_SUB_LAST, SI_ORDER_MIDDLE,
start_periodic_resettodr, NULL);