1 /* 2 * refclock_wwvb - clock driver for Spectracom WWVB and GPS receivers 3 */ 4 5 #ifdef HAVE_CONFIG_H 6 #include <config.h> 7 #endif 8 9 #if defined(REFCLOCK) && defined(CLOCK_SPECTRACOM) 10 11 #include "ntpd.h" 12 #include "ntp_io.h" 13 #include "ntp_refclock.h" 14 #include "ntp_calendar.h" 15 #include "ntp_stdlib.h" 16 17 #include <stdio.h> 18 #include <ctype.h> 19 20 /* 21 * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB 22 * Synchronized Clocks and the Netclock/GPS Master Clock. Both the WWVB 23 * and GPS clocks have proven reliable sources of time; however, the 24 * WWVB clocks have proven vulnerable to high ambient conductive RF 25 * interference. The claimed accuracy of the WWVB clocks is 100 us 26 * relative to the broadcast signal, while the claimed accuracy of the 27 * GPS clock is 50 ns; however, in most cases the actual accuracy is 28 * limited by the resolution of the timecode and the latencies of the 29 * serial interface and operating system. 30 * 31 * The WWVB and GPS clocks should be configured for 24-hour display, 32 * AUTO DST off, time zone 0 (UTC), data format 0 or 2 (see below) and 33 * baud rate 9600. If the clock is to used as the source for the IRIG 34 * Audio Decoder (refclock_irig.c in this distribution), it should be 35 * configured for AM IRIG output and IRIG format 1 (IRIG B with 36 * signature control). The GPS clock can be configured either to respond 37 * to a 'T' poll character or left running continuously. 38 * 39 * There are two timecode formats used by these clocks. Format 0, which 40 * is available with both the Netclock/2 and 8170, and format 2, which 41 * is available only with the Netclock/2, specially modified 8170 and 42 * GPS. 43 * 44 * Format 0 (22 ASCII printing characters): 45 * 46 * <cr><lf>i ddd hh:mm:ss TZ=zz<cr><lf> 47 * 48 * on-time = first <cr> 49 * hh:mm:ss = hours, minutes, seconds 50 * i = synchronization flag (' ' = in synch, '?' = out of synch) 51 * 52 * The alarm condition is indicated by other than ' ' at a, which occurs 53 * during initial synchronization and when received signal is lost for 54 * about ten hours. 55 * 56 * Format 2 (24 ASCII printing characters): 57 * 58 * <cr><lf>iqyy ddd hh:mm:ss.fff ld 59 * 60 * on-time = <cr> 61 * i = synchronization flag (' ' = in synch, '?' = out of synch) 62 * q = quality indicator (' ' = locked, 'A'...'D' = unlocked) 63 * yy = year (as broadcast) 64 * ddd = day of year 65 * hh:mm:ss.fff = hours, minutes, seconds, milliseconds 66 * 67 * The alarm condition is indicated by other than ' ' at a, which occurs 68 * during initial synchronization and when received signal is lost for 69 * about ten hours. The unlock condition is indicated by other than ' ' 70 * at q. 71 * 72 * The q is normally ' ' when the time error is less than 1 ms and a 73 * character in the set 'A'...'D' when the time error is less than 10, 74 * 100, 500 and greater than 500 ms respectively. The l is normally ' ', 75 * but is set to 'L' early in the month of an upcoming UTC leap second 76 * and reset to ' ' on the first day of the following month. The d is 77 * set to 'S' for standard time 'I' on the day preceding a switch to 78 * daylight time, 'D' for daylight time and 'O' on the day preceding a 79 * switch to standard time. The start bit of the first <cr> is 80 * synchronized to the indicated time as returned. 81 * 82 * This driver does not need to be told which format is in use - it 83 * figures out which one from the length of the message. The driver 84 * makes no attempt to correct for the intrinsic jitter of the radio 85 * itself, which is a known problem with the older radios. 86 * 87 * Fudge Factors 88 * 89 * This driver can retrieve a table of quality data maintained 90 * internally by the Netclock/2 clock. If flag4 of the fudge 91 * configuration command is set to 1, the driver will retrieve this 92 * table and write it to the clockstats file when the first timecode 93 * message of a new day is received. 94 * 95 * PPS calibration fudge time 1: format 0 .003134, format 2 .004034 96 */ 97 /* 98 * Interface definitions 99 */ 100 #define DEVICE "/dev/wwvb%d" /* device name and unit */ 101 #define SPEED232 B9600 /* uart speed (9600 baud) */ 102 #define PRECISION (-13) /* precision assumed (about 100 us) */ 103 #define REFID "WWVB" /* reference ID */ 104 #define DESCRIPTION "Spectracom WWVB/GPS Receiver" /* WRU */ 105 106 #define LENWWVB0 22 /* format 0 timecode length */ 107 #define LENWWVB1 22 /* format 1 timecode length */ 108 #define LENWWVB2 24 /* format 2 timecode length */ 109 #define LENWWVB3 29 /* format 3 timecode length */ 110 #define MONLIN 15 /* number of monitoring lines */ 111 112 /* 113 * WWVB unit control structure 114 */ 115 struct wwvbunit { 116 l_fp laststamp; /* last receive timestamp */ 117 u_char lasthour; /* last hour (for monitor) */ 118 u_char linect; /* count ignored lines (for monitor */ 119 }; 120 121 /* 122 * Function prototypes 123 */ 124 static int wwvb_start P((int, struct peer *)); 125 static void wwvb_shutdown P((int, struct peer *)); 126 static void wwvb_receive P((struct recvbuf *)); 127 static void wwvb_poll P((int, struct peer *)); 128 static void wwvb_timer P((int, struct peer *)); 129 130 /* 131 * Transfer vector 132 */ 133 struct refclock refclock_wwvb = { 134 wwvb_start, /* start up driver */ 135 wwvb_shutdown, /* shut down driver */ 136 wwvb_poll, /* transmit poll message */ 137 noentry, /* not used (old wwvb_control) */ 138 noentry, /* initialize driver (not used) */ 139 noentry, /* not used (old wwvb_buginfo) */ 140 wwvb_timer /* called once per second */ 141 }; 142 143 144 /* 145 * wwvb_start - open the devices and initialize data for processing 146 */ 147 static int 148 wwvb_start( 149 int unit, 150 struct peer *peer 151 ) 152 { 153 register struct wwvbunit *up; 154 struct refclockproc *pp; 155 int fd; 156 char device[20]; 157 158 /* 159 * Open serial port. Use CLK line discipline, if available. 160 */ 161 sprintf(device, DEVICE, unit); 162 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 163 return (0); 164 165 /* 166 * Allocate and initialize unit structure 167 */ 168 if (!(up = (struct wwvbunit *) 169 emalloc(sizeof(struct wwvbunit)))) { 170 close(fd); 171 return (0); 172 } 173 memset((char *)up, 0, sizeof(struct wwvbunit)); 174 pp = peer->procptr; 175 pp->unitptr = (caddr_t)up; 176 pp->io.clock_recv = wwvb_receive; 177 pp->io.srcclock = (caddr_t)peer; 178 pp->io.datalen = 0; 179 pp->io.fd = fd; 180 if (!io_addclock(&pp->io)) { 181 close(fd); 182 free(up); 183 return (0); 184 } 185 186 /* 187 * Initialize miscellaneous variables 188 */ 189 peer->precision = PRECISION; 190 pp->clockdesc = DESCRIPTION; 191 memcpy((char *)&pp->refid, REFID, 4); 192 return (1); 193 } 194 195 196 /* 197 * wwvb_shutdown - shut down the clock 198 */ 199 static void 200 wwvb_shutdown( 201 int unit, 202 struct peer *peer 203 ) 204 { 205 register struct wwvbunit *up; 206 struct refclockproc *pp; 207 208 pp = peer->procptr; 209 up = (struct wwvbunit *)pp->unitptr; 210 io_closeclock(&pp->io); 211 free(up); 212 } 213 214 215 /* 216 * wwvb_receive - receive data from the serial interface 217 */ 218 static void 219 wwvb_receive( 220 struct recvbuf *rbufp 221 ) 222 { 223 struct wwvbunit *up; 224 struct refclockproc *pp; 225 struct peer *peer; 226 227 l_fp trtmp; /* arrival timestamp */ 228 int tz; /* time zone */ 229 int day, month; /* ddd conversion */ 230 int temp; /* int temp */ 231 char syncchar; /* synchronization indicator */ 232 char qualchar; /* quality indicator */ 233 char leapchar; /* leap indicator */ 234 char dstchar; /* daylight/standard indicator */ 235 char tmpchar; /* trashbin */ 236 237 /* 238 * Initialize pointers and read the timecode and timestamp 239 */ 240 peer = (struct peer *)rbufp->recv_srcclock; 241 pp = peer->procptr; 242 up = (struct wwvbunit *)pp->unitptr; 243 temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 244 245 /* 246 * Note we get a buffer and timestamp for both a <cr> and <lf>, 247 * but only the <cr> timestamp is retained. Note: in format 0 on 248 * a Netclock/2 or upgraded 8170 the start bit is delayed 100 249 * +-50 us relative to the pps; however, on an unmodified 8170 250 * the start bit can be delayed up to 10 ms. In format 2 the 251 * reading precision is only to the millisecond. Thus, unless 252 * you have a PPS gadget and don't have to have the year, format 253 * 0 provides the lowest jitter. 254 */ 255 if (temp == 0) { 256 up->laststamp = trtmp; 257 return; 258 } 259 pp->lencode = temp; 260 pp->lastrec = up->laststamp; 261 262 /* 263 * We get down to business, check the timecode format and decode 264 * its contents. This code uses the timecode length to determine 265 * format 0, 2 or 3. If the timecode has invalid length or is 266 * not in proper format, we declare bad format and exit. 267 */ 268 syncchar = qualchar = leapchar = dstchar = ' '; 269 tz = 0; 270 switch (pp->lencode) { 271 272 case LENWWVB0: 273 274 /* 275 * Timecode format 0: "I ddd hh:mm:ss DTZ=nn" 276 */ 277 if (sscanf(pp->a_lastcode, 278 "%c %3d %2d:%2d:%2d%c%cTZ=%2d", 279 &syncchar, &pp->day, &pp->hour, &pp->minute, 280 &pp->second, &tmpchar, &dstchar, &tz) == 8) 281 pp->nsec = 0; 282 break; 283 284 case LENWWVB2: 285 286 /* 287 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */ 288 if (sscanf(pp->a_lastcode, 289 "%c%c %2d %3d %2d:%2d:%2d.%3ld %c", 290 &syncchar, &qualchar, &pp->year, &pp->day, 291 &pp->hour, &pp->minute, &pp->second, &pp->nsec, 292 &leapchar) == 9) 293 pp->nsec *= 1000000; 294 break; 295 296 case LENWWVB3: 297 298 /* 299 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#" 300 */ 301 if (sscanf(pp->a_lastcode, 302 "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c", 303 &syncchar, &pp->year, &month, &day, &pp->hour, 304 &pp->minute, &pp->second, &dstchar, &leapchar) == 8) 305 { 306 pp->day = ymd2yd(pp->year, month, day); 307 pp->nsec = 0; 308 break; 309 } 310 311 default: 312 313 /* 314 * Unknown format: If dumping internal table, record 315 * stats; otherwise, declare bad format. 316 */ 317 if (up->linect > 0) { 318 up->linect--; 319 record_clock_stats(&peer->srcadr, 320 pp->a_lastcode); 321 } else { 322 refclock_report(peer, CEVNT_BADREPLY); 323 } 324 return; 325 } 326 327 /* 328 * Decode synchronization, quality and leap characters. If 329 * unsynchronized, set the leap bits accordingly and exit. 330 * Otherwise, set the leap bits according to the leap character. 331 * Once synchronized, the dispersion depends only on the 332 * quality character. 333 */ 334 switch (qualchar) { 335 336 case ' ': 337 pp->disp = .001; 338 pp->lastref = pp->lastrec; 339 break; 340 341 case 'A': 342 pp->disp = .01; 343 break; 344 345 case 'B': 346 pp->disp = .1; 347 break; 348 349 case 'C': 350 pp->disp = .5; 351 break; 352 353 case 'D': 354 pp->disp = MAXDISPERSE; 355 break; 356 357 default: 358 pp->disp = MAXDISPERSE; 359 refclock_report(peer, CEVNT_BADREPLY); 360 break; 361 } 362 if (syncchar != ' ') 363 pp->leap = LEAP_NOTINSYNC; 364 else if (leapchar == 'L') 365 pp->leap = LEAP_ADDSECOND; 366 else 367 pp->leap = LEAP_NOWARNING; 368 369 /* 370 * Process the new sample in the median filter and determine the 371 * timecode timestamp. 372 */ 373 if (!refclock_process(pp)) 374 refclock_report(peer, CEVNT_BADTIME); 375 if (peer->disp > MAXDISTANCE) 376 refclock_receive(peer); 377 } 378 379 380 /* 381 * wwvb_timer - called once per second by the transmit procedure 382 */ 383 static void 384 wwvb_timer( 385 int unit, 386 struct peer *peer 387 ) 388 { 389 register struct wwvbunit *up; 390 struct refclockproc *pp; 391 char pollchar; /* character sent to clock */ 392 393 /* 394 * Time to poll the clock. The Spectracom clock responds to a 395 * 'T' by returning a timecode in the format(s) specified above. 396 * Note there is no checking on state, since this may not be the 397 * only customer reading the clock. Only one customer need poll 398 * the clock; all others just listen in. 399 */ 400 pp = peer->procptr; 401 up = (struct wwvbunit *)pp->unitptr; 402 if (up->linect > 0) 403 pollchar = 'R'; 404 else 405 pollchar = 'T'; 406 if (write(pp->io.fd, &pollchar, 1) != 1) 407 refclock_report(peer, CEVNT_FAULT); 408 } 409 410 411 /* 412 * wwvb_poll - called by the transmit procedure 413 */ 414 static void 415 wwvb_poll( 416 int unit, 417 struct peer *peer 418 ) 419 { 420 register struct wwvbunit *up; 421 struct refclockproc *pp; 422 423 /* 424 * Sweep up the samples received since the last poll. If none 425 * are received, declare a timeout and keep going. 426 */ 427 pp = peer->procptr; 428 up = (struct wwvbunit *)pp->unitptr; 429 pp->polls++; 430 431 /* 432 * If the monitor flag is set (flag4), we dump the internal 433 * quality table at the first timecode beginning the day. 434 */ 435 if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour < 436 (int)up->lasthour) 437 up->linect = MONLIN; 438 up->lasthour = pp->hour; 439 440 /* 441 * Process median filter samples. If none received, declare a 442 * timeout and keep going. 443 */ 444 if (pp->coderecv == pp->codeproc) { 445 refclock_report(peer, CEVNT_TIMEOUT); 446 return; 447 } 448 refclock_receive(peer); 449 record_clock_stats(&peer->srcadr, pp->a_lastcode); 450 #ifdef DEBUG 451 if (debug) 452 printf("wwvb: timecode %d %s\n", pp->lencode, 453 pp->a_lastcode); 454 #endif 455 } 456 457 #else 458 int refclock_wwvb_bs; 459 #endif /* REFCLOCK */ 460