xref: /netbsd-src/external/bsd/ntp/dist/ntpd/refclock_hpgps.c (revision f003fb54cd3f3f497191fbda0736ce70253305eb)
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