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