1 /* $NetBSD: refclock_hpgps.c,v 1.1.1.2 2012/01/31 21:25:51 kardel Exp $ */ 2 3 /* 4 * refclock_hpgps - clock driver for HP 58503A GPS receiver 5 */ 6 7 #ifdef HAVE_CONFIG_H 8 # include <config.h> 9 #endif 10 11 #if defined(REFCLOCK) && defined(CLOCK_HPGPS) 12 13 #include "ntpd.h" 14 #include "ntp_io.h" 15 #include "ntp_refclock.h" 16 #include "ntp_stdlib.h" 17 18 #include <stdio.h> 19 #include <ctype.h> 20 21 /* Version 0.1 April 1, 1995 22 * 0.2 April 25, 1995 23 * tolerant of missing timecode response prompt and sends 24 * clear status if prompt indicates error; 25 * can use either local time or UTC from receiver; 26 * can get receiver status screen via flag4 27 * 28 * WARNING!: This driver is UNDER CONSTRUCTION 29 * Everything in here should be treated with suspicion. 30 * If it looks wrong, it probably is. 31 * 32 * Comments and/or questions to: Dave Vitanye 33 * Hewlett Packard Company 34 * dave@scd.hp.com 35 * (408) 553-2856 36 * 37 * Thanks to the author of the PST driver, which was the starting point for 38 * this one. 39 * 40 * This driver supports the HP 58503A Time and Frequency Reference Receiver. 41 * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver. 42 * The receiver accuracy when locked to GPS in normal operation is better 43 * than 1 usec. The accuracy when operating in holdover is typically better 44 * than 10 usec. per day. 45 * 46 * The same driver also handles the HP Z3801A which is available surplus 47 * from the cell phone industry. It's popular with hams. 48 * It needs a different line setup: 19200 baud, 7 data bits, odd parity 49 * That is selected by adding "mode 1" to the server line in ntp.conf 50 * HP Z3801A code from Jeff Mock added by Hal Murray, Sep 2005 51 * 52 * 53 * The receiver should be operated with factory default settings. 54 * Initial driver operation: expects the receiver to be already locked 55 * to GPS, configured and able to output timecode format 2 messages. 56 * 57 * The driver uses the poll sequence :PTIME:TCODE? to get a response from 58 * the receiver. The receiver responds with a timecode string of ASCII 59 * printing characters, followed by a <cr><lf>, followed by a prompt string 60 * issued by the receiver, in the following format: 61 * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi > 62 * 63 * The driver processes the response at the <cr> and <lf>, so what the 64 * driver sees is the prompt from the previous poll, followed by this 65 * timecode. The prompt from the current poll is (usually) left unread until 66 * the next poll. So (except on the very first poll) the driver sees this: 67 * 68 * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf> 69 * 70 * The T is the on-time character, at 980 msec. before the next 1PPS edge. 71 * The # is the timecode format type. We look for format 2. 72 * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp 73 * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps, 74 * so the first approximation for fudge time1 is nominally -0.955 seconds. 75 * This number probably needs adjusting for each machine / OS type, so far: 76 * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05 77 * -0.953175 on an HP 9000 Model 370 HP-UX 9.10 78 * 79 * This receiver also provides a 1PPS signal, but I haven't figured out 80 * how to deal with any of the CLK or PPS stuff yet. Stay tuned. 81 * 82 */ 83 84 /* 85 * Fudge Factors 86 * 87 * Fudge time1 is used to accomodate the timecode serial interface adjustment. 88 * Fudge flag4 can be set to request a receiver status screen summary, which 89 * is recorded in the clockstats file. 90 */ 91 92 /* 93 * Interface definitions 94 */ 95 #define DEVICE "/dev/hpgps%d" /* device name and unit */ 96 #define SPEED232 B9600 /* uart speed (9600 baud) */ 97 #define SPEED232Z B19200 /* uart speed (19200 baud) */ 98 #define PRECISION (-10) /* precision assumed (about 1 ms) */ 99 #define REFID "GPS\0" /* reference ID */ 100 #define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver" 101 102 #define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */ 103 104 #define MTZONE 2 /* number of fields in timezone reply */ 105 #define MTCODET2 12 /* number of fields in timecode format T2 */ 106 #define NTCODET2 21 /* number of chars to checksum in format T2 */ 107 108 /* 109 * Tables to compute the day of year from yyyymmdd timecode. 110 * Viva la leap. 111 */ 112 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 113 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 114 115 /* 116 * Unit control structure 117 */ 118 struct hpgpsunit { 119 int pollcnt; /* poll message counter */ 120 int tzhour; /* timezone offset, hours */ 121 int tzminute; /* timezone offset, minutes */ 122 int linecnt; /* set for expected multiple line responses */ 123 char *lastptr; /* pointer to receiver response data */ 124 char statscrn[SMAX]; /* receiver status screen buffer */ 125 }; 126 127 /* 128 * Function prototypes 129 */ 130 static int hpgps_start (int, struct peer *); 131 static void hpgps_shutdown (int, struct peer *); 132 static void hpgps_receive (struct recvbuf *); 133 static void hpgps_poll (int, struct peer *); 134 135 /* 136 * Transfer vector 137 */ 138 struct refclock refclock_hpgps = { 139 hpgps_start, /* start up driver */ 140 hpgps_shutdown, /* shut down driver */ 141 hpgps_poll, /* transmit poll message */ 142 noentry, /* not used (old hpgps_control) */ 143 noentry, /* initialize driver */ 144 noentry, /* not used (old hpgps_buginfo) */ 145 NOFLAGS /* not used */ 146 }; 147 148 149 /* 150 * hpgps_start - open the devices and initialize data for processing 151 */ 152 static int 153 hpgps_start( 154 int unit, 155 struct peer *peer 156 ) 157 { 158 register struct hpgpsunit *up; 159 struct refclockproc *pp; 160 int fd; 161 char device[20]; 162 163 /* 164 * Open serial port. Use CLK line discipline, if available. 165 * Default is HP 58503A, mode arg selects HP Z3801A 166 */ 167 snprintf(device, sizeof(device), DEVICE, unit); 168 /* mode parameter to server config line shares ttl slot */ 169 if ((peer->ttl == 1)) { 170 if (!(fd = refclock_open(device, SPEED232Z, 171 LDISC_CLK | LDISC_7O1))) 172 return (0); 173 } else { 174 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 175 return (0); 176 } 177 /* 178 * Allocate and initialize unit structure 179 */ 180 up = emalloc(sizeof(*up)); 181 memset(up, 0, sizeof(*up)); 182 pp = peer->procptr; 183 pp->io.clock_recv = hpgps_receive; 184 pp->io.srcclock = (caddr_t)peer; 185 pp->io.datalen = 0; 186 pp->io.fd = fd; 187 if (!io_addclock(&pp->io)) { 188 close(fd); 189 pp->io.fd = -1; 190 free(up); 191 return (0); 192 } 193 pp->unitptr = (caddr_t)up; 194 195 /* 196 * Initialize miscellaneous variables 197 */ 198 peer->precision = PRECISION; 199 pp->clockdesc = DESCRIPTION; 200 memcpy((char *)&pp->refid, REFID, 4); 201 up->tzhour = 0; 202 up->tzminute = 0; 203 204 *up->statscrn = '\0'; 205 up->lastptr = up->statscrn; 206 up->pollcnt = 2; 207 208 /* 209 * Get the identifier string, which is logged but otherwise ignored, 210 * and get the local timezone information 211 */ 212 up->linecnt = 1; 213 if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20) 214 refclock_report(peer, CEVNT_FAULT); 215 216 return (1); 217 } 218 219 220 /* 221 * hpgps_shutdown - shut down the clock 222 */ 223 static void 224 hpgps_shutdown( 225 int unit, 226 struct peer *peer 227 ) 228 { 229 register struct hpgpsunit *up; 230 struct refclockproc *pp; 231 232 pp = peer->procptr; 233 up = (struct hpgpsunit *)pp->unitptr; 234 if (-1 != pp->io.fd) 235 io_closeclock(&pp->io); 236 if (NULL != up) 237 free(up); 238 } 239 240 241 /* 242 * hpgps_receive - receive data from the serial interface 243 */ 244 static void 245 hpgps_receive( 246 struct recvbuf *rbufp 247 ) 248 { 249 register struct hpgpsunit *up; 250 struct refclockproc *pp; 251 struct peer *peer; 252 l_fp trtmp; 253 char tcodechar1; /* identifies timecode format */ 254 char tcodechar2; /* identifies timecode format */ 255 char timequal; /* time figure of merit: 0-9 */ 256 char freqqual; /* frequency figure of merit: 0-3 */ 257 char leapchar; /* leapsecond: + or 0 or - */ 258 char servchar; /* request for service: 0 = no, 1 = yes */ 259 char syncchar; /* time info is invalid: 0 = no, 1 = yes */ 260 short expectedsm; /* expected timecode byte checksum */ 261 short tcodechksm; /* computed timecode byte checksum */ 262 int i,m,n; 263 int month, day, lastday; 264 char *tcp; /* timecode pointer (skips over the prompt) */ 265 char prompt[BMAX]; /* prompt in response from receiver */ 266 267 /* 268 * Initialize pointers and read the receiver response 269 */ 270 peer = (struct peer *)rbufp->recv_srcclock; 271 pp = peer->procptr; 272 up = (struct hpgpsunit *)pp->unitptr; 273 *pp->a_lastcode = '\0'; 274 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 275 276 #ifdef DEBUG 277 if (debug) 278 printf("hpgps: lencode: %d timecode:%s\n", 279 pp->lencode, pp->a_lastcode); 280 #endif 281 282 /* 283 * If there's no characters in the reply, we can quit now 284 */ 285 if (pp->lencode == 0) 286 return; 287 288 /* 289 * If linecnt is greater than zero, we are getting information only, 290 * such as the receiver identification string or the receiver status 291 * screen, so put the receiver response at the end of the status 292 * screen buffer. When we have the last line, write the buffer to 293 * the clockstats file and return without further processing. 294 * 295 * If linecnt is zero, we are expecting either the timezone 296 * or a timecode. At this point, also write the response 297 * to the clockstats file, and go on to process the prompt (if any), 298 * timezone, or timecode and timestamp. 299 */ 300 301 302 if (up->linecnt-- > 0) { 303 if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) { 304 *up->lastptr++ = '\n'; 305 (void)strcpy(up->lastptr, pp->a_lastcode); 306 up->lastptr += pp->lencode; 307 } 308 if (up->linecnt == 0) 309 record_clock_stats(&peer->srcadr, up->statscrn); 310 311 return; 312 } 313 314 record_clock_stats(&peer->srcadr, pp->a_lastcode); 315 pp->lastrec = trtmp; 316 317 up->lastptr = up->statscrn; 318 *up->lastptr = '\0'; 319 up->pollcnt = 2; 320 321 /* 322 * We get down to business: get a prompt if one is there, issue 323 * a clear status command if it contains an error indication. 324 * Next, check for either the timezone reply or the timecode reply 325 * and decode it. If we don't recognize the reply, or don't get the 326 * proper number of decoded fields, or get an out of range timezone, 327 * or if the timecode checksum is bad, then we declare bad format 328 * and exit. 329 * 330 * Timezone format (including nominal prompt): 331 * scpi > -H,-M<cr><lf> 332 * 333 * Timecode format (including nominal prompt): 334 * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf> 335 * 336 */ 337 338 (void)strcpy(prompt,pp->a_lastcode); 339 tcp = strrchr(pp->a_lastcode,'>'); 340 if (tcp == NULL) 341 tcp = pp->a_lastcode; 342 else 343 tcp++; 344 prompt[tcp - pp->a_lastcode] = '\0'; 345 while ((*tcp == ' ') || (*tcp == '\t')) tcp++; 346 347 /* 348 * deal with an error indication in the prompt here 349 */ 350 if (strrchr(prompt,'E') > strrchr(prompt,'s')){ 351 #ifdef DEBUG 352 if (debug) 353 printf("hpgps: error indicated in prompt: %s\n", prompt); 354 #endif 355 if (write(pp->io.fd, "*CLS\r\r", 6) != 6) 356 refclock_report(peer, CEVNT_FAULT); 357 } 358 359 /* 360 * make sure we got a timezone or timecode format and 361 * then process accordingly 362 */ 363 m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2); 364 365 if (m != 2){ 366 #ifdef DEBUG 367 if (debug) 368 printf("hpgps: no format indicator\n"); 369 #endif 370 refclock_report(peer, CEVNT_BADREPLY); 371 return; 372 } 373 374 switch (tcodechar1) { 375 376 case '+': 377 case '-': 378 m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute); 379 if (m != MTZONE) { 380 #ifdef DEBUG 381 if (debug) 382 printf("hpgps: only %d fields recognized in timezone\n", m); 383 #endif 384 refclock_report(peer, CEVNT_BADREPLY); 385 return; 386 } 387 if ((up->tzhour < -12) || (up->tzhour > 13) || 388 (up->tzminute < -59) || (up->tzminute > 59)){ 389 #ifdef DEBUG 390 if (debug) 391 printf("hpgps: timezone %d, %d out of range\n", 392 up->tzhour, up->tzminute); 393 #endif 394 refclock_report(peer, CEVNT_BADREPLY); 395 return; 396 } 397 return; 398 399 case 'T': 400 break; 401 402 default: 403 #ifdef DEBUG 404 if (debug) 405 printf("hpgps: unrecognized reply format %c%c\n", 406 tcodechar1, tcodechar2); 407 #endif 408 refclock_report(peer, CEVNT_BADREPLY); 409 return; 410 } /* end of tcodechar1 switch */ 411 412 413 switch (tcodechar2) { 414 415 case '2': 416 m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx", 417 &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second, 418 &timequal, &freqqual, &leapchar, &servchar, &syncchar, 419 &expectedsm); 420 n = NTCODET2; 421 422 if (m != MTCODET2){ 423 #ifdef DEBUG 424 if (debug) 425 printf("hpgps: only %d fields recognized in timecode\n", m); 426 #endif 427 refclock_report(peer, CEVNT_BADREPLY); 428 return; 429 } 430 break; 431 432 default: 433 #ifdef DEBUG 434 if (debug) 435 printf("hpgps: unrecognized timecode format %c%c\n", 436 tcodechar1, tcodechar2); 437 #endif 438 refclock_report(peer, CEVNT_BADREPLY); 439 return; 440 } /* end of tcodechar2 format switch */ 441 442 /* 443 * Compute and verify the checksum. 444 * Characters are summed starting at tcodechar1, ending at just 445 * before the expected checksum. Bail out if incorrect. 446 */ 447 tcodechksm = 0; 448 while (n-- > 0) tcodechksm += *tcp++; 449 tcodechksm &= 0x00ff; 450 451 if (tcodechksm != expectedsm) { 452 #ifdef DEBUG 453 if (debug) 454 printf("hpgps: checksum %2hX doesn't match %2hX expected\n", 455 tcodechksm, expectedsm); 456 #endif 457 refclock_report(peer, CEVNT_BADREPLY); 458 return; 459 } 460 461 /* 462 * Compute the day of year from the yyyymmdd format. 463 */ 464 if (month < 1 || month > 12 || day < 1) { 465 refclock_report(peer, CEVNT_BADTIME); 466 return; 467 } 468 469 if ( ! isleap_4(pp->year) ) { /* Y2KFixes */ 470 /* not a leap year */ 471 if (day > day1tab[month - 1]) { 472 refclock_report(peer, CEVNT_BADTIME); 473 return; 474 } 475 for (i = 0; i < month - 1; i++) day += day1tab[i]; 476 lastday = 365; 477 } else { 478 /* a leap year */ 479 if (day > day2tab[month - 1]) { 480 refclock_report(peer, CEVNT_BADTIME); 481 return; 482 } 483 for (i = 0; i < month - 1; i++) day += day2tab[i]; 484 lastday = 366; 485 } 486 487 /* 488 * Deal with the timezone offset here. The receiver timecode is in 489 * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values. 490 * For example, Pacific Standard Time is -8 hours , 0 minutes. 491 * Deal with the underflows and overflows. 492 */ 493 pp->minute -= up->tzminute; 494 pp->hour -= up->tzhour; 495 496 if (pp->minute < 0) { 497 pp->minute += 60; 498 pp->hour--; 499 } 500 if (pp->minute > 59) { 501 pp->minute -= 60; 502 pp->hour++; 503 } 504 if (pp->hour < 0) { 505 pp->hour += 24; 506 day--; 507 if (day < 1) { 508 pp->year--; 509 if ( isleap_4(pp->year) ) /* Y2KFixes */ 510 day = 366; 511 else 512 day = 365; 513 } 514 } 515 516 if (pp->hour > 23) { 517 pp->hour -= 24; 518 day++; 519 if (day > lastday) { 520 pp->year++; 521 day = 1; 522 } 523 } 524 525 pp->day = day; 526 527 /* 528 * Decode the MFLRV indicators. 529 * NEED TO FIGURE OUT how to deal with the request for service, 530 * time quality, and frequency quality indicators some day. 531 */ 532 if (syncchar != '0') { 533 pp->leap = LEAP_NOTINSYNC; 534 } 535 else { 536 pp->leap = LEAP_NOWARNING; 537 switch (leapchar) { 538 539 case '0': 540 break; 541 542 /* See http://bugs.ntp.org/1090 543 * Ignore leap announcements unless June or December. 544 * Better would be to use :GPSTime? to find the month, 545 * but that seems too likely to introduce other bugs. 546 */ 547 case '+': 548 if ((month==6) || (month==12)) 549 pp->leap = LEAP_ADDSECOND; 550 break; 551 552 case '-': 553 if ((month==6) || (month==12)) 554 pp->leap = LEAP_DELSECOND; 555 break; 556 557 default: 558 #ifdef DEBUG 559 if (debug) 560 printf("hpgps: unrecognized leap indicator: %c\n", 561 leapchar); 562 #endif 563 refclock_report(peer, CEVNT_BADTIME); 564 return; 565 } /* end of leapchar switch */ 566 } 567 568 /* 569 * Process the new sample in the median filter and determine the 570 * reference clock offset and dispersion. We use lastrec as both 571 * the reference time and receive time in order to avoid being 572 * cute, like setting the reference time later than the receive 573 * time, which may cause a paranoid protocol module to chuck out 574 * the data. 575 */ 576 if (!refclock_process(pp)) { 577 refclock_report(peer, CEVNT_BADTIME); 578 return; 579 } 580 pp->lastref = pp->lastrec; 581 refclock_receive(peer); 582 583 /* 584 * If CLK_FLAG4 is set, ask for the status screen response. 585 */ 586 if (pp->sloppyclockflag & CLK_FLAG4){ 587 up->linecnt = 22; 588 if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15) 589 refclock_report(peer, CEVNT_FAULT); 590 } 591 } 592 593 594 /* 595 * hpgps_poll - called by the transmit procedure 596 */ 597 static void 598 hpgps_poll( 599 int unit, 600 struct peer *peer 601 ) 602 { 603 register struct hpgpsunit *up; 604 struct refclockproc *pp; 605 606 /* 607 * Time to poll the clock. The HP 58503A responds to a 608 * ":PTIME:TCODE?" by returning a timecode in the format specified 609 * above. If nothing is heard from the clock for two polls, 610 * declare a timeout and keep going. 611 */ 612 pp = peer->procptr; 613 up = (struct hpgpsunit *)pp->unitptr; 614 if (up->pollcnt == 0) 615 refclock_report(peer, CEVNT_TIMEOUT); 616 else 617 up->pollcnt--; 618 if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) { 619 refclock_report(peer, CEVNT_FAULT); 620 } 621 else 622 pp->polls++; 623 } 624 625 #else 626 int refclock_hpgps_bs; 627 #endif /* REFCLOCK */ 628