2 * Copyright 2000, International Business Machines Corporation and others.
5 * This software has been released under the terms of the IBM Public
6 * License. For details, see the LICENSE file in the top-level source
7 * directory or online at http://www.openafs.org/dl/license10.html
10 #include <afsconfig.h>
11 #include <afs/param.h>
16 #if defined(REFCLOCK) && defined(PSTI)
17 #define ERR_RATE 60 /* Repeat errors once an hour */
21 * January 1988 -- orignal
22 * January 1989 -- QU version
24 * This module facilitates reading a Precision Time Standard, Inc.
25 * WWV radio clock. We assume that clock is configured for 9600 baud,
26 * no parity. Output is accepted in either 24 or 12 hour format.
27 * Time is requested and processed in GMT.
29 * This version is designed to make use of the QU command due to
30 * additional information it provides (like date and flags).
31 * Select is used to prevent hanging in a read when there are
32 * no characters to read. The line is run in cooked mode to
35 * This requires a PSTI ROM revision later 4.01.000 or later.
38 * init_clock_psti(): Open the tty associated with the clock and
39 * set its tty mode bits. Returns fd on success
41 * read_clock_psti(): Reads the clock and returns either 0 on success
42 * or non-zero on error. On success, pointers are
43 * provided to the reference and local time.
48 #include <sys/types.h>
49 #include <sys/ioctl.h>
50 #if defined(AFS_SUN_ENV)
58 static int nerrors = 0;
59 static char clockdata[32];
60 #define MIN_READ 13 /* for Rev 4.01.001 */
62 static double reltime();
67 #define CLOCKDEV "/dev/radioclock"
73 #include "AFS_component_version_number.c"
79 struct timeval *tvp, *otvp;
82 if (openclock(CLOCKDEV))
84 (void)readclock(&tvp, &otvp);
89 #endif /* STANDALONE */
92 init_clock_psti(timesource)
102 if ((cfd = open(timesource, 2)) < 0) {
108 syslog(LOG_ERR, "can't open %s: %m", timesource);
112 if (ioctl(cfd, TIOCEXCL, 0) < 0) {
115 perror("TIOCEXCL on radioclock failed");
118 syslog(LOG_ERR, "TIOCEXCL on %s failed: %m", timesource);
122 if (ioctl(cfd, TCGETA, &tty) < 0) {
125 perror("ioctl on radioclock failed");
128 syslog(LOG_ERR, "ioctl on %s failed: %m", timesource);
131 tty.c_cflag = (B9600 << 16) | B9600 | CS8 | CLOCAL | CREAD;
135 memset((char *)tty.c_cc, 0, sizeof tty.c_cc);
136 tty.c_cc[VMIN] = MIN_READ;
138 if (ioctl(cfd, TCSETA, &tty) < 0) {
139 #else /* TCSETA Use older Berkeley style IOCTL's */
140 memset((char *)&tty, 0, sizeof tty);
141 tty.sg_ispeed = tty.sg_ospeed = B9600;
142 tty.sg_flags = ANYP | CRMOD;
143 tty.sg_erase = tty.sg_kill = '\0';
144 if (ioctl(cfd, TIOCSETP, &tty) < 0) {
148 perror("ioctl on radioclock failed");
151 syslog(LOG_ERR, "ioctl on %s failed: %m", timesource);
154 if (write(cfd, "xxxxxxsn\r", 9) != 9) {
157 perror("init write to radioclock failed");
160 syslog(LOG_ERR, "init write to %s failed: %m", timesource);
163 return (cfd); /* Succeeded in opening the clock */
167 * read_clock_psti() -- Read the PSTI Radio Clock.
169 read_clock_psti(cfd, tvpp, otvpp)
171 struct timeval **tvpp, **otvpp;
173 static struct timeval radiotime;
174 static struct timeval mytime;
175 struct timeval timeout;
177 struct tm radio_tm, *rtm = &radio_tm;
180 register double diff;
190 FD_SET(cfd, &readfds);
194 (void)ioctl(cfd, TIOCFLUSH, 0); /* scrap the I/O queues */
196 /* BEGIN TIME CRITICAL CODE SECTION!!!!!! */
197 /* EVERY CYCLE FROM THIS POINT OUT ADDS TO THE INACCURACY OF
198 * THE READ CLOCK VALUE!!!!! */
199 if (write(cfd, "\003qu0000", 7) != 7) {
202 printf("radioclock write failed\n");
205 if ((nerrors++ % ERR_RATE) == 0)
206 syslog(LOG_ERR, "write to radioclock failed: %m");
209 if (select(cfd + 1, &readfds, 0, 0, &timeout) != 1) {
212 printf("radioclock poll timed out\n");
215 if ((nerrors++ % ERR_RATE) == 0)
216 syslog(LOG_ERR, "poll of radioclock failed: %m");
219 if ((i = read(cfd, clockdata, sizeof clockdata)) < MIN_READ) {
222 printf("radioclock read error (%d)\n", i);
225 if ((nerrors++ % ERR_RATE) == 0)
226 syslog(LOG_ERR, "radioclock read error (%d!=13): %m", i);
230 (void)gettimeofday(&mytime, NULL);
231 /* END OF TIME CRITICAL CODE SECTION!!!! */
233 if (clockdata[i - 1] != '\n') {
236 printf("radioclock format error1 (%.12s)(0x%x)\n", clockdata,
240 if ((nerrors++ % ERR_RATE) == 0)
241 syslog(LOG_ERR, "radioclock format error1 (%.12s)(0x%x)",
242 clockdata, clockdata[12]);
245 for (i = 0; i < 12; i++) {
246 if (clockdata[i] < '0' || clockdata[i] > 'o') {
249 printf("radioclock format error2\n");
252 if ((nerrors++ % ERR_RATE) == 0)
253 syslog(LOG_ERR, "radioclock format error2\n");
257 stat1 = clockdata[0] - '0';
258 stat2 = clockdata[1] - '0';
259 millis = ((clockdata[2] - '0') * 64) + (clockdata[3] - '0');
260 rtm->tm_sec = (clockdata[4] - '0');
261 rtm->tm_min = (clockdata[5] - '0');
262 rtm->tm_hour = (clockdata[6] - '0');
263 rtm->tm_yday = ((clockdata[7] - '0') * 64) + (clockdata[8] - '0') - 1;
264 rtm->tm_year = 86 + (clockdata[9] - '0');
265 /* byte 10 and 11 reserved */
268 * Correct "hours" based on whether or not AM/PM mode is enabled.
269 * If clock is in 24 hour (military) mode then no correction is
272 if (stat2 & 0x10) { /* Map AM/PM time to Military */
274 if (rtm->tm_hour != 12)
277 if (rtm->tm_hour == 12)
282 if (stat1 != 0x4 && (nerrors++ % ERR_RATE) == 0) {
285 printf("radioclock fault #%d 0x%x:%s%s%s%s%s%s\n", nerrors, stat1,
286 stat1 & 0x20 ? " Out of Spec," : "",
287 stat1 & 0x10 ? " Hardware Fault," : "",
288 stat1 & 0x8 ? " Signal Fault," : "",
289 stat1 & 0x4 ? " Time Avail," : "",
290 stat1 & 0x2 ? " Year Mismatch," : "",
291 stat1 & 0x1 ? " Clock Reset," : "");
294 sprintf(message, "radioclock fault #%d 0x%x:%s%s%s%s%s%s\n",
295 nerrors, stat1, stat1 & 0x20 ? " Out of Spec," : "",
296 stat1 & 0x10 ? " Hardware Fault," : "",
297 stat1 & 0x8 ? " Signal Fault," : "",
298 stat1 & 0x4 ? " Time Avail," : "",
299 stat1 & 0x2 ? " Year Mismatch," : "",
300 stat1 & 0x1 ? " Clock Reset," : "");
301 syslog(LOG_ERR, message);
304 if (stat1 & 0x38) /* Out of Spec, Hardware Fault, Signal Fault */
306 if ((millis > 999 || rtm->tm_sec > 60 || rtm->tm_min > 60
307 || rtm->tm_hour > 23 || rtm->tm_yday > 365)
308 && (nerrors++ % ERR_RATE) == 0) {
311 printf("radioclock bogon #%d: %dd %dh %dm %ds %dms\n", nerrors,
312 rtm->tm_yday, rtm->tm_hour, rtm->tm_min, rtm->tm_sec,
316 sprintf(message, "radioclock bogon #%d: %dd %dh %dm %ds %dms\n",
317 nerrors, rtm->tm_yday, rtm->tm_hour, rtm->tm_min,
318 rtm->tm_sec, millis);
319 syslog(LOG_ERR, message);
323 mtm = gmtime(&mytime.tv_sec);
324 diff = reltime(rtm, millis * 1000) - reltime(mtm, mytime.tv_usec);
328 strftime(buf, 256, "Clock time: %Y day %j %T", rtm);
329 strcat(buf, ".%03d diff %.3f\n", millis, diff);
334 if (diff > (90 * 24 * 60 * 60.0) && (nerrors++ % ERR_RATE) == 0) {
335 char buf1[16], buf2[16];
336 strftime(buf1, 16, "%Y/%j", mtm);
337 strftime(buf2, 16, "%Y/%j", rtm);
340 printf("offset excessive (system %s, clock %s)\n", buf1, buf2);
343 syslog(LOG_ERR, "offset excessive (system %s, clock %s)\n", buf1,
348 diff += (double)mytime.tv_sec + ((double)mytime.tv_usec / 1000000.0);
349 radiotime.tv_sec = diff;
350 radiotime.tv_usec = (diff - (double)radiotime.tv_sec) * 1000000;
354 strftime(buf, 256, "%Y day %j %T", mtm);
355 printf("System time: %s.%03d\n", buf, mytime.tv_usec / 1000);
356 printf("stat1 0%o, stat2 0%o: ", stat1, stat2);
358 printf("%s%s%s%s%s%s%s%s%s%s%s%s",
359 stat1 & 0x20 ? " Out of Spec," : "",
360 stat1 & 0x10 ? " Hardware Fault," : "",
361 stat1 & 0x8 ? " Signal Fault," : "",
362 stat1 & 0x4 ? " Time Avail," : "",
363 stat1 & 0x2 ? " Year Mismatch," : "",
364 stat1 & 0x1 ? " Clock Reset," : "",
365 stat2 & 0x20 ? " DST on," : "",
366 stat2 & 0x10 ? " 12hr mode," : "",
367 stat2 & 0x8 ? " PM," : "", stat2 & 0x4 ? " Spare?," : "",
368 stat2 & 0x2 ? " DST??? +1," : "",
369 stat2 & 0x1 ? " DST??? -1," : "");
373 /* If necessary, acknowledge "Clock Reset" flag bit */
375 if (write(cfd, "si0", 3) != 3) {
378 printf("radioclock reset write failed\n");
381 syslog(LOG_ERR, "reset write to radioclock failed: %m");
385 if (nerrors && stat1 == 0x4) {
386 syslog(LOG_ERR, "radioclock OK (after %d errors)", nerrors);
396 register struct tm *tm;
399 return (tm->tm_year * (366.0 * 24.0 * 60.0 * 60.0) +
400 tm->tm_yday * (24.0 * 60.0 * 60.0) + tm->tm_hour * (60.0 * 60.0) +
401 tm->tm_min * (60.0) + tm->tm_sec + usec / 1000000.0);