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