985 lines
22 KiB
C
985 lines
22 KiB
C
/* $OpenBSD: kern_tc.c,v 1.83 2024/02/23 23:01:15 cheloha Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2000 Poul-Henning Kamp <phk@FreeBSD.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* If we meet some day, and you think this stuff is worth it, you
|
|
* can buy me a beer in return. Poul-Henning Kamp
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/atomic.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/rwlock.h>
|
|
#include <sys/stdint.h>
|
|
#include <sys/timeout.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/timetc.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/malloc.h>
|
|
|
|
u_int dummy_get_timecount(struct timecounter *);
|
|
|
|
int sysctl_tc_hardware(void *, size_t *, void *, size_t);
|
|
int sysctl_tc_choice(void *, size_t *, void *, size_t);
|
|
|
|
/*
|
|
* Implement a dummy timecounter which we can use until we get a real one
|
|
* in the air. This allows the console and other early stuff to use
|
|
* time services.
|
|
*/
|
|
|
|
u_int
|
|
dummy_get_timecount(struct timecounter *tc)
|
|
{
|
|
static u_int now;
|
|
|
|
return atomic_inc_int_nv(&now);
|
|
}
|
|
|
|
static struct timecounter dummy_timecounter = {
|
|
.tc_get_timecount = dummy_get_timecount,
|
|
.tc_counter_mask = ~0u,
|
|
.tc_frequency = 1000000,
|
|
.tc_name = "dummy",
|
|
.tc_quality = -1000000,
|
|
.tc_priv = NULL,
|
|
.tc_user = 0,
|
|
};
|
|
|
|
/*
|
|
* Locks used to protect struct members, global variables in this file:
|
|
* I immutable after initialization
|
|
* T tc_lock
|
|
* W windup_mtx
|
|
*/
|
|
|
|
struct timehands {
|
|
/* These fields must be initialized by the driver. */
|
|
struct timecounter *th_counter; /* [W] */
|
|
int64_t th_adjtimedelta; /* [T,W] */
|
|
struct bintime th_next_ntp_update; /* [T,W] */
|
|
int64_t th_adjustment; /* [W] */
|
|
u_int64_t th_scale; /* [W] */
|
|
u_int th_offset_count; /* [W] */
|
|
struct bintime th_boottime; /* [T,W] */
|
|
struct bintime th_offset; /* [W] */
|
|
struct bintime th_naptime; /* [W] */
|
|
struct timeval th_microtime; /* [W] */
|
|
struct timespec th_nanotime; /* [W] */
|
|
/* Fields not to be copied in tc_windup start with th_generation. */
|
|
volatile u_int th_generation; /* [W] */
|
|
struct timehands *th_next; /* [I] */
|
|
};
|
|
|
|
static struct timehands th0;
|
|
static struct timehands th1 = {
|
|
.th_next = &th0
|
|
};
|
|
static struct timehands th0 = {
|
|
.th_counter = &dummy_timecounter,
|
|
.th_scale = UINT64_MAX / 1000000,
|
|
.th_offset = { .sec = 0, .frac = 0 },
|
|
.th_generation = 1,
|
|
.th_next = &th1
|
|
};
|
|
|
|
struct rwlock tc_lock = RWLOCK_INITIALIZER("tc_lock");
|
|
|
|
/*
|
|
* tc_windup() must be called before leaving this mutex.
|
|
*/
|
|
struct mutex windup_mtx = MUTEX_INITIALIZER(IPL_CLOCK);
|
|
|
|
static struct timehands *volatile timehands = &th0; /* [W] */
|
|
struct timecounter *timecounter = &dummy_timecounter; /* [T] */
|
|
static SLIST_HEAD(, timecounter) tc_list = SLIST_HEAD_INITIALIZER(tc_list);
|
|
|
|
/*
|
|
* These are updated from tc_windup(). They are useful when
|
|
* examining kernel core dumps.
|
|
*/
|
|
volatile time_t naptime = 0;
|
|
volatile time_t time_second = 0;
|
|
volatile time_t time_uptime = 0;
|
|
|
|
static int timestepwarnings;
|
|
|
|
void ntp_update_second(struct timehands *);
|
|
void tc_windup(struct bintime *, struct bintime *, int64_t *);
|
|
|
|
/*
|
|
* Return the difference between the timehands' counter value now and what
|
|
* was when we copied it to the timehands' offset_count.
|
|
*/
|
|
static __inline u_int
|
|
tc_delta(struct timehands *th)
|
|
{
|
|
struct timecounter *tc;
|
|
|
|
tc = th->th_counter;
|
|
return ((tc->tc_get_timecount(tc) - th->th_offset_count) &
|
|
tc->tc_counter_mask);
|
|
}
|
|
|
|
/*
|
|
* Functions for reading the time. We have to loop until we are sure that
|
|
* the timehands that we operated on was not updated under our feet. See
|
|
* the comment in <sys/time.h> for a description of these functions.
|
|
*/
|
|
|
|
void
|
|
binboottime(struct bintime *bt)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
*bt = th->th_boottime;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
microboottime(struct timeval *tvp)
|
|
{
|
|
struct bintime bt;
|
|
|
|
binboottime(&bt);
|
|
BINTIME_TO_TIMEVAL(&bt, tvp);
|
|
}
|
|
|
|
void
|
|
nanoboottime(struct timespec *tsp)
|
|
{
|
|
struct bintime bt;
|
|
|
|
binboottime(&bt);
|
|
BINTIME_TO_TIMESPEC(&bt, tsp);
|
|
}
|
|
|
|
void
|
|
binuptime(struct bintime *bt)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
TIMECOUNT_TO_BINTIME(tc_delta(th), th->th_scale, bt);
|
|
bintimeadd(bt, &th->th_offset, bt);
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
getbinuptime(struct bintime *bt)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
*bt = th->th_offset;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
nanouptime(struct timespec *tsp)
|
|
{
|
|
struct bintime bt;
|
|
|
|
binuptime(&bt);
|
|
BINTIME_TO_TIMESPEC(&bt, tsp);
|
|
}
|
|
|
|
void
|
|
microuptime(struct timeval *tvp)
|
|
{
|
|
struct bintime bt;
|
|
|
|
binuptime(&bt);
|
|
BINTIME_TO_TIMEVAL(&bt, tvp);
|
|
}
|
|
|
|
time_t
|
|
getuptime(void)
|
|
{
|
|
#if defined(__LP64__)
|
|
return time_uptime; /* atomic */
|
|
#else
|
|
time_t now;
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
now = th->th_offset.sec;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
|
|
return now;
|
|
#endif
|
|
}
|
|
|
|
uint64_t
|
|
nsecuptime(void)
|
|
{
|
|
struct bintime bt;
|
|
|
|
binuptime(&bt);
|
|
return BINTIME_TO_NSEC(&bt);
|
|
}
|
|
|
|
uint64_t
|
|
getnsecuptime(void)
|
|
{
|
|
struct bintime bt;
|
|
|
|
getbinuptime(&bt);
|
|
return BINTIME_TO_NSEC(&bt);
|
|
}
|
|
|
|
void
|
|
binruntime(struct bintime *bt)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
TIMECOUNT_TO_BINTIME(tc_delta(th), th->th_scale, bt);
|
|
bintimeadd(bt, &th->th_offset, bt);
|
|
bintimesub(bt, &th->th_naptime, bt);
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
nanoruntime(struct timespec *ts)
|
|
{
|
|
struct bintime bt;
|
|
|
|
binruntime(&bt);
|
|
BINTIME_TO_TIMESPEC(&bt, ts);
|
|
}
|
|
|
|
void
|
|
getbinruntime(struct bintime *bt)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
bintimesub(&th->th_offset, &th->th_naptime, bt);
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
uint64_t
|
|
getnsecruntime(void)
|
|
{
|
|
struct bintime bt;
|
|
|
|
getbinruntime(&bt);
|
|
return BINTIME_TO_NSEC(&bt);
|
|
}
|
|
|
|
void
|
|
bintime(struct bintime *bt)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
TIMECOUNT_TO_BINTIME(tc_delta(th), th->th_scale, bt);
|
|
bintimeadd(bt, &th->th_offset, bt);
|
|
bintimeadd(bt, &th->th_boottime, bt);
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
nanotime(struct timespec *tsp)
|
|
{
|
|
struct bintime bt;
|
|
|
|
bintime(&bt);
|
|
BINTIME_TO_TIMESPEC(&bt, tsp);
|
|
}
|
|
|
|
void
|
|
microtime(struct timeval *tvp)
|
|
{
|
|
struct bintime bt;
|
|
|
|
bintime(&bt);
|
|
BINTIME_TO_TIMEVAL(&bt, tvp);
|
|
}
|
|
|
|
time_t
|
|
gettime(void)
|
|
{
|
|
#if defined(__LP64__)
|
|
return time_second; /* atomic */
|
|
#else
|
|
time_t now;
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
now = th->th_microtime.tv_sec;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
|
|
return now;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
getnanouptime(struct timespec *tsp)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
BINTIME_TO_TIMESPEC(&th->th_offset, tsp);
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
getmicrouptime(struct timeval *tvp)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
BINTIME_TO_TIMEVAL(&th->th_offset, tvp);
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
getnanotime(struct timespec *tsp)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
*tsp = th->th_nanotime;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
void
|
|
getmicrotime(struct timeval *tvp)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
*tvp = th->th_microtime;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
|
|
/*
|
|
* Initialize a new timecounter and possibly use it.
|
|
*/
|
|
void
|
|
tc_init(struct timecounter *tc)
|
|
{
|
|
u_int64_t tmp;
|
|
u_int u;
|
|
|
|
u = tc->tc_frequency / tc->tc_counter_mask;
|
|
/* XXX: We need some margin here, 10% is a guess */
|
|
u *= 11;
|
|
u /= 10;
|
|
if (tc->tc_quality >= 0) {
|
|
if (u > hz) {
|
|
tc->tc_quality = -2000;
|
|
printf("Timecounter \"%s\" frequency %lu Hz",
|
|
tc->tc_name, (unsigned long)tc->tc_frequency);
|
|
printf(" -- Insufficient hz, needs at least %u\n", u);
|
|
}
|
|
}
|
|
|
|
/* Determine the counter's precision. */
|
|
for (tmp = 1; (tmp & tc->tc_counter_mask) == 0; tmp <<= 1)
|
|
continue;
|
|
tc->tc_precision = tmp;
|
|
|
|
SLIST_INSERT_HEAD(&tc_list, tc, tc_next);
|
|
|
|
/*
|
|
* Never automatically use a timecounter with negative quality.
|
|
* Even though we run on the dummy counter, switching here may be
|
|
* worse since this timecounter may not be monotonic.
|
|
*/
|
|
if (tc->tc_quality < 0)
|
|
return;
|
|
if (tc->tc_quality < timecounter->tc_quality)
|
|
return;
|
|
if (tc->tc_quality == timecounter->tc_quality &&
|
|
tc->tc_frequency < timecounter->tc_frequency)
|
|
return;
|
|
(void)tc->tc_get_timecount(tc);
|
|
enqueue_randomness(tc->tc_get_timecount(tc));
|
|
|
|
timecounter = tc;
|
|
}
|
|
|
|
/*
|
|
* Change the given timecounter's quality. If it is the active
|
|
* counter and it is no longer the best counter, activate the
|
|
* best counter.
|
|
*/
|
|
void
|
|
tc_reset_quality(struct timecounter *tc, int quality)
|
|
{
|
|
struct timecounter *best = &dummy_timecounter, *tmp;
|
|
|
|
if (tc == &dummy_timecounter)
|
|
panic("%s: cannot change dummy counter quality", __func__);
|
|
|
|
tc->tc_quality = quality;
|
|
if (timecounter == tc) {
|
|
SLIST_FOREACH(tmp, &tc_list, tc_next) {
|
|
if (tmp->tc_quality < 0)
|
|
continue;
|
|
if (tmp->tc_quality < best->tc_quality)
|
|
continue;
|
|
if (tmp->tc_quality == best->tc_quality &&
|
|
tmp->tc_frequency < best->tc_frequency)
|
|
continue;
|
|
best = tmp;
|
|
}
|
|
if (best != tc) {
|
|
enqueue_randomness(best->tc_get_timecount(best));
|
|
timecounter = best;
|
|
printf("timecounter: active counter changed: %s -> %s\n",
|
|
tc->tc_name, best->tc_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Report the frequency of the current timecounter. */
|
|
u_int64_t
|
|
tc_getfrequency(void)
|
|
{
|
|
return (timehands->th_counter->tc_frequency);
|
|
}
|
|
|
|
/* Report the precision of the current timecounter. */
|
|
u_int64_t
|
|
tc_getprecision(void)
|
|
{
|
|
return (timehands->th_counter->tc_precision);
|
|
}
|
|
|
|
/*
|
|
* Step our concept of UTC, aka the realtime clock.
|
|
* This is done by modifying our estimate of when we booted.
|
|
*
|
|
* Any ongoing adjustment is meaningless after a clock jump,
|
|
* so we zero adjtimedelta here as well.
|
|
*/
|
|
void
|
|
tc_setrealtimeclock(const struct timespec *ts)
|
|
{
|
|
struct bintime boottime, old_utc, uptime, utc;
|
|
struct timespec tmp;
|
|
int64_t zero = 0;
|
|
|
|
TIMESPEC_TO_BINTIME(ts, &utc);
|
|
|
|
rw_enter_write(&tc_lock);
|
|
mtx_enter(&windup_mtx);
|
|
|
|
binuptime(&uptime);
|
|
bintimesub(&utc, &uptime, &boottime);
|
|
bintimeadd(&timehands->th_boottime, &uptime, &old_utc);
|
|
/* XXX fiddle all the little crinkly bits around the fiords... */
|
|
tc_windup(&boottime, NULL, &zero);
|
|
|
|
mtx_leave(&windup_mtx);
|
|
rw_exit_write(&tc_lock);
|
|
|
|
enqueue_randomness(ts->tv_sec);
|
|
|
|
if (timestepwarnings) {
|
|
BINTIME_TO_TIMESPEC(&old_utc, &tmp);
|
|
log(LOG_INFO, "Time stepped from %lld.%09ld to %lld.%09ld\n",
|
|
(long long)tmp.tv_sec, tmp.tv_nsec,
|
|
(long long)ts->tv_sec, ts->tv_nsec);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Step the monotonic and realtime clocks, triggering any timeouts that
|
|
* should have occurred across the interval.
|
|
*/
|
|
void
|
|
tc_setclock(const struct timespec *ts)
|
|
{
|
|
struct bintime new_naptime, old_naptime, uptime, utc;
|
|
static int first = 1;
|
|
#ifndef SMALL_KERNEL
|
|
struct bintime elapsed;
|
|
long long adj_ticks;
|
|
#endif
|
|
|
|
/*
|
|
* When we're called for the first time, during boot when
|
|
* the root partition is mounted, we need to set boottime.
|
|
*/
|
|
if (first) {
|
|
tc_setrealtimeclock(ts);
|
|
first = 0;
|
|
return;
|
|
}
|
|
|
|
enqueue_randomness(ts->tv_sec);
|
|
|
|
TIMESPEC_TO_BINTIME(ts, &utc);
|
|
|
|
mtx_enter(&windup_mtx);
|
|
|
|
bintimesub(&utc, &timehands->th_boottime, &uptime);
|
|
old_naptime = timehands->th_naptime;
|
|
/* XXX fiddle all the little crinkly bits around the fiords... */
|
|
tc_windup(NULL, &uptime, NULL);
|
|
new_naptime = timehands->th_naptime;
|
|
|
|
mtx_leave(&windup_mtx);
|
|
|
|
#ifndef SMALL_KERNEL
|
|
/* convert the bintime to ticks */
|
|
bintimesub(&new_naptime, &old_naptime, &elapsed);
|
|
adj_ticks = BINTIME_TO_NSEC(&elapsed) / tick_nsec;
|
|
if (adj_ticks > 0) {
|
|
if (adj_ticks > INT_MAX)
|
|
adj_ticks = INT_MAX;
|
|
timeout_adjust_ticks(adj_ticks);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
tc_update_timekeep(void)
|
|
{
|
|
static struct timecounter *last_tc = NULL;
|
|
struct timehands *th;
|
|
|
|
MUTEX_ASSERT_LOCKED(&windup_mtx);
|
|
|
|
if (timekeep == NULL)
|
|
return;
|
|
|
|
th = timehands;
|
|
timekeep->tk_generation = 0;
|
|
membar_producer();
|
|
timekeep->tk_scale = th->th_scale;
|
|
timekeep->tk_offset_count = th->th_offset_count;
|
|
timekeep->tk_offset = th->th_offset;
|
|
timekeep->tk_naptime = th->th_naptime;
|
|
timekeep->tk_boottime = th->th_boottime;
|
|
if (last_tc != th->th_counter) {
|
|
timekeep->tk_counter_mask = th->th_counter->tc_counter_mask;
|
|
timekeep->tk_user = th->th_counter->tc_user;
|
|
last_tc = th->th_counter;
|
|
}
|
|
membar_producer();
|
|
timekeep->tk_generation = th->th_generation;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Initialize the next struct timehands in the ring and make
|
|
* it the active timehands. Along the way we might switch to a different
|
|
* timecounter and/or do seconds processing in NTP. Slightly magic.
|
|
*/
|
|
void
|
|
tc_windup(struct bintime *new_boottime, struct bintime *new_offset,
|
|
int64_t *new_adjtimedelta)
|
|
{
|
|
struct bintime bt;
|
|
struct timecounter *active_tc;
|
|
struct timehands *th, *tho;
|
|
u_int64_t scale;
|
|
u_int delta, ncount, ogen;
|
|
|
|
if (new_boottime != NULL || new_adjtimedelta != NULL)
|
|
rw_assert_wrlock(&tc_lock);
|
|
MUTEX_ASSERT_LOCKED(&windup_mtx);
|
|
|
|
active_tc = timecounter;
|
|
|
|
/*
|
|
* Make the next timehands a copy of the current one, but do not
|
|
* overwrite the generation or next pointer. While we update
|
|
* the contents, the generation must be zero.
|
|
*/
|
|
tho = timehands;
|
|
ogen = tho->th_generation;
|
|
th = tho->th_next;
|
|
th->th_generation = 0;
|
|
membar_producer();
|
|
memcpy(th, tho, offsetof(struct timehands, th_generation));
|
|
|
|
/*
|
|
* Capture a timecounter delta on the current timecounter and if
|
|
* changing timecounters, a counter value from the new timecounter.
|
|
* Update the offset fields accordingly.
|
|
*/
|
|
delta = tc_delta(th);
|
|
if (th->th_counter != active_tc)
|
|
ncount = active_tc->tc_get_timecount(active_tc);
|
|
else
|
|
ncount = 0;
|
|
th->th_offset_count += delta;
|
|
th->th_offset_count &= th->th_counter->tc_counter_mask;
|
|
TIMECOUNT_TO_BINTIME(delta, th->th_scale, &bt);
|
|
bintimeadd(&th->th_offset, &bt, &th->th_offset);
|
|
|
|
/*
|
|
* Ignore new offsets that predate the current offset.
|
|
* If changing the offset, first increase the naptime
|
|
* accordingly.
|
|
*/
|
|
if (new_offset != NULL && bintimecmp(&th->th_offset, new_offset, <)) {
|
|
bintimesub(new_offset, &th->th_offset, &bt);
|
|
bintimeadd(&th->th_naptime, &bt, &th->th_naptime);
|
|
naptime = th->th_naptime.sec;
|
|
th->th_offset = *new_offset;
|
|
}
|
|
|
|
/*
|
|
* If changing the boot time or clock adjustment, do so before
|
|
* NTP processing.
|
|
*/
|
|
if (new_boottime != NULL)
|
|
th->th_boottime = *new_boottime;
|
|
if (new_adjtimedelta != NULL) {
|
|
th->th_adjtimedelta = *new_adjtimedelta;
|
|
/* Reset the NTP update period. */
|
|
bintimesub(&th->th_offset, &th->th_naptime,
|
|
&th->th_next_ntp_update);
|
|
}
|
|
|
|
/*
|
|
* Deal with NTP second processing. The while-loop normally
|
|
* iterates at most once, but in extreme situations it might
|
|
* keep NTP sane if tc_windup() is not run for several seconds.
|
|
*/
|
|
bintimesub(&th->th_offset, &th->th_naptime, &bt);
|
|
while (bintimecmp(&th->th_next_ntp_update, &bt, <=)) {
|
|
ntp_update_second(th);
|
|
th->th_next_ntp_update.sec++;
|
|
}
|
|
|
|
/* Update the UTC timestamps used by the get*() functions. */
|
|
bintimeadd(&th->th_boottime, &th->th_offset, &bt);
|
|
BINTIME_TO_TIMEVAL(&bt, &th->th_microtime);
|
|
BINTIME_TO_TIMESPEC(&bt, &th->th_nanotime);
|
|
|
|
/* Now is a good time to change timecounters. */
|
|
if (th->th_counter != active_tc) {
|
|
th->th_counter = active_tc;
|
|
th->th_offset_count = ncount;
|
|
}
|
|
|
|
/*-
|
|
* Recalculate the scaling factor. We want the number of 1/2^64
|
|
* fractions of a second per period of the hardware counter, taking
|
|
* into account the th_adjustment factor which the NTP PLL/adjtime(2)
|
|
* processing provides us with.
|
|
*
|
|
* The th_adjustment is nanoseconds per second with 32 bit binary
|
|
* fraction and we want 64 bit binary fraction of second:
|
|
*
|
|
* x = a * 2^32 / 10^9 = a * 4.294967296
|
|
*
|
|
* The range of th_adjustment is +/- 5000PPM so inside a 64bit int
|
|
* we can only multiply by about 850 without overflowing, but that
|
|
* leaves suitably precise fractions for multiply before divide.
|
|
*
|
|
* Divide before multiply with a fraction of 2199/512 results in a
|
|
* systematic undercompensation of 10PPM of th_adjustment. On a
|
|
* 5000PPM adjustment this is a 0.05PPM error. This is acceptable.
|
|
*
|
|
* We happily sacrifice the lowest of the 64 bits of our result
|
|
* to the goddess of code clarity.
|
|
*
|
|
*/
|
|
scale = (u_int64_t)1 << 63;
|
|
scale += \
|
|
((th->th_adjustment + th->th_counter->tc_freq_adj) / 1024) * 2199;
|
|
scale /= th->th_counter->tc_frequency;
|
|
th->th_scale = scale * 2;
|
|
|
|
/*
|
|
* Now that the struct timehands is again consistent, set the new
|
|
* generation number, making sure to not make it zero.
|
|
*/
|
|
if (++ogen == 0)
|
|
ogen = 1;
|
|
membar_producer();
|
|
th->th_generation = ogen;
|
|
|
|
/* Go live with the new struct timehands. */
|
|
time_second = th->th_microtime.tv_sec;
|
|
time_uptime = th->th_offset.sec;
|
|
membar_producer();
|
|
timehands = th;
|
|
|
|
tc_update_timekeep();
|
|
}
|
|
|
|
/* Report or change the active timecounter hardware. */
|
|
int
|
|
sysctl_tc_hardware(void *oldp, size_t *oldlenp, void *newp, size_t newlen)
|
|
{
|
|
char newname[32];
|
|
struct timecounter *newtc, *tc;
|
|
int error;
|
|
|
|
tc = timecounter;
|
|
strlcpy(newname, tc->tc_name, sizeof(newname));
|
|
|
|
error = sysctl_string(oldp, oldlenp, newp, newlen, newname, sizeof(newname));
|
|
if (error != 0 || strcmp(newname, tc->tc_name) == 0)
|
|
return (error);
|
|
SLIST_FOREACH(newtc, &tc_list, tc_next) {
|
|
if (strcmp(newname, newtc->tc_name) != 0)
|
|
continue;
|
|
|
|
/* Warm up new timecounter. */
|
|
(void)newtc->tc_get_timecount(newtc);
|
|
(void)newtc->tc_get_timecount(newtc);
|
|
|
|
rw_enter_write(&tc_lock);
|
|
timecounter = newtc;
|
|
rw_exit_write(&tc_lock);
|
|
|
|
return (0);
|
|
}
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Report or change the active timecounter hardware. */
|
|
int
|
|
sysctl_tc_choice(void *oldp, size_t *oldlenp, void *newp, size_t newlen)
|
|
{
|
|
char buf[32], *spc, *choices;
|
|
struct timecounter *tc;
|
|
int error, maxlen;
|
|
|
|
if (SLIST_EMPTY(&tc_list))
|
|
return (sysctl_rdstring(oldp, oldlenp, newp, ""));
|
|
|
|
spc = "";
|
|
maxlen = 0;
|
|
SLIST_FOREACH(tc, &tc_list, tc_next)
|
|
maxlen += sizeof(buf);
|
|
choices = malloc(maxlen, M_TEMP, M_WAITOK);
|
|
*choices = '\0';
|
|
SLIST_FOREACH(tc, &tc_list, tc_next) {
|
|
snprintf(buf, sizeof(buf), "%s%s(%d)",
|
|
spc, tc->tc_name, tc->tc_quality);
|
|
spc = " ";
|
|
strlcat(choices, buf, maxlen);
|
|
}
|
|
error = sysctl_rdstring(oldp, oldlenp, newp, choices);
|
|
free(choices, M_TEMP, maxlen);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Timecounters need to be updated every so often to prevent the hardware
|
|
* counter from overflowing. Updating also recalculates the cached values
|
|
* used by the get*() family of functions, so their precision depends on
|
|
* the update frequency.
|
|
*/
|
|
static int tc_tick;
|
|
|
|
void
|
|
tc_ticktock(void)
|
|
{
|
|
static int count;
|
|
|
|
if (++count < tc_tick)
|
|
return;
|
|
if (!mtx_enter_try(&windup_mtx))
|
|
return;
|
|
count = 0;
|
|
tc_windup(NULL, NULL, NULL);
|
|
mtx_leave(&windup_mtx);
|
|
}
|
|
|
|
void
|
|
inittimecounter(void)
|
|
{
|
|
#ifdef DEBUG
|
|
u_int p;
|
|
#endif
|
|
|
|
/*
|
|
* Set the initial timeout to
|
|
* max(1, <approx. number of hardclock ticks in a millisecond>).
|
|
* People should probably not use the sysctl to set the timeout
|
|
* to smaller than its initial value, since that value is the
|
|
* smallest reasonable one. If they want better timestamps they
|
|
* should use the non-"get"* functions.
|
|
*/
|
|
if (hz > 1000)
|
|
tc_tick = (hz + 500) / 1000;
|
|
else
|
|
tc_tick = 1;
|
|
#ifdef DEBUG
|
|
p = (tc_tick * 1000000) / hz;
|
|
printf("Timecounters tick every %d.%03u msec\n", p / 1000, p % 1000);
|
|
#endif
|
|
|
|
/* warm up new timecounter (again) and get rolling. */
|
|
(void)timecounter->tc_get_timecount(timecounter);
|
|
(void)timecounter->tc_get_timecount(timecounter);
|
|
}
|
|
|
|
const struct sysctl_bounded_args tc_vars[] = {
|
|
{ KERN_TIMECOUNTER_TICK, &tc_tick, SYSCTL_INT_READONLY },
|
|
{ KERN_TIMECOUNTER_TIMESTEPWARNINGS, ×tepwarnings, 0, 1 },
|
|
};
|
|
|
|
/*
|
|
* Return timecounter-related information.
|
|
*/
|
|
int
|
|
sysctl_tc(int *name, u_int namelen, void *oldp, size_t *oldlenp,
|
|
void *newp, size_t newlen)
|
|
{
|
|
if (namelen != 1)
|
|
return (ENOTDIR);
|
|
|
|
switch (name[0]) {
|
|
case KERN_TIMECOUNTER_HARDWARE:
|
|
return (sysctl_tc_hardware(oldp, oldlenp, newp, newlen));
|
|
case KERN_TIMECOUNTER_CHOICE:
|
|
return (sysctl_tc_choice(oldp, oldlenp, newp, newlen));
|
|
default:
|
|
return (sysctl_bounded_arr(tc_vars, nitems(tc_vars), name,
|
|
namelen, oldp, oldlenp, newp, newlen));
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/*
|
|
* Skew the timehands according to any adjtime(2) adjustment.
|
|
*/
|
|
void
|
|
ntp_update_second(struct timehands *th)
|
|
{
|
|
int64_t adj;
|
|
|
|
MUTEX_ASSERT_LOCKED(&windup_mtx);
|
|
|
|
if (th->th_adjtimedelta > 0)
|
|
adj = MIN(5000, th->th_adjtimedelta);
|
|
else
|
|
adj = MAX(-5000, th->th_adjtimedelta);
|
|
th->th_adjtimedelta -= adj;
|
|
th->th_adjustment = (adj * 1000) << 32;
|
|
}
|
|
|
|
void
|
|
tc_adjfreq(int64_t *old, int64_t *new)
|
|
{
|
|
if (old != NULL) {
|
|
rw_assert_anylock(&tc_lock);
|
|
*old = timecounter->tc_freq_adj;
|
|
}
|
|
if (new != NULL) {
|
|
rw_assert_wrlock(&tc_lock);
|
|
mtx_enter(&windup_mtx);
|
|
timecounter->tc_freq_adj = *new;
|
|
tc_windup(NULL, NULL, NULL);
|
|
mtx_leave(&windup_mtx);
|
|
}
|
|
}
|
|
|
|
void
|
|
tc_adjtime(int64_t *old, int64_t *new)
|
|
{
|
|
struct timehands *th;
|
|
u_int gen;
|
|
|
|
if (old != NULL) {
|
|
do {
|
|
th = timehands;
|
|
gen = th->th_generation;
|
|
membar_consumer();
|
|
*old = th->th_adjtimedelta;
|
|
membar_consumer();
|
|
} while (gen == 0 || gen != th->th_generation);
|
|
}
|
|
if (new != NULL) {
|
|
rw_assert_wrlock(&tc_lock);
|
|
mtx_enter(&windup_mtx);
|
|
tc_windup(NULL, NULL, new);
|
|
mtx_leave(&windup_mtx);
|
|
}
|
|
}
|