#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);
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,
};
struct timehands {
struct timecounter *th_counter;
int64_t th_adjtimedelta;
struct bintime th_next_ntp_update;
int64_t th_adjustment;
u_int64_t th_scale;
u_int th_offset_count;
struct bintime th_boottime;
struct bintime th_offset;
struct bintime th_naptime;
struct timeval th_microtime;
struct timespec th_nanotime;
volatile u_int th_generation;
struct timehands *th_next;
};
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");
struct mutex windup_mtx = MUTEX_INITIALIZER(IPL_CLOCK);
static struct timehands *volatile timehands = &th0;
struct timecounter *timecounter = &dummy_timecounter;
static SLIST_HEAD(, timecounter) tc_list = SLIST_HEAD_INITIALIZER(tc_list);
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 *);
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);
}
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;
#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;
#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);
}
void
tc_init(struct timecounter *tc)
{
u_int64_t tmp;
u_int u;
u = tc->tc_frequency / tc->tc_counter_mask;
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);
}
}
for (tmp = 1; (tmp & tc->tc_counter_mask) == 0; tmp <<= 1)
continue;
tc->tc_precision = tmp;
SLIST_INSERT_HEAD(&tc_list, tc, tc_next);
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;
}
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);
}
}
}
u_int64_t
tc_getfrequency(void)
{
return (timehands->th_counter->tc_frequency);
}
u_int64_t
tc_getprecision(void)
{
return (timehands->th_counter->tc_precision);
}
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);
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);
}
}
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
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;
tc_windup(NULL, &uptime, NULL);
new_naptime = timehands->th_naptime;
mtx_leave(&windup_mtx);
#ifndef SMALL_KERNEL
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;
}
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;
tho = timehands;
ogen = tho->th_generation;
th = tho->th_next;
th->th_generation = 0;
membar_producer();
memcpy(th, tho, offsetof(struct timehands, th_generation));
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);
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 (new_boottime != NULL)
th->th_boottime = *new_boottime;
if (new_adjtimedelta != NULL) {
th->th_adjtimedelta = *new_adjtimedelta;
bintimesub(&th->th_offset, &th->th_naptime,
&th->th_next_ntp_update);
}
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++;
}
bintimeadd(&th->th_boottime, &th->th_offset, &bt);
BINTIME_TO_TIMEVAL(&bt, &th->th_microtime);
BINTIME_TO_TIMESPEC(&bt, &th->th_nanotime);
if (th->th_counter != active_tc) {
th->th_counter = active_tc;
th->th_offset_count = ncount;
}
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;
if (++ogen == 0)
ogen = 1;
membar_producer();
th->th_generation = ogen;
time_second = th->th_microtime.tv_sec;
time_uptime = th->th_offset.sec;
membar_producer();
timehands = th;
tc_update_timekeep();
}
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);
}
#ifndef SMALL_KERNEL
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;
(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);
}
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);
}
const struct sysctl_bounded_args tc_vars[] = {
{ KERN_TIMECOUNTER_TICK, &tc_tick, SYSCTL_INT_READONLY },
{ KERN_TIMECOUNTER_TIMESTEPWARNINGS, ×tepwarnings, 0, 1 },
};
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));
}
}
#endif
void
inittimecounter(void)
{
#ifdef DEBUG
u_int p;
#endif
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
(void)timecounter->tc_get_timecount(timecounter);
(void)timecounter->tc_get_timecount(timecounter);
}
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);
}
}