/* 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;
};
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);
/*
* 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);
/* 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;
/* 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;
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];
/*
* 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;
/*
* 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;