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 "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 makes 84 * no attempt to correct for the intrinsic jitter of the radio itself, 85 * 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 on when the first timecode 93 * message of a new day is received. 94 */ 95 96 /* 97 * Interface definitions 98 */ 99 #define DEVICE "/dev/wwvb%d" /* device name and unit */ 100 #define SPEED232 B9600 /* uart speed (9600 baud) */ 101 #define PRECISION (-13) /* precision assumed (about 100 us) */ 102 #define REFID "WWVB" /* reference ID */ 103 #define DESCRIPTION "Spectracom WWVB/GPS Receivers" /* WRU */ 104 105 #define LENWWVB0 22 /* format 0 timecode length */ 106 #define LENWWVB2 24 /* format 2 timecode length */ 107 #define LENWWVB3 29 /* format 3 timecode length */ 108 #define MONLIN 15 /* number of monitoring lines */ 109 110 /* 111 * WWVB unit control structure 112 */ 113 struct wwvbunit { 114 u_char tcswitch; /* timecode switch */ 115 l_fp laststamp; /* last receive timestamp */ 116 u_char lasthour; /* last hour (for monitor) */ 117 u_char linect; /* count ignored lines (for monitor */ 118 }; 119 120 /* 121 * Function prototypes 122 */ 123 static int wwvb_start P((int, struct peer *)); 124 static void wwvb_shutdown P((int, struct peer *)); 125 static void wwvb_receive P((struct recvbuf *)); 126 static void wwvb_poll P((int, struct peer *)); 127 128 /* 129 * Transfer vector 130 */ 131 struct refclock refclock_wwvb = { 132 wwvb_start, /* start up driver */ 133 wwvb_shutdown, /* shut down driver */ 134 wwvb_poll, /* transmit poll message */ 135 noentry, /* not used (old wwvb_control) */ 136 noentry, /* initialize driver (not used) */ 137 noentry, /* not used (old wwvb_buginfo) */ 138 NOFLAGS /* not used */ 139 }; 140 141 142 /* 143 * wwvb_start - open the devices and initialize data for processing 144 */ 145 static int 146 wwvb_start( 147 int unit, 148 struct peer *peer 149 ) 150 { 151 register struct wwvbunit *up; 152 struct refclockproc *pp; 153 int fd; 154 char device[20]; 155 156 /* 157 * Open serial port. Use CLK line discipline, if available. 158 */ 159 (void)sprintf(device, DEVICE, unit); 160 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 161 return (0); 162 163 /* 164 * Allocate and initialize unit structure 165 */ 166 if (!(up = (struct wwvbunit *) 167 emalloc(sizeof(struct wwvbunit)))) { 168 (void) close(fd); 169 return (0); 170 } 171 memset((char *)up, 0, sizeof(struct wwvbunit)); 172 pp = peer->procptr; 173 pp->unitptr = (caddr_t)up; 174 pp->io.clock_recv = wwvb_receive; 175 pp->io.srcclock = (caddr_t)peer; 176 pp->io.datalen = 0; 177 pp->io.fd = fd; 178 if (!io_addclock(&pp->io)) { 179 (void) close(fd); 180 free(up); 181 return (0); 182 } 183 184 /* 185 * Initialize miscellaneous variables 186 */ 187 peer->precision = PRECISION; 188 pp->clockdesc = DESCRIPTION; 189 memcpy((char *)&pp->refid, REFID, 4); 190 peer->burst = NSTAGE; 191 return (1); 192 } 193 194 195 /* 196 * wwvb_shutdown - shut down the clock 197 */ 198 static void 199 wwvb_shutdown( 200 int unit, 201 struct peer *peer 202 ) 203 { 204 register struct wwvbunit *up; 205 struct refclockproc *pp; 206 207 pp = peer->procptr; 208 up = (struct wwvbunit *)pp->unitptr; 209 io_closeclock(&pp->io); 210 free(up); 211 } 212 213 214 /* 215 * wwvb_receive - receive data from the serial interface 216 */ 217 static void 218 wwvb_receive( 219 struct recvbuf *rbufp 220 ) 221 { 222 struct wwvbunit *up; 223 struct refclockproc *pp; 224 struct peer *peer; 225 226 l_fp trtmp; /* arrival timestamp */ 227 int tz; /* time zone */ 228 int day, month; /* ddd conversion */ 229 int temp; /* int temp */ 230 char syncchar; /* synchronization indicator */ 231 char qualchar; /* quality indicator */ 232 char leapchar; /* leap indicator */ 233 char dstchar; /* daylight/standard indicator */ 234 char tmpchar; /* trashbin */ 235 236 /* 237 * Initialize pointers and read the timecode and timestamp 238 */ 239 peer = (struct peer *)rbufp->recv_srcclock; 240 pp = peer->procptr; 241 up = (struct wwvbunit *)pp->unitptr; 242 temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 243 244 /* 245 * Note we get a buffer and timestamp for both a <cr> and <lf>, 246 * but only the <cr> timestamp is retained. Note: in format 0 on 247 * a Netclock/2 or upgraded 8170 the start bit is delayed 100 248 * +-50 us relative to the pps; however, on an unmodified 8170 249 * the start bit can be delayed up to 10 ms. In format 2 the 250 * reading precision is only to the millisecond. Thus, unless 251 * you have a pps gadget and don't have to have the year, format 252 * 0 provides the lowest jitter. 253 */ 254 if (temp == 0) { 255 if (up->tcswitch == 0) { 256 up->tcswitch = 1; 257 up->laststamp = trtmp; 258 } else 259 up->tcswitch = 0; 260 return; 261 } 262 pp->lencode = temp; 263 pp->lastrec = up->laststamp; 264 up->laststamp = trtmp; 265 up->tcswitch = 1; 266 #ifdef DEBUG 267 if (debug) 268 printf("wwvb: timecode %d %s\n", pp->lencode, 269 pp->a_lastcode); 270 #endif 271 272 /* 273 * We get down to business, check the timecode format and decode 274 * its contents. This code uses the timecode length to determine 275 * format 0, 2 or 3. If the timecode has invalid length or is 276 * not in proper format, we declare bad format and exit. 277 */ 278 syncchar = qualchar = leapchar = dstchar = ' '; 279 tz = 0; 280 pp->msec = 0; 281 switch (pp->lencode) { 282 283 case LENWWVB0: 284 285 /* 286 * Timecode format 0: "I ddd hh:mm:ss DTZ=nn" 287 */ 288 if (sscanf(pp->a_lastcode, 289 "%c %3d %2d:%2d:%2d%c%cTZ=%2d", 290 &syncchar, &pp->day, &pp->hour, &pp->minute, 291 &pp->second, &tmpchar, &dstchar, &tz) == 8) 292 break; 293 294 case LENWWVB2: 295 296 /* 297 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */ 298 if (sscanf(pp->a_lastcode, 299 "%c%c %2d %3d %2d:%2d:%2d.%3d %c", 300 &syncchar, &qualchar, &pp->year, &pp->day, 301 &pp->hour, &pp->minute, &pp->second, &pp->msec, 302 &leapchar) == 9) 303 break; 304 305 case LENWWVB3: 306 307 /* 308 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#" 309 */ 310 if (sscanf(pp->a_lastcode, 311 "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c", 312 &syncchar, &pp->year, &month, &day, &pp->hour, 313 &pp->minute, &pp->second, &dstchar, &leapchar) == 8) 314 { 315 pp->day = ymd2yd(pp->year, month, day); 316 break; 317 } 318 319 default: 320 321 /* 322 * Unknown format: If dumping internal table, record 323 * stats; otherwise, declare bad format. 324 */ 325 if (up->linect > 0) { 326 up->linect--; 327 record_clock_stats(&peer->srcadr, 328 pp->a_lastcode); 329 } else { 330 refclock_report(peer, CEVNT_BADREPLY); 331 } 332 return; 333 } 334 335 /* 336 * Decode synchronization, quality and leap characters. If 337 * unsynchronized, set the leap bits accordingly and exit. 338 * Otherwise, set the leap bits according to the leap character. 339 * Once synchronized, the dispersion depends only on the 340 * quality character. 341 */ 342 switch (qualchar) { 343 344 case ' ': 345 pp->disp = .001; 346 break; 347 348 case 'A': 349 pp->disp = .01; 350 break; 351 352 case 'B': 353 pp->disp = .1; 354 break; 355 356 case 'C': 357 pp->disp = .5; 358 break; 359 360 case 'D': 361 pp->disp = MAXDISPERSE; 362 break; 363 364 default: 365 pp->disp = MAXDISPERSE; 366 refclock_report(peer, CEVNT_BADREPLY); 367 break; 368 } 369 if (syncchar != ' ') 370 pp->leap = LEAP_NOTINSYNC; 371 else if (leapchar == 'L') 372 pp->leap = LEAP_ADDSECOND; 373 else 374 pp->leap = LEAP_NOWARNING; 375 376 /* 377 * Process the new sample in the median filter and determine the 378 * timecode timestamp. 379 */ 380 if (!refclock_process(pp)) 381 refclock_report(peer, CEVNT_BADTIME); 382 } 383 384 385 /* 386 * wwvb_poll - called by the transmit procedure 387 */ 388 static void 389 wwvb_poll( 390 int unit, 391 struct peer *peer 392 ) 393 { 394 register struct wwvbunit *up; 395 struct refclockproc *pp; 396 char pollchar; /* character sent to clock */ 397 398 /* 399 * Time to poll the clock. The Spectracom clock responds to a 400 * 'T' by returning a timecode in the format(s) specified above. 401 * Note there is no checking on state, since this may not be the 402 * only customer reading the clock. Only one customer need poll 403 * the clock; all others just listen in. If the clock becomes 404 * unreachable, declare a timeout and keep going. 405 */ 406 pp = peer->procptr; 407 up = (struct wwvbunit *)pp->unitptr; 408 if (up->linect > 0) 409 pollchar = 'R'; 410 else 411 pollchar = 'T'; 412 if (write(pp->io.fd, &pollchar, 1) != 1) 413 refclock_report(peer, CEVNT_FAULT); 414 else 415 pp->polls++; 416 if (peer->burst > 0) 417 return; 418 if (pp->coderecv == pp->codeproc) { 419 refclock_report(peer, CEVNT_TIMEOUT); 420 return; 421 } 422 record_clock_stats(&peer->srcadr, pp->a_lastcode); 423 refclock_receive(peer); 424 peer->burst = NSTAGE; 425 426 /* 427 * If the monitor flag is set (flag4), we dump the internal 428 * quality table at the first timecode beginning the day. 429 */ 430 if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour < 431 (int)up->lasthour) 432 up->linect = MONLIN; 433 up->lasthour = pp->hour; 434 } 435 436 #else 437 int refclock_wwvb_bs; 438 #endif /* REFCLOCK */ 439