diff options
Diffstat (limited to 'sys/kern/kern_tc.c')
-rw-r--r-- | sys/kern/kern_tc.c | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/sys/kern/kern_tc.c b/sys/kern/kern_tc.c new file mode 100644 index 0000000..23a6606 --- /dev/null +++ b/sys/kern/kern_tc.c @@ -0,0 +1,581 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.ORG> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + */ + +#include "opt_ntp.h" + +#include <sys/param.h> +#include <sys/timetc.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/timex.h> +#include <sys/timepps.h> + +/* + * Number of timecounters used to implement stable storage + */ +#ifndef NTIMECOUNTER +#define NTIMECOUNTER 5 +#endif + +static MALLOC_DEFINE(M_TIMECOUNTER, "timecounter", + "Timecounter stable storage"); + +static void tco_setscales __P((struct timecounter *tc)); +static __inline unsigned tco_delta __P((struct timecounter *tc)); + +time_t time_second; + +struct timeval boottime; +SYSCTL_STRUCT(_kern, KERN_BOOTTIME, boottime, CTLFLAG_RD, + &boottime, timeval, "System boottime"); + +SYSCTL_NODE(_kern, OID_AUTO, timecounter, CTLFLAG_RW, 0, ""); + +static unsigned nmicrotime; +static unsigned nnanotime; +static unsigned ngetmicrotime; +static unsigned ngetnanotime; +static unsigned nmicrouptime; +static unsigned nnanouptime; +static unsigned ngetmicrouptime; +static unsigned ngetnanouptime; +SYSCTL_INT(_kern_timecounter, OID_AUTO, nmicrotime, CTLFLAG_RD, &nmicrotime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, nnanotime, CTLFLAG_RD, &nnanotime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, nmicrouptime, CTLFLAG_RD, &nmicrouptime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, nnanouptime, CTLFLAG_RD, &nnanouptime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, ngetmicrotime, CTLFLAG_RD, &ngetmicrotime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, ngetnanotime, CTLFLAG_RD, &ngetnanotime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, ngetmicrouptime, CTLFLAG_RD, &ngetmicrouptime, 0, ""); +SYSCTL_INT(_kern_timecounter, OID_AUTO, ngetnanouptime, CTLFLAG_RD, &ngetnanouptime, 0, ""); + +/* + * 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 + * timeservices. + */ + +static unsigned +dummy_get_timecount(struct timecounter *tc) +{ + static unsigned now; + + return (++now); +} + +static struct timecounter dummy_timecounter = { + dummy_get_timecount, + 0, + ~0u, + 1000000, + "dummy" +}; + +struct timecounter *timecounter = &dummy_timecounter; + +static __inline unsigned +tco_delta(struct timecounter *tc) +{ + + return ((tc->tc_get_timecount(tc) - tc->tc_offset_count) & + tc->tc_counter_mask); +} + +/* + * We have eight functions for looking at the clock, four for + * microseconds and four for nanoseconds. For each there is fast + * but less precise version "get{nano|micro}[up]time" which will + * return a time which is up to 1/HZ previous to the call, whereas + * the raw version "{nano|micro}[up]time" will return a timestamp + * which is as precise as possible. The "up" variants return the + * time relative to system boot, these are well suited for time + * interval measurements. + */ + +void +getmicrotime(struct timeval *tvp) +{ + struct timecounter *tc; + + ngetmicrotime++; + tc = timecounter; + *tvp = tc->tc_microtime; +} + +void +getnanotime(struct timespec *tsp) +{ + struct timecounter *tc; + + ngetnanotime++; + tc = timecounter; + *tsp = tc->tc_nanotime; +} + +void +microtime(struct timeval *tv) +{ + struct timecounter *tc; + + nmicrotime++; + tc = timecounter; + tv->tv_sec = tc->tc_offset_sec; + tv->tv_usec = tc->tc_offset_micro; + tv->tv_usec += ((u_int64_t)tco_delta(tc) * tc->tc_scale_micro) >> 32; + tv->tv_usec += boottime.tv_usec; + tv->tv_sec += boottime.tv_sec; + while (tv->tv_usec >= 1000000) { + tv->tv_usec -= 1000000; + tv->tv_sec++; + } +} + +void +nanotime(struct timespec *ts) +{ + unsigned count; + u_int64_t delta; + struct timecounter *tc; + + nnanotime++; + tc = timecounter; + ts->tv_sec = tc->tc_offset_sec; + count = tco_delta(tc); + delta = tc->tc_offset_nano; + delta += ((u_int64_t)count * tc->tc_scale_nano_f); + delta >>= 32; + delta += ((u_int64_t)count * tc->tc_scale_nano_i); + delta += boottime.tv_usec * 1000; + ts->tv_sec += boottime.tv_sec; + while (delta >= 1000000000) { + delta -= 1000000000; + ts->tv_sec++; + } + ts->tv_nsec = delta; +} + +void +getmicrouptime(struct timeval *tvp) +{ + struct timecounter *tc; + + ngetmicrouptime++; + tc = timecounter; + tvp->tv_sec = tc->tc_offset_sec; + tvp->tv_usec = tc->tc_offset_micro; +} + +void +getnanouptime(struct timespec *tsp) +{ + struct timecounter *tc; + + ngetnanouptime++; + tc = timecounter; + tsp->tv_sec = tc->tc_offset_sec; + tsp->tv_nsec = tc->tc_offset_nano >> 32; +} + +void +microuptime(struct timeval *tv) +{ + struct timecounter *tc; + + nmicrouptime++; + tc = timecounter; + tv->tv_sec = tc->tc_offset_sec; + tv->tv_usec = tc->tc_offset_micro; + tv->tv_usec += ((u_int64_t)tco_delta(tc) * tc->tc_scale_micro) >> 32; + if (tv->tv_usec >= 1000000) { + tv->tv_usec -= 1000000; + tv->tv_sec++; + } +} + +void +nanouptime(struct timespec *ts) +{ + unsigned count; + u_int64_t delta; + struct timecounter *tc; + + nnanouptime++; + tc = timecounter; + ts->tv_sec = tc->tc_offset_sec; + count = tco_delta(tc); + delta = tc->tc_offset_nano; + delta += ((u_int64_t)count * tc->tc_scale_nano_f); + delta >>= 32; + delta += ((u_int64_t)count * tc->tc_scale_nano_i); + if (delta >= 1000000000) { + delta -= 1000000000; + ts->tv_sec++; + } + ts->tv_nsec = delta; +} + +static void +tco_setscales(struct timecounter *tc) +{ + u_int64_t scale; + + scale = 1000000000LL << 32; + scale += tc->tc_adjustment; + scale /= tc->tc_tweak->tc_frequency; + tc->tc_scale_micro = scale / 1000; + tc->tc_scale_nano_f = scale & 0xffffffff; + tc->tc_scale_nano_i = scale >> 32; +} + +void +tc_update(struct timecounter *tc) +{ + tco_setscales(tc); +} + +void +tc_init(struct timecounter *tc) +{ + struct timespec ts1; + struct timecounter *t1, *t2, *t3; + int i; + + tc->tc_adjustment = 0; + tc->tc_tweak = tc; + tco_setscales(tc); + tc->tc_offset_count = tc->tc_get_timecount(tc); + if (timecounter == &dummy_timecounter) + tc->tc_avail = tc; + else { + tc->tc_avail = timecounter->tc_tweak->tc_avail; + timecounter->tc_tweak->tc_avail = tc; + } + MALLOC(t1, struct timecounter *, sizeof *t1, M_TIMECOUNTER, M_WAITOK); + tc->tc_other = t1; + *t1 = *tc; + t2 = t1; + for (i = 1; i < NTIMECOUNTER; i++) { + MALLOC(t3, struct timecounter *, sizeof *t3, + M_TIMECOUNTER, M_WAITOK); + *t3 = *tc; + t3->tc_other = t2; + t2 = t3; + } + t1->tc_other = t3; + tc = t1; + + printf("Timecounter \"%s\" frequency %lu Hz\n", + tc->tc_name, (u_long)tc->tc_frequency); + + /* XXX: For now always start using the counter. */ + tc->tc_offset_count = tc->tc_get_timecount(tc); + nanouptime(&ts1); + tc->tc_offset_nano = (u_int64_t)ts1.tv_nsec << 32; + tc->tc_offset_micro = ts1.tv_nsec / 1000; + tc->tc_offset_sec = ts1.tv_sec; + timecounter = tc; +} + +void +tc_setclock(struct timespec *ts) +{ + struct timespec ts2; + + nanouptime(&ts2); + boottime.tv_sec = ts->tv_sec - ts2.tv_sec; + boottime.tv_usec = (ts->tv_nsec - ts2.tv_nsec) / 1000; + if (boottime.tv_usec < 0) { + boottime.tv_usec += 1000000; + boottime.tv_sec--; + } + /* fiddle all the little crinkly bits around the fiords... */ + tc_windup(); +} + +static void +switch_timecounter(struct timecounter *newtc) +{ + int s; + struct timecounter *tc; + struct timespec ts; + + s = splclock(); + tc = timecounter; + if (newtc->tc_tweak == tc->tc_tweak) { + splx(s); + return; + } + newtc = newtc->tc_tweak->tc_other; + nanouptime(&ts); + newtc->tc_offset_sec = ts.tv_sec; + newtc->tc_offset_nano = (u_int64_t)ts.tv_nsec << 32; + newtc->tc_offset_micro = ts.tv_nsec / 1000; + newtc->tc_offset_count = newtc->tc_get_timecount(newtc); + tco_setscales(newtc); + timecounter = newtc; + splx(s); +} + +static struct timecounter * +sync_other_counter(void) +{ + struct timecounter *tc, *tcn, *tco; + unsigned delta; + + tco = timecounter; + tc = tco->tc_other; + tcn = tc->tc_other; + *tc = *tco; + tc->tc_other = tcn; + delta = tco_delta(tc); + tc->tc_offset_count += delta; + tc->tc_offset_count &= tc->tc_counter_mask; + tc->tc_offset_nano += (u_int64_t)delta * tc->tc_scale_nano_f; + tc->tc_offset_nano += (u_int64_t)delta * tc->tc_scale_nano_i << 32; + return (tc); +} + +void +tc_windup(void) +{ + struct timecounter *tc, *tco; + struct timeval tvt; + + tco = timecounter; + tc = sync_other_counter(); + /* + * We may be inducing a tiny error here, the tc_poll_pps() may + * process a latched count which happens after the tco_delta() + * in sync_other_counter(), which would extend the previous + * counters parameters into the domain of this new one. + * Since the timewindow is very small for this, the error is + * going to be only a few weenieseconds (as Dave Mills would + * say), so lets just not talk more about it, OK ? + */ + if (tco->tc_poll_pps) + tco->tc_poll_pps(tco); + if (timedelta != 0) { + tvt = boottime; + tvt.tv_usec += tickdelta; + if (tvt.tv_usec >= 1000000) { + tvt.tv_sec++; + tvt.tv_usec -= 1000000; + } else if (tvt.tv_usec < 0) { + tvt.tv_sec--; + tvt.tv_usec += 1000000; + } + boottime = tvt; + timedelta -= tickdelta; + } + + while (tc->tc_offset_nano >= 1000000000ULL << 32) { + tc->tc_offset_nano -= 1000000000ULL << 32; + tc->tc_offset_sec++; + ntp_update_second(tc); /* XXX only needed if xntpd runs */ + tco_setscales(tc); + } + + tc->tc_offset_micro = (tc->tc_offset_nano / 1000) >> 32; + + /* Figure out the wall-clock time */ + tc->tc_nanotime.tv_sec = tc->tc_offset_sec + boottime.tv_sec; + tc->tc_nanotime.tv_nsec = + (tc->tc_offset_nano >> 32) + boottime.tv_usec * 1000; + tc->tc_microtime.tv_usec = tc->tc_offset_micro + boottime.tv_usec; + if (tc->tc_nanotime.tv_nsec >= 1000000000) { + tc->tc_nanotime.tv_nsec -= 1000000000; + tc->tc_microtime.tv_usec -= 1000000; + tc->tc_nanotime.tv_sec++; + } + time_second = tc->tc_microtime.tv_sec = tc->tc_nanotime.tv_sec; + + timecounter = tc; +} + +static int +sysctl_kern_timecounter_hardware SYSCTL_HANDLER_ARGS +{ + char newname[32]; + struct timecounter *newtc, *tc; + int error; + + tc = timecounter->tc_tweak; + strncpy(newname, tc->tc_name, sizeof(newname)); + error = sysctl_handle_string(oidp, &newname[0], sizeof(newname), req); + if (error == 0 && req->newptr != NULL && + strcmp(newname, tc->tc_name) != 0) { + for (newtc = tc->tc_avail; newtc != tc; + newtc = newtc->tc_avail) { + if (strcmp(newname, newtc->tc_name) == 0) { + /* Warm up new timecounter. */ + (void)newtc->tc_get_timecount(newtc); + + switch_timecounter(newtc); + return (0); + } + } + return (EINVAL); + } + return (error); +} + +SYSCTL_PROC(_kern_timecounter, OID_AUTO, hardware, CTLTYPE_STRING | CTLFLAG_RW, + 0, 0, sysctl_kern_timecounter_hardware, "A", ""); + + +int +pps_ioctl(u_long cmd, caddr_t data, struct pps_state *pps) +{ + pps_params_t *app; + struct pps_fetch_args *fapi; +#ifdef PPS_SYNC + struct pps_kcbind_args *kapi; +#endif + + switch (cmd) { + case PPS_IOC_CREATE: + return (0); + case PPS_IOC_DESTROY: + return (0); + case PPS_IOC_SETPARAMS: + app = (pps_params_t *)data; + if (app->mode & ~pps->ppscap) + return (EINVAL); + pps->ppsparam = *app; + return (0); + case PPS_IOC_GETPARAMS: + app = (pps_params_t *)data; + *app = pps->ppsparam; + app->api_version = PPS_API_VERS_1; + return (0); + case PPS_IOC_GETCAP: + *(int*)data = pps->ppscap; + return (0); + case PPS_IOC_FETCH: + fapi = (struct pps_fetch_args *)data; + if (fapi->tsformat && fapi->tsformat != PPS_TSFMT_TSPEC) + return (EINVAL); + if (fapi->timeout.tv_sec || fapi->timeout.tv_nsec) + return (EOPNOTSUPP); + pps->ppsinfo.current_mode = pps->ppsparam.mode; + fapi->pps_info_buf = pps->ppsinfo; + return (0); + case PPS_IOC_KCBIND: +#ifdef PPS_SYNC + kapi = (struct pps_kcbind_args *)data; + /* XXX Only root should be able to do this */ + if (kapi->tsformat && kapi->tsformat != PPS_TSFMT_TSPEC) + return (EINVAL); + if (kapi->kernel_consumer != PPS_KC_HARDPPS) + return (EINVAL); + if (kapi->edge & ~pps->ppscap) + return (EINVAL); + pps->kcmode = kapi->edge; + return (0); +#else + return (EOPNOTSUPP); +#endif + default: + return (ENOTTY); + } +} + +void +pps_init(struct pps_state *pps) +{ + pps->ppscap |= PPS_TSFMT_TSPEC; + if (pps->ppscap & PPS_CAPTUREASSERT) + pps->ppscap |= PPS_OFFSETASSERT; + if (pps->ppscap & PPS_CAPTURECLEAR) + pps->ppscap |= PPS_OFFSETCLEAR; +} + +void +pps_event(struct pps_state *pps, struct timecounter *tc, unsigned count, int event) +{ + struct timespec ts, *tsp, *osp; + u_int64_t delta; + unsigned tcount, *pcount; + int foff, fhard; + pps_seq_t *pseq; + + /* Things would be easier with arrays... */ + if (event == PPS_CAPTUREASSERT) { + tsp = &pps->ppsinfo.assert_timestamp; + osp = &pps->ppsparam.assert_offset; + foff = pps->ppsparam.mode & PPS_OFFSETASSERT; + fhard = pps->kcmode & PPS_CAPTUREASSERT; + pcount = &pps->ppscount[0]; + pseq = &pps->ppsinfo.assert_sequence; + } else { + tsp = &pps->ppsinfo.clear_timestamp; + osp = &pps->ppsparam.clear_offset; + foff = pps->ppsparam.mode & PPS_OFFSETCLEAR; + fhard = pps->kcmode & PPS_CAPTURECLEAR; + pcount = &pps->ppscount[1]; + pseq = &pps->ppsinfo.clear_sequence; + } + + /* The timecounter changed: bail */ + if (!pps->ppstc || + pps->ppstc->tc_name != tc->tc_name || + tc->tc_name != timecounter->tc_name) { + pps->ppstc = tc; + *pcount = count; + return; + } + + /* Nothing really happened */ + if (*pcount == count) + return; + + *pcount = count; + + /* Convert the count to timespec */ + ts.tv_sec = tc->tc_offset_sec; + tcount = count - tc->tc_offset_count; + tcount &= tc->tc_counter_mask; + delta = tc->tc_offset_nano; + delta += ((u_int64_t)tcount * tc->tc_scale_nano_f); + delta >>= 32; + delta += ((u_int64_t)tcount * tc->tc_scale_nano_i); + delta += boottime.tv_usec * 1000; + ts.tv_sec += boottime.tv_sec; + while (delta >= 1000000000) { + delta -= 1000000000; + ts.tv_sec++; + } + ts.tv_nsec = delta; + + (*pseq)++; + *tsp = ts; + + if (foff) { + timespecadd(tsp, osp); + if (tsp->tv_nsec < 0) { + tsp->tv_nsec += 1000000000; + tsp->tv_sec -= 1; + } + } +#ifdef PPS_SYNC + if (fhard) { + /* magic, at its best... */ + tcount = count - pps->ppscount[2]; + pps->ppscount[2] = count; + tcount &= tc->tc_counter_mask; + delta = ((u_int64_t)tcount * tc->tc_tweak->tc_scale_nano_f); + delta >>= 32; + delta += ((u_int64_t)tcount * tc->tc_tweak->tc_scale_nano_i); + hardpps(tsp, delta); + } +#endif +} |