diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/aux/timesync.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/aux/timesync.c')
-rwxr-xr-x | sys/src/cmd/aux/timesync.c | 1377 |
1 files changed, 1377 insertions, 0 deletions
diff --git a/sys/src/cmd/aux/timesync.c b/sys/src/cmd/aux/timesync.c new file mode 100755 index 000000000..1926b0486 --- /dev/null +++ b/sys/src/cmd/aux/timesync.c @@ -0,0 +1,1377 @@ +#include <u.h> +#include <libc.h> +#include <auth.h> +#include <ip.h> +#include <mp.h> + +/* nanosecond times */ +#define SEC 1000000000LL +#define MIN (60LL*SEC) +#define HOUR (60LL*MIN) +#define DAY (24LL*HOUR) + +enum { + Fs, + Rtc, + Ntp, + Utc, + Gps, + + HZAvgSecs= 3*60, /* target averaging period for frequency in seconds */ + MinSampleSecs= 60, /* minimum sampling time in seconds */ +}; + + +char *dir = "/tmp"; /* directory sample files live in */ +char *logfile = "timesync"; +char *timeserver; +char *Rootid; +int utcfil; +int gpsfil; +int debug; +int impotent; +int logging; +int type; +int gmtdelta; /* rtc+gmtdelta = gmt */ +uvlong avgerr; + +/* ntp server info */ +int stratum = 14; +vlong mydisp, rootdisp; +vlong mydelay, rootdelay; +vlong avgdelay; +vlong lastutc; +uchar rootid[4]; +char *sysid; +int myprec; + +/* list of time samples */ +typedef struct Sample Sample; +struct Sample +{ + Sample *next; + uvlong ticks; + vlong ltime; + vlong stime; +}; + +/* ntp packet */ +typedef struct NTPpkt NTPpkt; +struct NTPpkt +{ + uchar mode; + uchar stratum; + uchar poll; + uchar precision; + uchar rootdelay[4]; + uchar rootdisp[4]; + uchar rootid[4]; + uchar refts[8]; + uchar origts[8]; /* departed client */ + uchar recvts[8]; /* arrived at server */ + uchar xmitts[8]; /* departed server */ + uchar keyid[4]; + uchar digest[16]; +}; + +/* ntp server */ +typedef struct NTPserver NTPserver; +struct NTPserver +{ + NTPserver *next; + char *name; + uchar stratum; + uchar precision; + vlong rootdelay; + vlong rootdisp; + vlong rtt; + vlong dt; +}; + +NTPserver *ntpservers; + +enum +{ + NTPSIZE= 48, /* basic ntp packet */ + NTPDIGESTSIZE= 20, /* key and digest */ +}; + +/* error bound of last sample */ +ulong ε; + +static void addntpserver(char *name); +static int adjustperiod(vlong diff, vlong accuracy, int secs); +static void background(void); +static int caperror(vlong dhz, int tsecs, vlong taccuracy); +static long fstime(void); +static int gettime(vlong *nsec, uvlong *ticks, uvlong *hz); /* returns time, ticks, hz */ +static int getclockprecision(vlong); +static vlong gpssample(void); +static void hnputts(void *p, vlong nsec); +static void hnputts(void *p, vlong nsec); +static void inittime(void); +static vlong nhgetts(void *p); +static vlong nhgetts(void *p); +static void ntpserver(char*); +static vlong ntpsample(void); +static int ntptimediff(NTPserver *ns); +static int openfreqfile(void); +static vlong readfreqfile(int fd, vlong ohz, vlong minhz, vlong maxhz); +static long rtctime(void); +static vlong sample(long (*get)(void)); +static void setpriority(void); +static void setrootid(char *d); +static void settime(vlong now, uvlong hz, vlong delta, int n); /* set time, hz, delta, period */ +static vlong utcsample(void); +static uvlong vabs(vlong); +static uvlong whatisthefrequencykenneth(uvlong hz, uvlong minhz, uvlong maxhz, + vlong dt, vlong ticks, vlong period); +static void writefreqfile(int fd, vlong hz, int secs, vlong diff); + +// ((1970-1900)*365 + 17 /*leap days*/)*24*60*60 +#define EPOCHDIFF 2208988800UL + +static void +usage(void) +{ + fprint(2, "usage: %s [-a accuracy][-d dir][-I rootid][-s net]" + "[-S stratum][-DfGilLnrU] timesource ...\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int i, t, fd, nservenet; + int secs; /* sampling period */ + int tsecs; /* temporary sampling period */ + uvlong hz, minhz, maxhz, period, nhz; + vlong diff, accuracy, taccuracy; + char *servenet[4]; + Sample *s, *x, *first, **l; + Tm tl, tg; + + type = Fs; /* by default, sync with the file system */ + debug = 0; + accuracy = 1000000LL; /* default accuracy is 1 millisecond */ + nservenet = 0; + tsecs = secs = MinSampleSecs; + timeserver = ""; + + ARGBEGIN{ + case 'a': + accuracy = strtoll(EARGF(usage()), 0, 0); /* specified in ns */ + if(accuracy <= 1) + sysfatal("bad accuracy specified"); + break; + case 'd': + dir = EARGF(usage()); + break; + case 'D': + debug = 1; + break; + case 'f': + type = Fs; + stratum = 2; + break; + case 'G': + type = Gps; + stratum = 1; + break; + case 'i': + impotent = 1; + break; + case 'I': + Rootid = EARGF(usage()); + break; + case 'l': + logging = 1; + break; + case 'L': + /* + * Assume time source in local time rather than GMT. + * Calculate difference so that rtctime can return GMT. + * This is useful with the rtc on PC's that run Windows + * since Windows keeps the local time in the rtc. + */ + t = time(0); + tl = *localtime(t); + tg = *gmtime(t); + + /* + * if the years are different, we're at most a day off, + * so just rewrite + */ + if(tl.year < tg.year){ + tg.year--; + tg.yday = tl.yday + 1; + }else if(tl.year > tg.year){ + tl.year--; + tl.yday = tg.yday+1; + } + assert(tl.year == tg.year); + + tg.sec -= tl.sec; + tg.min -= tl.min; + tg.hour -= tl.hour; + tg.yday -= tl.yday; + gmtdelta = tg.sec+60*(tg.min+60*(tg.hour+tg.yday*24)); + + assert(abs(gmtdelta) <= 24*60*60); + break; + case 'n': + type = Ntp; + break; + case 'r': + type = Rtc; + stratum = 0; + break; + case 'U': + type = Utc; + stratum = 1; + break; + case 's': + if(nservenet >= nelem(servenet)) + sysfatal("too many networks to serve on"); + servenet[nservenet++] = EARGF(usage()); + break; + case 'S': + stratum = strtoll(EARGF(usage()), 0, 0); + break; + default: + usage(); + }ARGEND; + + fmtinstall('E', eipfmt); + fmtinstall('I', eipfmt); + fmtinstall('V', eipfmt); + sysid = getenv("sysname"); + + /* detach from the current namespace */ + if(debug) + rfork(RFNAMEG); + + switch(type){ + case Utc: + if(argc > 0) + timeserver = argv[0]; + else + sysfatal("bad time source"); + break; + case Gps: + if(argc > 0) + timeserver = argv[0]; + else + timeserver = "/mnt/gps/time"; + break; + case Fs: + if(argc > 0) + timeserver = argv[0]; + else + timeserver = "/srv/boot"; + break; + case Ntp: + if(argc > 0) + for(i = 0; i < argc; i++) + addntpserver(argv[i]); + else + addntpserver("$ntp"); + break; + } + + setpriority(); + + /* figure out our time interface and initial frequency */ + inittime(); + gettime(0, 0, &hz); + minhz = hz/10; + maxhz = hz*10; + myprec = getclockprecision(hz); + + /* convert the accuracy from nanoseconds to ticks */ + taccuracy = hz*accuracy/SEC; + + /* + * bind in clocks + */ + switch(type){ + case Fs: + fd = open(timeserver, ORDWR); + if(fd < 0) + sysfatal("opening %s: %r", timeserver); + if(amount(fd, "/n/boot", MREPL, "") < 0) + sysfatal("mounting %s: %r", timeserver); + close(fd); + break; + case Rtc: + bind("#r", "/dev", MAFTER); + if(access("/dev/rtc", AREAD) < 0) + sysfatal("accessing /dev/rtc: %r"); + break; + case Utc: + fd = open(timeserver, OREAD); + if(fd < 0) + sysfatal("opening %s: %r", timeserver); + utcfil = fd; + break; + case Gps: + fd = open(timeserver, OREAD); + if(fd < 0) + sysfatal("opening %s: %r", timeserver); + gpsfil = fd; + break; + } + + /* + * start a local ntp server(s) + */ + for(i = 0; i < nservenet; i++) + switch(rfork(RFPROC|RFFDG|RFMEM|RFNOWAIT)){ + case -1: + sysfatal("forking: %r"); + case 0: + ntpserver(servenet[i]); + _exits(0); + } + + /* get the last known frequency from the file */ + fd = openfreqfile(); + hz = readfreqfile(fd, hz, minhz, maxhz); + + /* + * this is the main loop. it gets a sample, adjusts the + * clock and computes a sleep period until the next loop. + * we balance frequency drift against the length of the + * period to avoid blowing the accuracy limit. + */ + first = nil; + l = &first; + avgerr = accuracy >> 1; + for(;; background(), sleep(tsecs*1000)){ + s = mallocz(sizeof *s, 1); + diff = 0; + + /* get times for this sample */ + ε = ~0; + switch(type){ + case Fs: + s->stime = sample(fstime); + break; + case Rtc: + s->stime = sample(rtctime); + break; + case Utc: + s->stime = utcsample(); + if(s->stime == 0LL){ + if(logging) + syslog(0, logfile, "no sample"); + free(s); + if (secs > 60 * 15) + tsecs = 60*15; + continue; + } + break; + case Ntp: + diff = ntpsample(); + if(diff == 0LL){ + if(logging) + syslog(0, logfile, "no sample"); + free(s); + if(secs > 60*15) + tsecs = 60*15; + continue; + } + break; + case Gps: + diff = gpssample(); + if(diff == 0LL){ + if(logging) + syslog(0, logfile, "no sample"); + free(s); + if(secs > 60*15) + tsecs = 60*15; + continue; + } + } + + /* use fastest method to read local clock and ticks */ + gettime(&s->ltime, &s->ticks, 0); + if(type == Ntp || type == Gps) + s->stime = s->ltime + diff; + + /* if the sample was bad, ignore it */ + if(s->stime < 0){ + free(s); + continue; + } + + /* reset local time */ + diff = s->stime - s->ltime; + if(diff > 10*SEC || diff < -10*SEC){ + /* we're way off, just set the time */ + secs = MinSampleSecs; + settime(s->stime, 0, 0, 0); + } else { + /* keep a running average of the error. */ + avgerr = (avgerr>>1) + (vabs(diff)>>1); + + /* + * the time to next sample depends on how good or + * bad we're doing. + */ + tsecs = secs = adjustperiod(diff, accuracy, secs); + + /* + * work off the fixed difference. This is done + * by adding a ramp to the clock. Each 100th of a + * second (or so) the kernel will add diff/(4*secs*100) + * to the clock. we only do 1/4 of the difference per + * period to dampen any measurement noise. + * + * any difference greater than ε we work off during the + * sampling period. + */ + if(abs(diff) > ε) + if(diff > 0) + settime(-1, 0, diff-((3*ε)/4), secs); + else + settime(-1, 0, diff+((3*ε)/4), secs); + else + settime(-1, 0, diff, 4*secs); + + } + if(debug) + fprint(2, "δ %lld avgδ %lld f %lld\n", diff, avgerr, hz); + + /* dump old samples (keep at least one) */ + while(first != nil){ + if(first->next == nil) + break; + if(s->stime - first->next->stime < DAY) + break; + x = first; + first = first->next; + free(x); + } + + /* + * The sampling error is limited by the total error. If + * we make sure the sampling period is at least 16 million + * times the average error, we should calculate a frequency + * with on average a 1e-7 error. + * + * So that big hz changes don't blow our accuracy requirement, + * we shorten the period to make sure that δhz*secs will be + * greater than the accuracy limit. + */ + period = avgerr << 24; + for(x = first; x != nil; x = x->next) + if(s->stime - x->stime < period || + x->next == nil || s->stime - x->next->stime < period) + break; + if(x != nil){ + nhz = whatisthefrequencykenneth( + hz, minhz, maxhz, + s->stime - x->stime, + s->ticks - x->ticks, + period); + tsecs = caperror(vabs(nhz-hz), tsecs, taccuracy); + hz = nhz; + writefreqfile(fd, hz, (s->stime - x->stime)/SEC, diff); + } + + /* add current sample to list. */ + *l = s; + l = &s->next; + + if(logging) + syslog(0, logfile, "δ %lld avgδ %lld hz %lld", + diff, avgerr, hz); + } +} + +/* + * adjust the sampling period with some histeresis + */ +static int +adjustperiod(vlong diff, vlong accuracy, int secs) +{ + uvlong absdiff; + + absdiff = vabs(diff); + + if(absdiff < (accuracy>>1)) + secs += 60; + else if(absdiff > accuracy) + secs >>= 1; + else + secs -= 60; + if(secs < MinSampleSecs) + secs = MinSampleSecs; + return secs; +} + +/* + * adjust the frequency + */ +static uvlong +whatisthefrequencykenneth(uvlong hz, uvlong minhz, uvlong maxhz, vlong dt, + vlong ticks, vlong period) +{ + uvlong ohz = hz; + static mpint *mpdt, *mpticks, *mphz, *mpbillion; + + /* sanity check */ + if(dt <= 0 || ticks <= 0) + return hz; + + if(mphz == nil){ + mphz = mpnew(0); + mpbillion = uvtomp(SEC, nil); + } + + /* hz = (ticks*SEC)/dt */ + mpdt = vtomp(dt, mpdt); + mpticks = vtomp(ticks, mpticks); + mpmul(mpticks, mpbillion, mpticks); + mpdiv(mpticks, mpdt, mphz, nil); + hz = mptoui(mphz); + + /* sanity */ + if(hz < minhz || hz > maxhz) + return ohz; + + /* damp the change if we're shorter than the target period */ + if(period > dt) + hz = (12ULL*ohz + 4ULL*hz)/16ULL; + + settime(-1, hz, 0, 0); + return hz; +} + +/* + * We may be changing the frequency to match a bad measurement + * or to match a condition no longer in effect. To make sure + * that this doesn't blow our error budget over the next measurement + * period, shorten the period to make sure that δhz*secs will be + * less than the accuracy limit. Here taccuracy is accuracy converted + * from nanoseconds to ticks. + */ +static int +caperror(vlong dhz, int tsecs, vlong taccuracy) +{ + if(dhz*tsecs <= taccuracy) + return tsecs; + + if(debug) + fprint(2, "δhz %lld tsecs %d tacc %lld\n", dhz, tsecs, taccuracy); + + tsecs = taccuracy/dhz; + if(tsecs < MinSampleSecs) + tsecs = MinSampleSecs; + return tsecs; +} + +/* + * kernel interface + */ +enum +{ + Ibintime, + Insec, + Itiming, +}; +int ifc; +int bintimefd = -1; +int timingfd = -1; +int nsecfd = -1; +int fastclockfd = -1; + +static void +inittime(void) +{ + int mode; + + if(impotent) + mode = OREAD; + else + mode = ORDWR; + + /* bind in clocks */ + if(access("/dev/time", 0) < 0) + bind("#c", "/dev", MAFTER); + if(access("/dev/rtc", 0) < 0) + bind("#r", "/dev", MAFTER); + + /* figure out what interface we have */ + ifc = Ibintime; + bintimefd = open("/dev/bintime", mode); + if(bintimefd >= 0) + return; + ifc = Insec; + nsecfd = open("/dev/nsec", mode); + if(nsecfd < 0) + sysfatal("opening /dev/nsec"); + fastclockfd = open("/dev/fastclock", mode); + if(fastclockfd < 0) + sysfatal("opening /dev/fastclock"); + timingfd = open("/dev/timing", OREAD); + if(timingfd < 0) + return; + ifc = Itiming; +} + +/* + * convert binary numbers from/to kernel + */ +static uvlong uvorder = 0x0001020304050607ULL; + +static uchar* +be2vlong(vlong *to, uchar *f) +{ + uchar *t, *o; + int i; + + t = (uchar*)to; + o = (uchar*)&uvorder; + for(i = 0; i < sizeof(vlong); i++) + t[o[i]] = f[i]; + return f+sizeof(vlong); +} + +static uchar* +vlong2be(uchar *t, vlong from) +{ + uchar *f, *o; + int i; + + f = (uchar*)&from; + o = (uchar*)&uvorder; + for(i = 0; i < sizeof(vlong); i++) + t[i] = f[o[i]]; + return t+sizeof(vlong); +} + +static long order = 0x00010203; + +static uchar* +be2long(long *to, uchar *f) +{ + uchar *t, *o; + int i; + + t = (uchar*)to; + o = (uchar*)ℴ + for(i = 0; i < sizeof(long); i++) + t[o[i]] = f[i]; + return f+sizeof(long); +} + +static uchar* +long2be(uchar *t, long from) +{ + uchar *f, *o; + int i; + + f = (uchar*)&from; + o = (uchar*)ℴ + for(i = 0; i < sizeof(long); i++) + t[i] = f[o[i]]; + return t+sizeof(long); +} + +/* + * read ticks and local time in nanoseconds + */ +static int +gettime(vlong *nsec, uvlong *ticks, uvlong *hz) +{ + int i, n; + uchar ub[3*8], *p; + char b[2*24+1]; + + switch(ifc){ + case Ibintime: + n = sizeof(vlong); + if(hz != nil) + n = 3*sizeof(vlong); + if(ticks != nil) + n = 2*sizeof(vlong); + i = read(bintimefd, ub, n); + if(i != n) + break; + p = ub; + if(nsec != nil) + be2vlong(nsec, ub); + p += sizeof(vlong); + if(ticks != nil) + be2vlong((vlong*)ticks, p); + p += sizeof(vlong); + if(hz != nil) + be2vlong((vlong*)hz, p); + return 0; + case Itiming: + n = sizeof(vlong); + if(ticks != nil) + n = 2*sizeof(vlong); + i = read(timingfd, ub, n); + if(i != n) + break; + p = ub; + if(nsec != nil) + be2vlong(nsec, ub); + p += sizeof(vlong); + if(ticks != nil) + be2vlong((vlong*)ticks, p); + if(hz != nil){ + seek(fastclockfd, 0, 0); + n = read(fastclockfd, b, sizeof(b)-1); + if(n <= 0) + break; + b[n] = 0; + *hz = strtoll(b+24, 0, 0); + } + return 0; + case Insec: + if(nsec != nil){ + seek(nsecfd, 0, 0); + n = read(nsecfd, b, sizeof(b)-1); + if(n <= 0) + break; + b[n] = 0; + *nsec = strtoll(b, 0, 0); + } + if(ticks != nil){ + seek(fastclockfd, 0, 0); + n = read(fastclockfd, b, sizeof(b)-1); + if(n <= 0) + break; + b[n] = 0; + *ticks = strtoll(b, 0, 0); + } + if(hz != nil){ + seek(fastclockfd, 0, 0); + n = read(fastclockfd, b, sizeof(b)-1); + if(n <= 24) + break; + b[n] = 0; + *hz = strtoll(b+24, 0, 0); + } + return 0; + } + return -1; +} + +static void +settime(vlong now, uvlong hz, vlong delta, int n) +{ + uchar b[1+sizeof(vlong)+sizeof(long)], *p; + + if(debug) + fprint(2, "settime(now=%lld, hz=%llud, delta=%lld, period=%d)\n", + now, hz, delta, n); + if(impotent) + return; + switch(ifc){ + case Ibintime: + if(now >= 0){ + p = b; + *p++ = 'n'; + p = vlong2be(p, now); + if(write(bintimefd, b, p-b) < 0) + sysfatal("writing /dev/bintime: %r"); + } + if(delta != 0){ + p = b; + *p++ = 'd'; + p = vlong2be(p, delta); + p = long2be(p, n); + if(write(bintimefd, b, p-b) < 0) + sysfatal("writing /dev/bintime: %r"); + } + if(hz != 0){ + p = b; + *p++ = 'f'; + p = vlong2be(p, hz); + if(write(bintimefd, b, p-b) < 0) + sysfatal("writing /dev/bintime: %r"); + } + break; + case Itiming: + case Insec: + seek(nsecfd, 0, 0); + if(now >= 0 || delta != 0){ + if(fprint(nsecfd, "%lld %lld %d", now, delta, n) < 0) + sysfatal("writing /dev/nsec: %r"); + } + if(hz > 0){ + seek(fastclockfd, 0, 0); + if(fprint(fastclockfd, "%lld", hz) < 0) + sysfatal("writing /dev/fastclock: %r"); + } + } +} + +/* + * set priority high and wire process to a processor + */ +static void +setpriority(void) +{ + int fd; + char buf[32]; + + sprint(buf, "/proc/%d/ctl", getpid()); + fd = open(buf, ORDWR); + if(fd < 0){ + fprint(2, "can't set priority\n"); + return; + } + if(fprint(fd, "pri 100") < 0) + fprint(2, "can't set priority\n"); + if(fprint(fd, "wired 2") < 0) + fprint(2, "can't wire process\n"); + close(fd); +} + +/* convert to ntp timestamps */ +static void +hnputts(void *p, vlong nsec) +{ + uchar *a; + ulong tsh, tsl; + + a = p; + + /* zero is a special case */ + if(nsec == 0) + return; + + tsh = nsec/SEC; + nsec -= tsh*SEC; + tsl = (nsec<<32)/SEC; + hnputl(a, tsh+EPOCHDIFF); + hnputl(a+4, tsl); +} + +/* convert from ntp timestamps */ +static vlong +nhgetts(void *p) +{ + uchar *a; + ulong tsh, tsl; + vlong nsec; + + a = p; + tsh = nhgetl(a); + tsl = nhgetl(a+4); + nsec = tsl*SEC; + nsec >>= 32; + nsec += (tsh - EPOCHDIFF)*SEC; + return nsec; +} + +/* convert to ntp 32 bit fixed point */ +static void +hnputfp(void *p, vlong nsec) +{ + uchar *a; + ulong fp; + + a = p; + + fp = nsec/(SEC/((vlong)(1<<16))); + hnputl(a, fp); +} + +/* convert from ntp fixed point to nanosecs */ +static vlong +nhgetfp(void *p) +{ + uchar *a; + ulong fp; + vlong nsec; + + a = p; + fp = nhgetl(a); + nsec = ((vlong)fp)*(SEC/((vlong)(1<<16))); + return nsec; +} + +/* get network address of the server */ +static void +setrootid(char *d) +{ + char buf[128]; + int fd, n; + char *p; + + snprint(buf, sizeof buf, "%s/remote", d); + fd = open(buf, OREAD); + if(fd < 0) + return; + n = read(fd, buf, sizeof buf); + close(fd); + if(n <= 0) + return; + p = strchr(buf, '!'); + if(p != nil) + *p = 0; + v4parseip(rootid, buf); +} + +static void +ding(void*, char *s) +{ + if(strstr(s, "alarm") != nil) + noted(NCONT); + noted(NDFLT); +} + +static void +addntpserver(char *name) +{ + NTPserver *ns, **l; + + ns = mallocz(sizeof(NTPserver), 1); + if(ns == nil) + sysfatal("addntpserver: %r"); + timeserver = strdup(name); + ns->name = name; + for(l = &ntpservers; *l != nil; l = &(*l)->next) + ; + *l = ns; +} + +/* + * sntp client, we keep calling if the delay seems + * unusually high, i.e., 30% longer than avg. + */ +static int +ntptimediff(NTPserver *ns) +{ + int fd, tries, n; + NTPpkt ntpin, ntpout; + vlong dt, recvts, origts, xmitts, destts, x; + char dir[64]; + static int whined; + + notify(ding); + alarm(30*1000); /* don't wait forever if ns->name is unreachable */ + fd = dial(netmkaddr(ns->name, "udp", "ntp"), 0, dir, 0); + if(fd < 0){ + if (!whined++) + syslog(0, logfile, "can't reach %s: %r", ns->name); + return -1; + } + setrootid(dir); + + memset(&ntpout, 0, sizeof(ntpout)); + ntpout.mode = 3 | (3 << 3); + + for(tries = 0; tries < 3; tries++){ + alarm(2*1000); + + gettime(&x, 0, 0); + hnputts(ntpout.xmitts, x); + if(write(fd, &ntpout, NTPSIZE) < 0){ + alarm(0); + continue; + } + + n = read(fd, &ntpin, sizeof ntpin); + alarm(0); + gettime(&destts, 0, 0); + if(n >= NTPSIZE){ + close(fd); + + /* we got one, use it */ + recvts = nhgetts(ntpin.recvts); + origts = nhgetts(ntpin.origts); + xmitts = nhgetts(ntpin.xmitts); + dt = ((recvts - origts) + (xmitts - destts))/2; + + /* save results */ + ns->rtt = ((destts - origts) - (xmitts - recvts))/2; + ns->dt = dt; + ns->stratum = ntpin.stratum; + ns->precision = ntpin.precision; + ns->rootdelay = nhgetfp(ntpin.rootdelay); + ns->rootdisp = nhgetfp(ntpin.rootdisp); + + if(debug) + fprint(2, "ntp %s stratum %d ntpdelay(%lld)\n", + ns->name, ntpin.stratum, ns->rtt); + return 0; + } + + /* try again */ + sleep(250); + } + close(fd); + return -1; +} + +static vlong +gpssample(void) +{ + vlong l, g, d; + int i, n; + char *v[4], buf[128]; + + d = -1000000000000000000LL; + for(i = 0; i < 5; i++){ + sleep(1100); + seek(gpsfil, 0, 0); + n = read(gpsfil, buf, sizeof buf - 1); + if (n <= 0) + return 0; + buf[n] = 0; + n = tokenize(buf, v, nelem(v)); + if(n != 4 || strcmp(v[3], "A") != 0) + return 0; + g = atoll(v[1]); + l = atoll(v[2]); + if(g-l > d) + d = g-l; + } + return d; +} + +static vlong +ntpsample(void) +{ + NTPserver *tns, *ns; + vlong metric, x; + + metric = 1000LL*SEC; + ns = nil; + for(tns = ntpservers; tns != nil; tns = tns->next){ + if(ntptimediff(tns) < 0) + continue; + x = vabs(tns->rootdisp) + (vabs(tns->rtt+tns->rootdelay)>>1); + if(debug) + fprint(2, "ntp %s rootdelay %lld rootdisp %lld metric %lld\n", + tns->name, tns->rootdelay, tns->rootdisp, x); + if(x < metric){ + metric = x; + ns = tns; + } + } + + if(ns == nil) + return 0; + + /* save data for our server */ + rootdisp = ns->rootdisp; + rootdelay = ns->rootdelay; + mydelay = ns->rtt; + mydisp = avgerr; + if(ns->stratum == 0) + stratum = 0; + else + stratum = ns->stratum + 1; + + ε = abs(ns->rtt/2); + return ns->dt; +} + +/* + * sample the utc file + */ +static vlong +utcsample(void) +{ + vlong s; + int n; + char *v[2], buf[128]; + + s = 0; + seek(utcfil, 0, 0); + n = read(utcfil, buf, sizeof buf - 1); + if (n <= 0) + return 0; + buf[n] = 0; + n = tokenize(buf, v, nelem(v)); + if (strcmp(v[0], "0") == 0) + return 0; + if (n == 2) { + gettime(&s, nil, nil); + s -= atoll(v[1]); + } + lastutc = atoll(v[0]) + s; + return lastutc; +} + +/* + * sntp server + */ +static int +openlisten(char *net) +{ + int fd, cfd; + char data[128], devdir[40]; + + sprint(data, "%s/udp!*!ntp", net); + cfd = announce(data, devdir); + if(cfd < 0) + sysfatal("can't announce"); + if(fprint(cfd, "headers") < 0) + sysfatal("can't set header mode"); + + sprint(data, "%s/data", devdir); + fd = open(data, ORDWR); + if(fd < 0) + sysfatal("open %s: %r", data); + return fd; +} + +static void +ntpserver(char *servenet) +{ + int fd, n, vers, mode; + vlong recvts, x; + char buf[512]; + NTPpkt *ntp; + + fd = openlisten(servenet); + + if (Rootid == nil) + switch(type){ + case Fs: + Rootid = "WWV"; + break; + case Rtc: + Rootid = "LOCL"; + break; + case Utc: + Rootid = "UTC"; + break; + case Gps: + Rootid = "GPS"; + break; + case Ntp: + /* set by the ntp client */ + break; + } + if (Rootid != nil) + memmove(rootid, Rootid, strlen(Rootid) > 4? 4: strlen(Rootid)); + + for(;;){ + n = read(fd, buf, sizeof buf); + gettime(&recvts, 0, 0); + if(n <= 0) { + /* don't croak on input error, but don't spin either */ + sleep(500); + continue; + } + if(n < Udphdrsize + NTPSIZE) + continue; + + ntp = (NTPpkt*)(buf + Udphdrsize); + mode = ntp->mode & 7; + vers = (ntp->mode>>3) & 7; + if(mode != 3) + continue; + + ntp->mode = (vers<<3)|4; + ntp->stratum = stratum; + ntp->precision = myprec; + hnputfp(ntp->rootdelay, rootdelay + mydelay); + hnputfp(ntp->rootdisp, rootdisp + mydisp); + hnputts(ntp->refts, lastutc); + memmove(ntp->origts, ntp->xmitts, sizeof(ntp->origts)); + hnputts(ntp->recvts, recvts); + memmove(ntp->rootid, rootid, sizeof(ntp->rootid)); + gettime(&x, 0, 0); + hnputts(ntp->xmitts, x); + write(fd, buf, NTPSIZE + Udphdrsize); + } +} + +/* + * get the current time from the file system + */ +static long +fstime(void) +{ + Dir *d; + ulong t; + + d = dirstat("/n/boot"); + if(d != nil){ + t = d->atime; + free(d); + } else + t = 0; + return t; +} + +/* + * get the current time from the real time clock + */ +static long +rtctime(void) +{ + char b[20]; + static int f = -1; + int i, retries; + + memset(b, 0, sizeof(b)); + for(retries = 0; retries < 100; retries++){ + if(f < 0) + f = open("/dev/rtc", OREAD|OCEXEC); + if(f < 0) + break; + if(seek(f, 0, 0) < 0 || (i = read(f, b, sizeof b)) < 0){ + close(f); + f = -1; + } else + if(i != 0) + break; + } + return strtoul(b, 0, 10)+gmtdelta; +} + + +/* + * Sample a clock. We wait for the clock to always + * be at the leading edge of a clock period. + */ +static vlong +sample(long (*get)(void)) +{ + long this, last; + vlong start, end; + + /* + * wait for the second to change + */ + last = (*get)(); + for(;;){ + gettime(&start, 0, 0); + this = (*get)(); + gettime(&end, 0, 0); + if(this != last) + break; + last = this; + } + return SEC*this - (end-start)/2; +} + +/* + * the name of the frequency file has the method and possibly the + * server name encoded in it. + */ +static int +openfreqfile(void) +{ + char *p; + int fd; + + if(sysid == nil) + return -1; + + switch(type){ + case Ntp: + p = smprint("%s/ts.%s.%d.%s", dir, sysid, type, timeserver); + break; + default: + p = smprint("%s/ts.%s.%d", dir, sysid, type); + break; + } + fd = open(p, ORDWR); + if(fd < 0) + fd = create(p, ORDWR, 0666); + free(p); + if(fd < 0) + return -1; + return fd; +} + +/* + * the file contains the last known frequency and the + * number of seconds it was sampled over + */ +static vlong +readfreqfile(int fd, vlong ohz, vlong minhz, vlong maxhz) +{ + int n; + char buf[128]; + vlong hz; + + n = read(fd, buf, sizeof buf-1); + if(n <= 0) + return ohz; + buf[n] = 0; + hz = strtoll(buf, nil, 0); + + if(hz > maxhz || hz < minhz) + return ohz; + + settime(-1, hz, 0, 0); + return hz; +} + +/* + * remember hz and averaging period + */ +static void +writefreqfile(int fd, vlong hz, int secs, vlong diff) +{ + long now; + static long last; + + if(fd < 0) + return; + now = time(0); + if(now - last < 10*60) + return; + last = now; + if(seek(fd, 0, 0) < 0) + return; + fprint(fd, "%lld %d %d %lld\n", hz, secs, type, diff); +} + +static uvlong +vabs(vlong x) +{ + if(x < 0) + return -x; + else + return x; +} + +static void +background(void) +{ + static int inbackground; + + if(inbackground) + return; + + if(!debug) + switch(rfork(RFPROC|RFFDG|RFNAMEG|RFNOTEG|RFNOWAIT)){ + case -1: + sysfatal("forking: %r"); + break; + case 0: + break; + default: + exits(0); + } + inbackground = 1; +} + +static int +getclockprecision(vlong hz) +{ + int i; + + i = 8; + while(hz > 0){ + i--; + hz >>= 1; + } + return i; +} |