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