1 /*        $NetBSD: chutest.c,v 1.6 2020/05/25 20:47:19 christos Exp $ */
2 
3 /* chutest.c,v 3.1 1993/07/06 01:05:21 jbj Exp
4  * chutest - test the CHU clock
5  */
6 
7 #ifdef HAVE_CONFIG_H
8 # include <config.h>
9 #endif
10 #include <stdio.h>
11 #include <fcntl.h>
12 #ifdef HAVE_UNISTD_H
13 # include <unistd.h>
14 #endif
15 #ifdef HAVE_STROPTS_H
16 # include <stropts.h>
17 #else
18 # ifdef HAVE_SYS_STROPTS_H
19 #  include <sys/stropts.h>
20 # endif
21 #endif
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <sys/ioctl.h>
26 #include <sys/time.h>
27 #include <sys/file.h>
28 #ifdef HAVE_TERMIOS_H
29 # include <termios.h>
30 #else
31 # ifdef HAVE_SGTTY_H
32 #  include <sgtty.h>
33 # endif
34 #endif
35 
36 #include "ntp_fp.h"
37 #include "ntp.h"
38 #include "ntp_unixtime.h"
39 #include "ntp_calendar.h"
40 
41 #ifdef CHULDISC
42 # ifdef HAVE_SYS_CHUDEFS_H
43 #  include <sys/chudefs.h>
44 # endif
45 #endif
46 
47 
48 #ifndef CHULDISC
49 #define   NCHUCHARS (10)
50 
51 struct chucode {
52           u_char codechars[NCHUCHARS];  /* code characters */
53           u_char ncodechars;            /* number of code characters */
54           u_char chustatus;             /* not used currently */
55           struct timeval codetimes[NCHUCHARS];    /* arrival times */
56 };
57 #endif
58 
59 #define   STREQ(a, b)         (*(a) == *(b) && strcmp((a), (b)) == 0)
60 
61 char const *progname;
62 
63 int dofilter = 0;   /* set to 1 when we should run filter algorithm */
64 int showtimes = 0;  /* set to 1 when we should show char arrival times */
65 int doprocess = 0;  /* set to 1 when we do processing analogous to driver */
66 #ifdef CHULDISC
67 int usechuldisc = 0;          /* set to 1 when CHU line discipline should be used */
68 #endif
69 #ifdef STREAM
70 int usechuldisc = 0;          /* set to 1 when CHU line discipline should be used */
71 #endif
72 
73 struct timeval lasttv;
74 struct chucode chudata;
75 
76 void      error(char *fmt, char *s1, char *s2);
77 void      init_chu(void);
78 int       openterm(char *dev);
79 int       process_raw(int s);
80 int       process_ldisc(int s);
81 void      raw_filter(unsigned int c, struct timeval *tv);
82 void      chufilter(struct chucode *chuc,         l_fp *rtime);
83 
84 
85 /*
86  * main - parse arguments and handle options
87  */
88 int
main(int argc,char * argv[])89 main(
90           int argc,
91           char *argv[]
92           )
93 {
94           int c;
95           int errflg = 0;
96           extern int ntp_optind;
97 
98           progname = argv[0];
99           while ((c = ntp_getopt(argc, argv, "cdfpt")) != EOF)
100               switch (c) {
101                     case 'c':
102 #ifdef STREAM
103                         usechuldisc = 1;
104                         break;
105 #endif
106 #ifdef CHULDISC
107                         usechuldisc = 1;
108                         break;
109 #endif
110 #ifndef STREAM
111 #ifndef CHULDISC
112                         (void) fprintf(stderr,
113                                            "%s: CHU line discipline not available on this machine\n",
114                                            progname);
115                         exit(2);
116 #endif
117 #endif
118                     case 'd':
119                         ++debug;
120                         break;
121                     case 'f':
122                         dofilter = 1;
123                         break;
124                     case 'p':
125                         doprocess = 1;
126                     case 't':
127                         showtimes = 1;
128                         break;
129                     default:
130                         errflg++;
131                         break;
132               }
133           if (errflg || ntp_optind+1 != argc) {
134 #ifdef STREAM
135                     (void) fprintf(stderr, "usage: %s [-dft] tty_device\n",
136                                      progname);
137 #endif
138 #ifdef CHULDISC
139                     (void) fprintf(stderr, "usage: %s [-dft] tty_device\n",
140                                      progname);
141 #endif
142 #ifndef STREAM
143 #ifndef CHULDISC
144                     (void) fprintf(stderr, "usage: %s [-cdft] tty_device\n",
145                                      progname);
146 #endif
147 #endif
148                     exit(2);
149           }
150 
151           (void) gettimeofday(&lasttv, (struct timezone *)0);
152           c = openterm(argv[ntp_optind]);
153           init_chu();
154 #ifdef STREAM
155           if (usechuldisc)
156               process_ldisc(c);
157           else
158 #endif
159 #ifdef CHULDISC
160               if (usechuldisc)
161                     process_ldisc(c);
162               else
163 #endif
164                     process_raw(c);
165           /*NOTREACHED*/
166 }
167 
168 
169 /*
170  * openterm - open a port to the CHU clock
171  */
172 int
openterm(char * dev)173 openterm(
174           char *dev
175           )
176 {
177           int s;
178           struct sgttyb ttyb;
179 
180           if (debug)
181               (void) fprintf(stderr, "Doing open...");
182           if ((s = open(dev, O_RDONLY, 0777)) < 0)
183               error("open(%s)", dev, "");
184           if (debug)
185               (void) fprintf(stderr, "open okay\n");
186 
187           if (debug)
188               (void) fprintf(stderr, "Setting exclusive use...");
189           if (ioctl(s, TIOCEXCL, (char *)0) < 0)
190               error("ioctl(TIOCEXCL)", "", "");
191           if (debug)
192               (void) fprintf(stderr, "done\n");
193 
194           ttyb.sg_ispeed = ttyb.sg_ospeed = B300;
195           ttyb.sg_erase = ttyb.sg_kill = 0;
196           ttyb.sg_flags = EVENP|ODDP|RAW;
197           if (debug)
198               (void) fprintf(stderr, "Setting baud rate et al...");
199           if (ioctl(s, TIOCSETP, (char *)&ttyb) < 0)
200               error("ioctl(TIOCSETP, raw)", "", "");
201           if (debug)
202               (void) fprintf(stderr, "done\n");
203 
204 #ifdef CHULDISC
205           if (usechuldisc) {
206                     int ldisc;
207 
208                     if (debug)
209                         (void) fprintf(stderr, "Switching to CHU ldisc...");
210                     ldisc = CHULDISC;
211                     if (ioctl(s, TIOCSETD, (char *)&ldisc) < 0)
212                         error("ioctl(TIOCSETD, CHULDISC)", "", "");
213                     if (debug)
214                         (void) fprintf(stderr, "okay\n");
215           }
216 #endif
217 #ifdef STREAM
218           if (usechuldisc) {
219 
220                     if (debug)
221                         (void) fprintf(stderr, "Poping off streams...");
222                     while (ioctl(s, I_POP, 0) >=0) ;
223                     if (debug)
224                         (void) fprintf(stderr, "okay\n");
225                     if (debug)
226                         (void) fprintf(stderr, "Pushing CHU stream...");
227                     if (ioctl(s, I_PUSH, "chu") < 0)
228                         error("ioctl(I_PUSH, \"chu\")", "", "");
229                     if (debug)
230                         (void) fprintf(stderr, "okay\n");
231           }
232 #endif
233           return s;
234 }
235 
236 
237 /*
238  * process_raw - process characters in raw mode
239  */
240 int
process_raw(int s)241 process_raw(
242           int s
243           )
244 {
245           u_char c;
246           int n;
247           struct timeval tv;
248           struct timeval difftv;
249 
250           while ((n = read(s, &c, sizeof(char))) > 0) {
251                     (void) gettimeofday(&tv, (struct timezone *)0);
252                     if (dofilter)
253                         raw_filter((unsigned int)c, &tv);
254                     else {
255                               difftv.tv_sec = tv.tv_sec - lasttv.tv_sec;
256                               difftv.tv_usec = tv.tv_usec - lasttv.tv_usec;
257                               if (difftv.tv_usec < 0) {
258                                         difftv.tv_sec--;
259                                         difftv.tv_usec += 1000000;
260                               }
261                               (void) printf("%02x\t%lu.%06lu\t%lu.%06lu\n",
262                                               c, tv.tv_sec, tv.tv_usec, difftv.tv_sec,
263                                               difftv.tv_usec);
264                               lasttv = tv;
265                     }
266           }
267 
268           if (n == 0) {
269                     (void) fprintf(stderr, "%s: zero returned on read\n", progname);
270                     exit(1);
271           } else
272               error("read()", "", "");
273 }
274 
275 
276 /*
277  * raw_filter - run the line discipline filter over raw data
278  */
279 void
raw_filter(unsigned int c,struct timeval * tv)280 raw_filter(
281           unsigned int c,
282           struct timeval *tv
283           )
284 {
285           static struct timeval diffs[10];
286           struct timeval diff;
287           l_fp ts;
288 
289           if ((c & 0xf) > 9 || ((c>>4)&0xf) > 9) {
290                     if (debug)
291                         (void) fprintf(stderr,
292                                            "character %02x failed BCD test\n", c);
293                     chudata.ncodechars = 0;
294                     return;
295           }
296 
297           if (chudata.ncodechars > 0) {
298                     diff.tv_sec = tv->tv_sec
299                               - chudata.codetimes[chudata.ncodechars].tv_sec;
300                     diff.tv_usec = tv->tv_usec
301                               - chudata.codetimes[chudata.ncodechars].tv_usec;
302                     if (diff.tv_usec < 0) {
303                               diff.tv_sec--;
304                               diff.tv_usec += 1000000;
305                     } /*
306                         if (diff.tv_sec != 0 || diff.tv_usec > 900000) {
307                         if (debug)
308                         (void) fprintf(stderr,
309                         "character %02x failed time test\n");
310                         chudata.ncodechars = 0;
311                         return;
312                         } */
313           }
314 
315           chudata.codechars[chudata.ncodechars] = c;
316           chudata.codetimes[chudata.ncodechars] = *tv;
317           if (chudata.ncodechars > 0)
318               diffs[chudata.ncodechars] = diff;
319           if (++chudata.ncodechars == 10) {
320                     if (doprocess) {
321                               TVTOTS(&chudata.codetimes[NCHUCHARS-1], &ts);
322                               ts.l_ui += JAN_1970;
323                               chufilter(&chudata, &chudata.codetimes[NCHUCHARS-1]);
324                     } else {
325                               register int i;
326 
327                               for (i = 0; i < chudata.ncodechars; i++) {
328                                         (void) printf("%x%x\t%lu.%06lu\t%lu.%06lu\n",
329                                                         chudata.codechars[i] & 0xf,
330                                                         (chudata.codechars[i] >>4 ) & 0xf,
331                                                         chudata.codetimes[i].tv_sec,
332                                                         chudata.codetimes[i].tv_usec,
333                                                         diffs[i].tv_sec, diffs[i].tv_usec);
334                               }
335                     }
336                     chudata.ncodechars = 0;
337           }
338 }
339 
340 
341 /* #ifdef CHULDISC*/
342 /*
343  * process_ldisc - process line discipline
344  */
345 int
process_ldisc(int s)346 process_ldisc(
347           int s
348           )
349 {
350           struct chucode chu;
351           int n;
352           register int i;
353           struct timeval diff;
354           l_fp ts;
355           void chufilter();
356 
357           while ((n = read(s, (char *)&chu, sizeof chu)) > 0) {
358                     if (n != sizeof chu) {
359                               (void) fprintf(stderr, "Expected %d, got %d\n",
360                                                sizeof chu, n);
361                               continue;
362                     }
363 
364                     if (doprocess) {
365                               TVTOTS(&chu.codetimes[NCHUCHARS-1], &ts);
366                               ts.l_ui += JAN_1970;
367                               chufilter(&chu, &ts);
368                     } else {
369                               for (i = 0; i < NCHUCHARS; i++) {
370                                         if (i == 0)
371                                             diff.tv_sec = diff.tv_usec = 0;
372                                         else {
373                                                   diff.tv_sec = chu.codetimes[i].tv_sec
374                                                             - chu.codetimes[i-1].tv_sec;
375                                                   diff.tv_usec = chu.codetimes[i].tv_usec
376                                                             - chu.codetimes[i-1].tv_usec;
377                                                   if (diff.tv_usec < 0) {
378                                                             diff.tv_sec--;
379                                                             diff.tv_usec += 1000000;
380                                                   }
381                                         }
382                                         (void) printf("%x%x\t%lu.%06lu\t%lu.%06lu\n",
383                                                         chu.codechars[i] & 0xf, (chu.codechars[i]>>4)&0xf,
384                                                         chu.codetimes[i].tv_sec, chu.codetimes[i].tv_usec,
385                                                         diff.tv_sec, diff.tv_usec);
386                               }
387                     }
388           }
389           if (n == 0) {
390                     (void) fprintf(stderr, "%s: zero returned on read\n", progname);
391                     exit(1);
392           } else
393               error("read()", "", "");
394 }
395 /*#endif*/
396 
397 
398 /*
399  * error - print an error message
400  */
401 void
error(char * fmt,char * s1,char * s2)402 error(
403           char *fmt,
404           char *s1,
405           char *s2
406           )
407 {
408           (void) fprintf(stderr, "%s: ", progname);
409           (void) fprintf(stderr, fmt, s1, s2);
410           (void) fprintf(stderr, ": ");
411           perror("");
412           exit(1);
413 }
414 
415 /*
416  * Definitions
417  */
418 #define   MAXUNITS  4         /* maximum number of CHU units permitted */
419 #define   CHUDEV    "/dev/chu%d"        /* device we open.  %d is unit number */
420 #define   NCHUCODES 9         /* expect 9 CHU codes per minute */
421 
422 /*
423  * When CHU is operating optimally we want the primary clock distance
424  * to come out at 300 ms.  Thus, peer.distance in the CHU peer structure
425  * is set to 290 ms and we compute delays which are at least 10 ms long.
426  * The following are 290 ms and 10 ms expressed in u_fp format
427  */
428 #define   CHUDISTANCE         0x00004a3d
429 #define   CHUBASEDELAY        0x0000028f
430 
431 /*
432  * To compute a quality for the estimate (a pseudo delay) we add a
433  * fixed 10 ms for each missing code in the minute and add to this
434  * the sum of the differences between the remaining offsets and the
435  * estimated sample offset.
436  */
437 #define   CHUDELAYPENALTY     0x0000028f
438 
439 /*
440  * Other constant stuff
441  */
442 #define   CHUPRECISION        (-9)                /* what the heck */
443 #define   CHUREFID  "CHU\0"
444 
445 /*
446  * Default fudge factors
447  */
448 #define   DEFPROPDELAY        0x00624dd3          /* 0.0015 seconds, 1.5 ms */
449 #define   DEFFILTFUDGE        0x000d1b71          /* 0.0002 seconds, 200 us */
450 
451 /*
452  * Hacks to avoid excercising the multiplier.  I have no pride.
453  */
454 #define   MULBY10(x)          (((x)<<3) + ((x)<<1))
455 #define   MULBY60(x)          (((x)<<6) - ((x)<<2))         /* watch overflow */
456 #define   MULBY24(x)          (((x)<<4) + ((x)<<3))
457 
458 /*
459  * Constants for use when multiplying by 0.1.  ZEROPTONE is 0.1
460  * as an l_fp fraction, NZPOBITS is the number of significant bits
461  * in ZEROPTONE.
462  */
463 #define   ZEROPTONE 0x1999999a
464 #define   NZPOBITS  29
465 
466 /*
467  * The CHU table.  This gives the expected time of arrival of each
468  * character after the on-time second and is computed as follows:
469  * The CHU time code is sent at 300 bps.  Your average UART will
470  * synchronize at the edge of the start bit and will consider the
471  * character complete at the center of the first stop bit, i.e.
472  * 0.031667 ms later.  Thus the expected time of each interrupt
473  * is the start bit time plus 0.031667 seconds.  These times are
474  * in chutable[].  To this we add such things as propagation delay
475  * and delay fudge factor.
476  */
477 #define   CHARDELAY 0x081b4e80
478 
479 static u_long chutable[NCHUCHARS] = {
480           0x2147ae14 + CHARDELAY,                 /* 0.130 (exactly) */
481           0x2ac08312 + CHARDELAY,                 /* 0.167 (exactly) */
482           0x34395810 + CHARDELAY,                 /* 0.204 (exactly) */
483           0x3db22d0e + CHARDELAY,                 /* 0.241 (exactly) */
484           0x472b020c + CHARDELAY,                 /* 0.278 (exactly) */
485           0x50a3d70a + CHARDELAY,                 /* 0.315 (exactly) */
486           0x5a1cac08 + CHARDELAY,                 /* 0.352 (exactly) */
487           0x63958106 + CHARDELAY,                 /* 0.389 (exactly) */
488           0x6d0e5604 + CHARDELAY,                 /* 0.426 (exactly) */
489           0x76872b02 + CHARDELAY,                 /* 0.463 (exactly) */
490 };
491 
492 /*
493  * Keep the fudge factors separately so they can be set even
494  * when no clock is configured.
495  */
496 static l_fp propagation_delay;
497 static l_fp fudgefactor;
498 static l_fp offset_fudge;
499 
500 /*
501  * We keep track of the start of the year, watching for changes.
502  * We also keep track of whether the year is a leap year or not.
503  * All because stupid CHU doesn't include the year in the time code.
504  */
505 static u_long yearstart;
506 
507 /*
508  * Imported from the timer module
509  */
510 extern u_long current_time;
511 extern struct event timerqueue[];
512 
513 /*
514  * init_chu - initialize internal chu driver data
515  */
516 void
init_chu(void)517 init_chu(void)
518 {
519 
520           /*
521            * Initialize fudge factors to default.
522            */
523           propagation_delay.l_ui = 0;
524           propagation_delay.l_uf = DEFPROPDELAY;
525           fudgefactor.l_ui = 0;
526           fudgefactor.l_uf = DEFFILTFUDGE;
527           offset_fudge = propagation_delay;
528           L_ADD(&offset_fudge, &fudgefactor);
529 
530           yearstart = 0;
531 }
532 
533 
534 void
chufilter(struct chucode * chuc,l_fp * rtime)535 chufilter(
536           struct chucode *chuc,
537           l_fp *rtime
538           )
539 {
540           register int i;
541           register u_long date_ui;
542           register u_long tmp;
543           register u_char *code;
544           int isneg;
545           int imin;
546           int imax;
547           u_long reftime;
548           l_fp off[NCHUCHARS];
549           l_fp ts;
550           int day, hour, minute, second;
551           static u_char lastcode[NCHUCHARS];
552 
553           /*
554            * We'll skip the checks made in the kernel, but assume they've
555            * been done.  This means that all characters are BCD and
556            * the intercharacter spacing isn't unreasonable.
557            */
558 
559           /*
560            * print the code
561            */
562           for (i = 0; i < NCHUCHARS; i++)
563               printf("%c%c", (chuc->codechars[i] & 0xf) + '0',
564                        ((chuc->codechars[i]>>4) & 0xf) + '0');
565           printf("\n");
566 
567           /*
568            * Format check.  Make sure the two halves match.
569            */
570           for (i = 0; i < NCHUCHARS/2; i++)
571               if (chuc->codechars[i] != chuc->codechars[i+(NCHUCHARS/2)]) {
572                         (void) printf("Bad format, halves don't match\n");
573                         return;
574               }
575 
576           /*
577            * Break out the code into the BCD nibbles.  Only need to fiddle
578            * with the first half since both are identical.  Note the first
579            * BCD character is the low order nibble, the second the high order.
580            */
581           code = lastcode;
582           for (i = 0; i < NCHUCHARS/2; i++) {
583                     *code++ = chuc->codechars[i] & 0xf;
584                     *code++ = (chuc->codechars[i] >> 4) & 0xf;
585           }
586 
587           /*
588            * If the first nibble isn't a 6, we're up the creek
589            */
590           code = lastcode;
591           if (*code++ != 6) {
592                     (void) printf("Bad format, no 6 at start\n");
593                     return;
594           }
595 
596           /*
597            * Collect the day, the hour, the minute and the second.
598            */
599           day = *code++;
600           day = MULBY10(day) + *code++;
601           day = MULBY10(day) + *code++;
602           hour = *code++;
603           hour = MULBY10(hour) + *code++;
604           minute = *code++;
605           minute = MULBY10(minute) + *code++;
606           second = *code++;
607           second = MULBY10(second) + *code++;
608 
609           /*
610            * Sanity check the day and time.  Note that this
611            * only occurs on the 31st through the 39th second
612            * of the minute.
613            */
614           if (day < 1 || day > 366
615               || hour > 23 || minute > 59
616               || second < 31 || second > 39) {
617                     (void) printf("Failed date sanity check: %d %d %d %d\n",
618                                     day, hour, minute, second);
619                     return;
620           }
621 
622           /*
623            * Compute seconds into the year.
624            */
625           tmp = (u_long)(MULBY24((day-1)) + hour);          /* hours */
626           tmp = MULBY60(tmp) + (u_long)minute;              /* minutes */
627           tmp = MULBY60(tmp) + (u_long)second;              /* seconds */
628 
629           /*
630            * Now the fun begins.  We demand that the received time code
631            * be within CLOCK_WAYTOOBIG of the receive timestamp, but
632            * there is uncertainty about the year the timestamp is in.
633            * Use the current year start for the first check, this should
634            * work most of the time.
635            */
636           date_ui = tmp + yearstart;
637 #define CLOCK_WAYTOOBIG 1000 /* revived from ancient sources */
638           if (date_ui < (rtime->l_ui + CLOCK_WAYTOOBIG)
639               && date_ui > (rtime->l_ui - CLOCK_WAYTOOBIG))
640               goto codeokay;  /* looks good */
641 
642           /*
643            * Trouble.  Next check is to see if the year rolled over and, if
644            * so, try again with the new year's start.
645            */
646           date_ui = calyearstart(rtime->l_ui, NULL);
647           if (date_ui != yearstart) {
648                     yearstart = date_ui;
649                     date_ui += tmp;
650                     (void) printf("time %u, code %u, difference %d\n",
651                                     date_ui, rtime->l_ui, (long)date_ui-(long)rtime->l_ui);
652                     if (date_ui < (rtime->l_ui + CLOCK_WAYTOOBIG)
653                         && date_ui > (rtime->l_ui - CLOCK_WAYTOOBIG))
654                         goto codeokay;  /* okay this time */
655           }
656 
657           ts.l_uf = 0;
658           ts.l_ui = yearstart;
659           printf("yearstart %s\n", prettydate(&ts));
660           printf("received %s\n", prettydate(rtime));
661           ts.l_ui = date_ui;
662           printf("date_ui %s\n", prettydate(&ts));
663 
664           /*
665            * Here we know the year start matches the current system
666            * time.  One remaining possibility is that the time code
667            * is in the year previous to that of the system time.  This
668            * is only worth checking if the receive timestamp is less
669            * than CLOCK_WAYTOOBIG seconds into the new year.
670            */
671           if ((rtime->l_ui - yearstart) < CLOCK_WAYTOOBIG) {
672                     date_ui = tmp;
673                     date_ui += calyearstart(yearstart - CLOCK_WAYTOOBIG,
674                                                   NULL);
675                     if ((rtime->l_ui - date_ui) < CLOCK_WAYTOOBIG)
676                         goto codeokay;
677           }
678 
679           /*
680            * One last possibility is that the time stamp is in the year
681            * following the year the system is in.  Try this one before
682            * giving up.
683            */
684           date_ui = tmp;
685           date_ui += calyearstart(yearstart + (400 * SECSPERDAY),
686                                         NULL);
687           if ((date_ui - rtime->l_ui) >= CLOCK_WAYTOOBIG) {
688                     printf("Date hopelessly off\n");
689                     return;             /* hopeless, let it sync to other peers */
690           }
691 
692     codeokay:
693           reftime = date_ui;
694           /*
695            * We've now got the integral seconds part of the time code (we hope).
696            * The fractional part comes from the table.  We next compute
697            * the offsets for each character.
698            */
699           for (i = 0; i < NCHUCHARS; i++) {
700                     register u_long tmp2;
701 
702                     off[i].l_ui = date_ui;
703                     off[i].l_uf = chutable[i];
704                     tmp = chuc->codetimes[i].tv_sec + JAN_1970;
705                     TVUTOTSF(chuc->codetimes[i].tv_usec, tmp2);
706                     M_SUB(off[i].l_ui, off[i].l_uf, tmp, tmp2);
707           }
708 
709           /*
710            * Here is a *big* problem.  What one would normally
711            * do here on a machine with lots of clock bits (say
712            * a Vax or the gizmo board) is pick the most positive
713            * offset and the estimate, since this is the one that
714            * is most likely suffered the smallest interrupt delay.
715            * The trouble is that the low order clock bit on an IBM
716            * RT, which is the machine I had in mind when doing this,
717            * ticks at just under the millisecond mark.  This isn't
718            * precise enough.  What we can do to improve this is to
719            * average all 10 samples and rely on the second level
720            * filtering to pick the least delayed estimate.  Trouble
721            * is, this means we have to divide a 64 bit fixed point
722            * number by 10, a procedure which really sucks.  Oh, well.
723            * First compute the sum.
724            */
725           date_ui = 0;
726           tmp = 0;
727           for (i = 0; i < NCHUCHARS; i++)
728               M_ADD(date_ui, tmp, off[i].l_ui, off[i].l_uf);
729           if (M_ISNEG(date_ui, tmp))
730               isneg = 1;
731           else
732               isneg = 0;
733 
734           /*
735            * Here is a multiply-by-0.1 optimization that should apply
736            * just about everywhere.  If the magnitude of the sum
737            * is less than 9 we don't have to worry about overflow
738            * out of a 64 bit product, even after rounding.
739            */
740           if (date_ui < 9 || date_ui > 0xfffffff7) {
741                     register u_long prod_ui;
742                     register u_long prod_uf;
743 
744                     prod_ui = prod_uf = 0;
745                     /*
746                      * This code knows the low order bit in 0.1 is zero
747                      */
748                     for (i = 1; i < NZPOBITS; i++) {
749                               M_LSHIFT(date_ui, tmp);
750                               if (ZEROPTONE & (1<<i))
751                                   M_ADD(prod_ui, prod_uf, date_ui, tmp);
752                     }
753 
754                     /*
755                      * Done, round it correctly.  Prod_ui contains the
756                      * fraction.
757                      */
758                     if (prod_uf & 0x80000000)
759                         prod_ui++;
760                     if (isneg)
761                         date_ui = 0xffffffff;
762                     else
763                         date_ui = 0;
764                     tmp = prod_ui;
765                     /*
766                      * date_ui is integral part, tmp is fraction.
767                      */
768           } else {
769                     register u_long prod_ovr;
770                     register u_long prod_ui;
771                     register u_long prod_uf;
772                     register u_long highbits;
773 
774                     prod_ovr = prod_ui = prod_uf = 0;
775                     if (isneg)
776                         highbits = 0xffffffff;    /* sign extend */
777                     else
778                         highbits = 0;
779                     /*
780                      * This code knows the low order bit in 0.1 is zero
781                      */
782                     for (i = 1; i < NZPOBITS; i++) {
783                               M_LSHIFT3(highbits, date_ui, tmp);
784                               if (ZEROPTONE & (1<<i))
785                                   M_ADD3(prod_ovr, prod_uf, prod_ui,
786                                            highbits, date_ui, tmp);
787                     }
788 
789                     if (prod_uf & 0x80000000)
790                         M_ADDUF(prod_ovr, prod_ui, (u_long)1);
791                     date_ui = prod_ovr;
792                     tmp = prod_ui;
793           }
794 
795           /*
796            * At this point we have the mean offset, with the integral
797            * part in date_ui and the fractional part in tmp.  Store
798            * it in the structure.
799            */
800           /*
801            * Add in fudge factor.
802            */
803           M_ADD(date_ui, tmp, offset_fudge.l_ui, offset_fudge.l_uf);
804 
805           /*
806            * Find the minimun and maximum offset
807            */
808           imin = imax = 0;
809           for (i = 1; i < NCHUCHARS; i++) {
810                     if (L_ISGEQ(&off[i], &off[imax])) {
811                               imax = i;
812                     } else if (L_ISGEQ(&off[imin], &off[i])) {
813                               imin = i;
814                     }
815           }
816 
817           L_ADD(&off[imin], &offset_fudge);
818           if (imin != imax)
819               L_ADD(&off[imax], &offset_fudge);
820           (void) printf("mean %s, min %s, max %s\n",
821                           mfptoa(date_ui, tmp, 8), lfptoa(&off[imin], 8),
822                           lfptoa(&off[imax], 8));
823 }
824