3 /* chutest.c,v 3.1 1993/07/06 01:05:21 jbj Exp
4 * chutest - test the CHU clock
9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <sys/ioctl.h>
16 #include "../include/ntp_fp.h"
17 #include "../include/ntp.h"
18 #include "../include/ntp_unixtime.h"
22 # ifdef HAVE_SYS_CHUDEFS_H
23 #include <sys/chudefs.h>
30 # ifdef HAVE_SYS_CHUDEFS_H
31 #include <sys/chudefs.h>
37 #define NCHUCHARS (10)
40 u_char codechars
[NCHUCHARS
]; /* code characters */
41 u_char ncodechars
; /* number of code characters */
42 u_char chustatus
; /* not used currently */
43 struct timeval codetimes
[NCHUCHARS
]; /* arrival times */
48 #define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
53 int dofilter
= 0; /* set to 1 when we should run filter algorithm */
54 int showtimes
= 0; /* set to 1 when we should show char arrival times */
55 int doprocess
= 0; /* set to 1 when we do processing analogous to driver */
57 int usechuldisc
= 0; /* set to 1 when CHU line discipline should be used */
60 int usechuldisc
= 0; /* set to 1 when CHU line discipline should be used */
63 struct timeval lasttv
;
64 struct chucode chudata
;
66 extern u_long ustotslo
[];
67 extern u_long ustotsmid
[];
68 extern u_long ustotshi
[];
71 * main - parse arguments and handle options
81 extern int ntp_optind
;
82 extern char *ntp_optarg
;
86 while ((c
= ntp_getopt(argc
, argv
, "cdfpt")) != EOF
)
99 (void) fprintf(stderr
,
100 "%s: CHU line discipline not available on this machine\n",
120 if (errflg
|| ntp_optind
+1 != argc
) {
122 (void) fprintf(stderr
, "usage: %s [-dft] tty_device\n",
126 (void) fprintf(stderr
, "usage: %s [-dft] tty_device\n",
131 (void) fprintf(stderr
, "usage: %s [-cdft] tty_device\n",
138 (void) gettimeofday(&lasttv
, (struct timezone
*)0);
139 c
= openterm(argv
[ntp_optind
]);
157 * openterm - open a port to the CHU clock
168 (void) fprintf(stderr
, "Doing open...");
169 if ((s
= open(dev
, O_RDONLY
, 0777)) < 0)
170 error("open(%s)", dev
, "");
172 (void) fprintf(stderr
, "open okay\n");
175 (void) fprintf(stderr
, "Setting exclusive use...");
176 if (ioctl(s
, TIOCEXCL
, (char *)0) < 0)
177 error("ioctl(TIOCEXCL)", "", "");
179 (void) fprintf(stderr
, "done\n");
181 ttyb
.sg_ispeed
= ttyb
.sg_ospeed
= B300
;
182 ttyb
.sg_erase
= ttyb
.sg_kill
= 0;
183 ttyb
.sg_flags
= EVENP
|ODDP
|RAW
;
185 (void) fprintf(stderr
, "Setting baud rate et al...");
186 if (ioctl(s
, TIOCSETP
, (char *)&ttyb
) < 0)
187 error("ioctl(TIOCSETP, raw)", "", "");
189 (void) fprintf(stderr
, "done\n");
196 (void) fprintf(stderr
, "Switching to CHU ldisc...");
198 if (ioctl(s
, TIOCSETD
, (char *)&ldisc
) < 0)
199 error("ioctl(TIOCSETD, CHULDISC)", "", "");
201 (void) fprintf(stderr
, "okay\n");
208 (void) fprintf(stderr
, "Poping off streams...");
209 while (ioctl(s
, I_POP
, 0) >=0) ;
211 (void) fprintf(stderr
, "okay\n");
213 (void) fprintf(stderr
, "Pushing CHU stream...");
214 if (ioctl(s
, I_PUSH
, "chu") < 0)
215 error("ioctl(I_PUSH, \"chu\")", "", "");
217 (void) fprintf(stderr
, "okay\n");
225 * process_raw - process characters in raw mode
235 struct timeval difftv
;
237 while ((n
= read(s
, &c
, sizeof(char))) > 0) {
238 (void) gettimeofday(&tv
, (struct timezone
*)0);
240 raw_filter((unsigned int)c
, &tv
);
242 difftv
.tv_sec
= tv
.tv_sec
- lasttv
.tv_sec
;
243 difftv
.tv_usec
= tv
.tv_usec
- lasttv
.tv_usec
;
244 if (difftv
.tv_usec
< 0) {
246 difftv
.tv_usec
+= 1000000;
248 (void) printf("%02x\t%lu.%06lu\t%lu.%06lu\n",
249 c
, tv
.tv_sec
, tv
.tv_usec
, difftv
.tv_sec
,
256 (void) fprintf(stderr
, "%s: zero returned on read\n", progname
);
259 error("read()", "", "");
264 * raw_filter - run the line discipline filter over raw data
272 static struct timeval diffs
[10] = { 0 };
277 if ((c
& 0xf) > 9 || ((c
>>4)&0xf) > 9) {
279 (void) fprintf(stderr
,
280 "character %02x failed BCD test\n");
281 chudata
.ncodechars
= 0;
285 if (chudata
.ncodechars
> 0) {
286 diff
.tv_sec
= tv
->tv_sec
287 - chudata
.codetimes
[chudata
.ncodechars
].tv_sec
;
288 diff
.tv_usec
= tv
->tv_usec
289 - chudata
.codetimes
[chudata
.ncodechars
].tv_usec
;
290 if (diff
.tv_usec
< 0) {
292 diff
.tv_usec
+= 1000000;
294 if (diff.tv_sec != 0 || diff.tv_usec > 900000) {
296 (void) fprintf(stderr,
297 "character %02x failed time test\n");
298 chudata.ncodechars = 0;
303 chudata
.codechars
[chudata
.ncodechars
] = c
;
304 chudata
.codetimes
[chudata
.ncodechars
] = *tv
;
305 if (chudata
.ncodechars
> 0)
306 diffs
[chudata
.ncodechars
] = diff
;
307 if (++chudata
.ncodechars
== 10) {
309 TVTOTS(&chudata
.codetimes
[NCHUCHARS
-1], &ts
);
311 chufilter(&chudata
, &chudata
.codetimes
[NCHUCHARS
-1]);
315 for (i
= 0; i
< chudata
.ncodechars
; i
++) {
316 (void) printf("%x%x\t%lu.%06lu\t%lu.%06lu\n",
317 chudata
.codechars
[i
] & 0xf,
318 (chudata
.codechars
[i
] >>4 ) & 0xf,
319 chudata
.codetimes
[i
].tv_sec
,
320 chudata
.codetimes
[i
].tv_usec
,
321 diffs
[i
].tv_sec
, diffs
[i
].tv_usec
);
324 chudata
.ncodechars
= 0;
331 * process_ldisc - process line discipline
345 while ((n
= read(s
, (char *)&chu
, sizeof chu
)) > 0) {
346 if (n
!= sizeof chu
) {
347 (void) fprintf(stderr
, "Expected %d, got %d\n",
353 TVTOTS(&chu
.codetimes
[NCHUCHARS
-1], &ts
);
355 chufilter(&chu
, &ts
);
357 for (i
= 0; i
< NCHUCHARS
; i
++) {
359 diff
.tv_sec
= diff
.tv_usec
= 0;
361 diff
.tv_sec
= chu
.codetimes
[i
].tv_sec
362 - chu
.codetimes
[i
-1].tv_sec
;
363 diff
.tv_usec
= chu
.codetimes
[i
].tv_usec
364 - chu
.codetimes
[i
-1].tv_usec
;
365 if (diff
.tv_usec
< 0) {
367 diff
.tv_usec
+= 1000000;
370 (void) printf("%x%x\t%lu.%06lu\t%lu.%06lu\n",
371 chu
.codechars
[i
] & 0xf, (chu
.codechars
[i
]>>4)&0xf,
372 chu
.codetimes
[i
].tv_sec
, chu
.codetimes
[i
].tv_usec
,
373 diff
.tv_sec
, diff
.tv_usec
);
378 (void) fprintf(stderr
, "%s: zero returned on read\n", progname
);
381 error("read()", "", "");
387 * error - print an error message
396 (void) fprintf(stderr
, "%s: ", progname
);
397 (void) fprintf(stderr
, fmt
, s1
, s2
);
398 (void) fprintf(stderr
, ": ");
406 #define MAXUNITS 4 /* maximum number of CHU units permitted */
407 #define CHUDEV "/dev/chu%d" /* device we open. %d is unit number */
408 #define NCHUCODES 9 /* expect 9 CHU codes per minute */
411 * When CHU is operating optimally we want the primary clock distance
412 * to come out at 300 ms. Thus, peer.distance in the CHU peer structure
413 * is set to 290 ms and we compute delays which are at least 10 ms long.
414 * The following are 290 ms and 10 ms expressed in u_fp format
416 #define CHUDISTANCE 0x00004a3d
417 #define CHUBASEDELAY 0x0000028f
420 * To compute a quality for the estimate (a pseudo delay) we add a
421 * fixed 10 ms for each missing code in the minute and add to this
422 * the sum of the differences between the remaining offsets and the
423 * estimated sample offset.
425 #define CHUDELAYPENALTY 0x0000028f
428 * Other constant stuff
430 #define CHUPRECISION (-9) /* what the heck */
431 #define CHUREFID "CHU\0"
434 * Default fudge factors
436 #define DEFPROPDELAY 0x00624dd3 /* 0.0015 seconds, 1.5 ms */
437 #define DEFFILTFUDGE 0x000d1b71 /* 0.0002 seconds, 200 us */
440 * Hacks to avoid excercising the multiplier. I have no pride.
442 #define MULBY10(x) (((x)<<3) + ((x)<<1))
443 #define MULBY60(x) (((x)<<6) - ((x)<<2)) /* watch overflow */
444 #define MULBY24(x) (((x)<<4) + ((x)<<3))
447 * Constants for use when multiplying by 0.1. ZEROPTONE is 0.1
448 * as an l_fp fraction, NZPOBITS is the number of significant bits
451 #define ZEROPTONE 0x1999999a
455 * The CHU table. This gives the expected time of arrival of each
456 * character after the on-time second and is computed as follows:
457 * The CHU time code is sent at 300 bps. Your average UART will
458 * synchronize at the edge of the start bit and will consider the
459 * character complete at the center of the first stop bit, i.e.
460 * 0.031667 ms later. Thus the expected time of each interrupt
461 * is the start bit time plus 0.031667 seconds. These times are
462 * in chutable[]. To this we add such things as propagation delay
463 * and delay fudge factor.
465 #define CHARDELAY 0x081b4e80
467 static u_long chutable
[NCHUCHARS
] = {
468 0x2147ae14 + CHARDELAY
, /* 0.130 (exactly) */
469 0x2ac08312 + CHARDELAY
, /* 0.167 (exactly) */
470 0x34395810 + CHARDELAY
, /* 0.204 (exactly) */
471 0x3db22d0e + CHARDELAY
, /* 0.241 (exactly) */
472 0x472b020c + CHARDELAY
, /* 0.278 (exactly) */
473 0x50a3d70a + CHARDELAY
, /* 0.315 (exactly) */
474 0x5a1cac08 + CHARDELAY
, /* 0.352 (exactly) */
475 0x63958106 + CHARDELAY
, /* 0.389 (exactly) */
476 0x6d0e5604 + CHARDELAY
, /* 0.426 (exactly) */
477 0x76872b02 + CHARDELAY
, /* 0.463 (exactly) */
481 * Keep the fudge factors separately so they can be set even
482 * when no clock is configured.
484 static l_fp propagation_delay
;
485 static l_fp fudgefactor
;
486 static l_fp offset_fudge
;
489 * We keep track of the start of the year, watching for changes.
490 * We also keep track of whether the year is a leap year or not.
491 * All because stupid CHU doesn't include the year in the time code.
493 static u_long yearstart
;
496 * Imported from the timer module
498 extern u_long current_time
;
499 extern struct event timerqueue
[];
502 * Time conversion tables imported from the library
504 extern u_long ustotslo
[];
505 extern u_long ustotsmid
[];
506 extern u_long ustotshi
[];
510 * init_chu - initialize internal chu driver data
517 * Initialize fudge factors to default.
519 propagation_delay
.l_ui
= 0;
520 propagation_delay
.l_uf
= DEFPROPDELAY
;
521 fudgefactor
.l_ui
= 0;
522 fudgefactor
.l_uf
= DEFFILTFUDGE
;
523 offset_fudge
= propagation_delay
;
524 L_ADD(&offset_fudge
, &fudgefactor
);
532 struct chucode
*chuc
,
537 register u_long date_ui
;
539 register u_char
*code
;
546 int day
, hour
, minute
, second
;
547 static u_char lastcode
[NCHUCHARS
];
548 extern u_long
calyearstart();
549 extern char *mfptoa();
551 extern char *prettydate();
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.
562 for (i
= 0; i
< NCHUCHARS
; i
++)
563 printf("%c%c", (chuc
->codechars
[i
] & 0xf) + '0',
564 ((chuc
->codechars
[i
]>>4) & 0xf) + '0');
568 * Format check. Make sure the two halves match.
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");
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.
582 for (i
= 0; i
< NCHUCHARS
/2; i
++) {
583 *code
++ = chuc
->codechars
[i
] & 0xf;
584 *code
++ = (chuc
->codechars
[i
] >> 4) & 0xf;
588 * If the first nibble isn't a 6, we're up the creek
592 (void) printf("Bad format, no 6 at start\n");
597 * Collect the day, the hour, the minute and the second.
600 day
= MULBY10(day
) + *code
++;
601 day
= MULBY10(day
) + *code
++;
603 hour
= MULBY10(hour
) + *code
++;
605 minute
= MULBY10(minute
) + *code
++;
607 second
= MULBY10(second
) + *code
++;
610 * Sanity check the day and time. Note that this
611 * only occurs on the 31st through the 39th second
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
);
623 * Compute seconds into the year.
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 */
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.
636 date_ui
= tmp
+ yearstart
;
637 if (date_ui
< (rtime
->l_ui
+ CLOCK_WAYTOOBIG
)
638 && date_ui
> (rtime
->l_ui
- CLOCK_WAYTOOBIG
))
639 goto codeokay
; /* looks good */
642 * Trouble. Next check is to see if the year rolled over and, if
643 * so, try again with the new year's start.
645 date_ui
= calyearstart(rtime
->l_ui
);
646 if (date_ui
!= yearstart
) {
649 (void) printf("time %u, code %u, difference %d\n",
650 date_ui
, rtime
->l_ui
, (long)date_ui
-(long)rtime
->l_ui
);
651 if (date_ui
< (rtime
->l_ui
+ CLOCK_WAYTOOBIG
)
652 && date_ui
> (rtime
->l_ui
- CLOCK_WAYTOOBIG
))
653 goto codeokay
; /* okay this time */
658 printf("yearstart %s\n", prettydate(&ts
));
659 printf("received %s\n", prettydate(rtime
));
661 printf("date_ui %s\n", prettydate(&ts
));
664 * Here we know the year start matches the current system
665 * time. One remaining possibility is that the time code
666 * is in the year previous to that of the system time. This
667 * is only worth checking if the receive timestamp is less
668 * than CLOCK_WAYTOOBIG seconds into the new year.
670 if ((rtime
->l_ui
- yearstart
) < CLOCK_WAYTOOBIG
) {
671 date_ui
= tmp
+ calyearstart(yearstart
- CLOCK_WAYTOOBIG
);
672 if ((rtime
->l_ui
- date_ui
) < CLOCK_WAYTOOBIG
)
677 * One last possibility is that the time stamp is in the year
678 * following the year the system is in. Try this one before
681 date_ui
= tmp
+ calyearstart(yearstart
+ (400*24*60*60)); /* 400 days */
682 if ((date_ui
- rtime
->l_ui
) >= CLOCK_WAYTOOBIG
) {
683 printf("Date hopelessly off\n");
684 return; /* hopeless, let it sync to other peers */
690 * We've now got the integral seconds part of the time code (we hope).
691 * The fractional part comes from the table. We next compute
692 * the offsets for each character.
694 for (i
= 0; i
< NCHUCHARS
; i
++) {
695 register u_long tmp2
;
697 off
[i
].l_ui
= date_ui
;
698 off
[i
].l_uf
= chutable
[i
];
699 tmp
= chuc
->codetimes
[i
].tv_sec
+ JAN_1970
;
700 TVUTOTSF(chuc
->codetimes
[i
].tv_usec
, tmp2
);
701 M_SUB(off
[i
].l_ui
, off
[i
].l_uf
, tmp
, tmp2
);
705 * Here is a *big* problem. What one would normally
706 * do here on a machine with lots of clock bits (say
707 * a Vax or the gizmo board) is pick the most positive
708 * offset and the estimate, since this is the one that
709 * is most likely suffered the smallest interrupt delay.
710 * The trouble is that the low order clock bit on an IBM
711 * RT, which is the machine I had in mind when doing this,
712 * ticks at just under the millisecond mark. This isn't
713 * precise enough. What we can do to improve this is to
714 * average all 10 samples and rely on the second level
715 * filtering to pick the least delayed estimate. Trouble
716 * is, this means we have to divide a 64 bit fixed point
717 * number by 10, a procedure which really sucks. Oh, well.
718 * First compute the sum.
722 for (i
= 0; i
< NCHUCHARS
; i
++)
723 M_ADD(date_ui
, tmp
, off
[i
].l_ui
, off
[i
].l_uf
);
724 if (M_ISNEG(date_ui
, tmp
))
730 * Here is a multiply-by-0.1 optimization that should apply
731 * just about everywhere. If the magnitude of the sum
732 * is less than 9 we don't have to worry about overflow
733 * out of a 64 bit product, even after rounding.
735 if (date_ui
< 9 || date_ui
> 0xfffffff7) {
736 register u_long prod_ui
;
737 register u_long prod_uf
;
739 prod_ui
= prod_uf
= 0;
741 * This code knows the low order bit in 0.1 is zero
743 for (i
= 1; i
< NZPOBITS
; i
++) {
744 M_LSHIFT(date_ui
, tmp
);
745 if (ZEROPTONE
& (1<<i
))
746 M_ADD(prod_ui
, prod_uf
, date_ui
, tmp
);
750 * Done, round it correctly. Prod_ui contains the
753 if (prod_uf
& 0x80000000)
756 date_ui
= 0xffffffff;
761 * date_ui is integral part, tmp is fraction.
764 register u_long prod_ovr
;
765 register u_long prod_ui
;
766 register u_long prod_uf
;
767 register u_long highbits
;
769 prod_ovr
= prod_ui
= prod_uf
= 0;
771 highbits
= 0xffffffff; /* sign extend */
775 * This code knows the low order bit in 0.1 is zero
777 for (i
= 1; i
< NZPOBITS
; i
++) {
778 M_LSHIFT3(highbits
, date_ui
, tmp
);
779 if (ZEROPTONE
& (1<<i
))
780 M_ADD3(prod_ovr
, prod_uf
, prod_ui
,
781 highbits
, date_ui
, tmp
);
784 if (prod_uf
& 0x80000000)
785 M_ADDUF(prod_ovr
, prod_ui
, (u_long
)1);
791 * At this point we have the mean offset, with the integral
792 * part in date_ui and the fractional part in tmp. Store
793 * it in the structure.
796 * Add in fudge factor.
798 M_ADD(date_ui
, tmp
, offset_fudge
.l_ui
, offset_fudge
.l_uf
);
801 * Find the minimun and maximum offset
804 for (i
= 1; i
< NCHUCHARS
; i
++) {
805 if (L_ISGEQ(&off
[i
], &off
[imax
])) {
807 } else if (L_ISGEQ(&off
[imin
], &off
[i
])) {
812 L_ADD(&off
[imin
], &offset_fudge
);
814 L_ADD(&off
[imax
], &offset_fudge
);
815 (void) printf("mean %s, min %s, max %s\n",
816 mfptoa(date_ui
, tmp
, 8), lfptoa(&off
[imin
], 8),
817 lfptoa(&off
[imax
], 8));