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