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