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 LENWWVB1 22 /* format 1 timecode length */ 107 #define LENWWVB2 24 /* format 2 timecode length */ 108 #define LENWWVB3 29 /* format 3 timecode length */ 109 #define MONLIN 15 /* number of monitoring lines */ 110 111 /* 112 * WWVB unit control structure 113 */ 114 struct wwvbunit { 115 u_char tcswitch; /* timecode switch */ 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 129 /* 130 * Transfer vector 131 */ 132 struct refclock refclock_wwvb = { 133 wwvb_start, /* start up driver */ 134 wwvb_shutdown, /* shut down driver */ 135 wwvb_poll, /* transmit poll message */ 136 noentry, /* not used (old wwvb_control) */ 137 noentry, /* initialize driver (not used) */ 138 noentry, /* not used (old wwvb_buginfo) */ 139 NOFLAGS /* not used */ 140 }; 141 142 143 /* 144 * wwvb_start - open the devices and initialize data for processing 145 */ 146 static int 147 wwvb_start( 148 int unit, 149 struct peer *peer 150 ) 151 { 152 register struct wwvbunit *up; 153 struct refclockproc *pp; 154 int fd; 155 char device[20]; 156 157 /* 158 * Open serial port. Use CLK line discipline, if available. 159 */ 160 (void)sprintf(device, DEVICE, unit); 161 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 162 return (0); 163 164 /* 165 * Allocate and initialize unit structure 166 */ 167 if (!(up = (struct wwvbunit *) 168 emalloc(sizeof(struct wwvbunit)))) { 169 (void) close(fd); 170 return (0); 171 } 172 memset((char *)up, 0, sizeof(struct wwvbunit)); 173 pp = peer->procptr; 174 pp->unitptr = (caddr_t)up; 175 pp->io.clock_recv = wwvb_receive; 176 pp->io.srcclock = (caddr_t)peer; 177 pp->io.datalen = 0; 178 pp->io.fd = fd; 179 if (!io_addclock(&pp->io)) { 180 (void) close(fd); 181 free(up); 182 return (0); 183 } 184 185 /* 186 * Initialize miscellaneous variables 187 */ 188 peer->precision = PRECISION; 189 pp->clockdesc = DESCRIPTION; 190 memcpy((char *)&pp->refid, REFID, 4); 191 peer->burst = MAXSTAGE; 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 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 268 /* 269 * We get down to business, check the timecode format and decode 270 * its contents. This code uses the timecode length to determine 271 * format 0, 2 or 3. If the timecode has invalid length or is 272 * not in proper format, we declare bad format and exit. 273 */ 274 syncchar = qualchar = leapchar = dstchar = ' '; 275 tz = 0; 276 switch (pp->lencode) { 277 278 case LENWWVB0: 279 280 /* 281 * Timecode format 0: "I ddd hh:mm:ss DTZ=nn" 282 */ 283 if (sscanf(pp->a_lastcode, 284 "%c %3d %2d:%2d:%2d%c%cTZ=%2d", 285 &syncchar, &pp->day, &pp->hour, &pp->minute, 286 &pp->second, &tmpchar, &dstchar, &tz) == 8) 287 pp->nsec = 0; 288 break; 289 290 case LENWWVB2: 291 292 /* 293 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */ 294 if (sscanf(pp->a_lastcode, 295 "%c%c %2d %3d %2d:%2d:%2d.%3ld %c", 296 &syncchar, &qualchar, &pp->year, &pp->day, 297 &pp->hour, &pp->minute, &pp->second, &pp->nsec, 298 &leapchar) == 9) 299 pp->nsec *= 1000000; 300 break; 301 302 case LENWWVB3: 303 304 /* 305 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#" 306 */ 307 if (sscanf(pp->a_lastcode, 308 "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c", 309 &syncchar, &pp->year, &month, &day, &pp->hour, 310 &pp->minute, &pp->second, &dstchar, &leapchar) == 8) 311 { 312 pp->day = ymd2yd(pp->year, month, day); 313 pp->nsec = 0; 314 break; 315 } 316 317 default: 318 319 /* 320 * Unknown format: If dumping internal table, record 321 * stats; otherwise, declare bad format. 322 */ 323 if (up->linect > 0) { 324 up->linect--; 325 record_clock_stats(&peer->srcadr, 326 pp->a_lastcode); 327 } else { 328 refclock_report(peer, CEVNT_BADREPLY); 329 } 330 return; 331 } 332 333 /* 334 * Decode synchronization, quality and leap characters. If 335 * unsynchronized, set the leap bits accordingly and exit. 336 * Otherwise, set the leap bits according to the leap character. 337 * Once synchronized, the dispersion depends only on the 338 * quality character. 339 */ 340 switch (qualchar) { 341 342 case ' ': 343 pp->disp = .001; 344 pp->lastref = pp->lastrec; 345 break; 346 347 case 'A': 348 pp->disp = .01; 349 break; 350 351 case 'B': 352 pp->disp = .1; 353 break; 354 355 case 'C': 356 pp->disp = .5; 357 break; 358 359 case 'D': 360 pp->disp = MAXDISPERSE; 361 break; 362 363 default: 364 pp->disp = MAXDISPERSE; 365 refclock_report(peer, CEVNT_BADREPLY); 366 break; 367 } 368 if (syncchar != ' ') 369 pp->leap = LEAP_NOTINSYNC; 370 else if (leapchar == 'L') 371 pp->leap = LEAP_ADDSECOND; 372 else 373 pp->leap = LEAP_NOWARNING; 374 375 /* 376 * Process the new sample in the median filter and determine the 377 * timecode timestamp. 378 */ 379 if (!refclock_process(pp)) 380 refclock_report(peer, CEVNT_BADTIME); 381 } 382 383 384 /* 385 * wwvb_poll - called by the transmit procedure 386 */ 387 static void 388 wwvb_poll( 389 int unit, 390 struct peer *peer 391 ) 392 { 393 register struct wwvbunit *up; 394 struct refclockproc *pp; 395 char pollchar; /* character sent to clock */ 396 397 /* 398 * Time to poll the clock. The Spectracom clock responds to a 399 * 'T' by returning a timecode in the format(s) specified above. 400 * Note there is no checking on state, since this may not be the 401 * only customer reading the clock. Only one customer need poll 402 * the clock; all others just listen in. If the clock becomes 403 * unreachable, declare a timeout and keep going. 404 */ 405 pp = peer->procptr; 406 up = (struct wwvbunit *)pp->unitptr; 407 if (up->linect > 0) 408 pollchar = 'R'; 409 else 410 pollchar = 'T'; 411 if (write(pp->io.fd, &pollchar, 1) != 1) 412 refclock_report(peer, CEVNT_FAULT); 413 if (peer->burst > 0) 414 return; 415 if (pp->coderecv == pp->codeproc) { 416 refclock_report(peer, CEVNT_TIMEOUT); 417 return; 418 } 419 refclock_receive(peer); 420 record_clock_stats(&peer->srcadr, pp->a_lastcode); 421 #ifdef DEBUG 422 if (debug) 423 printf("wwvb: timecode %d %s\n", pp->lencode, 424 pp->a_lastcode); 425 #endif 426 peer->burst = MAXSTAGE; 427 pp->polls++; 428 429 /* 430 * If the monitor flag is set (flag4), we dump the internal 431 * quality table at the first timecode beginning the day. 432 */ 433 if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour < 434 (int)up->lasthour) 435 up->linect = MONLIN; 436 up->lasthour = pp->hour; 437 } 438 439 #else 440 int refclock_wwvb_bs; 441 #endif /* REFCLOCK */ 442