ref: fe1eb39db7ae6904924f3ab1f6f9b34416f2eb1b
dir: /sys/src/cmd/aux/timesync.c/
#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 void setrtctime(long); 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 */ int syncrtc; 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; syncrtc = 1; 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; syncrtc = 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 / 2; maxhz = hz * 2; 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, "") == -1) sysfatal("mounting %s: %r", timeserver); close(fd); break; case Rtc: 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(syncrtc) setrtctime(s->stime / SEC); 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* 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, OWRITE); 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; } static void setrtctime(long t) { static int f = -1; if(f < 0) f = open("/dev/rtc", OWRITE|OCEXEC); if(f < 0) return; if(seek(f, 0, 0) < 0 || fprint(f, "%ld", t-gmtdelta) < 0){ close(f); f = -1; } } /* * 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); sleep(5); 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; }