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