Coverage Report

Created: 2023-05-19 06:16

/src/ntp-dev/ntpd/refclock_hpgps.c
Line
Count
Source (jump to first uncovered line)
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
0
#define SPEED232  B9600  /* uart speed (9600 baud) */
95
0
#define SPEED232Z B19200  /* uart speed (19200 baud) */
96
0
#define PRECISION (-10)  /* precision assumed (about 1 ms) */
97
0
#define REFID   "GPS\0"  /*  reference ID */
98
0
#define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver" 
99
100
0
#define SMAX            23*80+1 /* for :SYSTEM:PRINT? status screen response */
101
102
0
#define MTZONE          2       /* number of fields in timezone reply */
103
0
#define MTCODET2        12      /* number of fields in timecode format T2 */
104
0
#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 (int, struct peer *);
129
static  void  hpgps_shutdown  (int, struct peer *);
130
static  void  hpgps_receive (struct recvbuf *);
131
static  void  hpgps_poll  (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
0
{
156
0
  register struct hpgpsunit *up;
157
0
  struct refclockproc *pp;
158
0
  int fd;
159
0
  int speed, ldisc;
160
0
  char device[20];
161
162
  /*
163
   * Open serial port. Use CLK line discipline, if available.
164
   * Default is HP 58503A, mode arg selects HP Z3801A
165
   */
166
0
  snprintf(device, sizeof(device), DEVICE, unit);
167
0
  ldisc = LDISC_CLK;
168
0
  speed = SPEED232;
169
  /* mode parameter to server config line shares ttl slot */
170
0
  if (1 == peer->ttl) {
171
0
    ldisc |= LDISC_7O1;
172
0
    speed = SPEED232Z;
173
0
  }
174
0
  fd = refclock_open(device, speed, ldisc);
175
0
  if (fd <= 0)
176
0
    return (0);
177
  /*
178
   * Allocate and initialize unit structure
179
   */
180
0
  up = emalloc_zero(sizeof(*up));
181
0
  pp = peer->procptr;
182
0
  pp->io.clock_recv = hpgps_receive;
183
0
  pp->io.srcclock = peer;
184
0
  pp->io.datalen = 0;
185
0
  pp->io.fd = fd;
186
0
  if (!io_addclock(&pp->io)) {
187
0
    close(fd);
188
0
    pp->io.fd = -1;
189
0
    free(up);
190
0
    return (0);
191
0
  }
192
0
  pp->unitptr = up;
193
194
  /*
195
   * Initialize miscellaneous variables
196
   */
197
0
  peer->precision = PRECISION;
198
0
  pp->clockdesc = DESCRIPTION;
199
0
  memcpy((char *)&pp->refid, REFID, 4);
200
0
  up->tzhour = 0;
201
0
  up->tzminute = 0;
202
203
0
  *up->statscrn = '\0';
204
0
  up->lastptr = up->statscrn;
205
0
  up->pollcnt = 2;
206
207
  /*
208
   * Get the identifier string, which is logged but otherwise ignored,
209
   * and get the local timezone information
210
   */
211
0
  up->linecnt = 1;
212
0
  if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
213
0
      refclock_report(peer, CEVNT_FAULT);
214
215
0
  return (1);
216
0
}
217
218
219
/*
220
 * hpgps_shutdown - shut down the clock
221
 */
222
static void
223
hpgps_shutdown(
224
  int unit,
225
  struct peer *peer
226
  )
227
0
{
228
0
  register struct hpgpsunit *up;
229
0
  struct refclockproc *pp;
230
231
0
  pp = peer->procptr;
232
0
  up = pp->unitptr;
233
0
  if (-1 != pp->io.fd)
234
0
    io_closeclock(&pp->io);
235
0
  if (NULL != up)
236
0
    free(up);
237
0
}
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
0
{
248
0
  register struct hpgpsunit *up;
249
0
  struct refclockproc *pp;
250
0
  struct peer *peer;
251
0
  l_fp trtmp;
252
0
  char tcodechar1;        /* identifies timecode format */
253
0
  char tcodechar2;        /* identifies timecode format */
254
0
  char timequal;          /* time figure of merit: 0-9 */
255
0
  char freqqual;          /* frequency figure of merit: 0-3 */
256
0
  char leapchar;          /* leapsecond: + or 0 or - */
257
0
  char servchar;          /* request for service: 0 = no, 1 = yes */
258
0
  char syncchar;          /* time info is invalid: 0 = no, 1 = yes */
259
0
  short expectedsm;       /* expected timecode byte checksum */
260
0
  short tcodechksm;       /* computed timecode byte checksum */
261
0
  int i,m,n;
262
0
  int month, day, lastday;
263
0
  char *tcp;              /* timecode pointer (skips over the prompt) */
264
0
  char prompt[BMAX];      /* prompt in response from receiver */
265
266
  /*
267
   * Initialize pointers and read the receiver response
268
   */
269
0
  peer = rbufp->recv_peer;
270
0
  pp = peer->procptr;
271
0
  up = pp->unitptr;
272
0
  *pp->a_lastcode = '\0';
273
0
  pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
274
275
0
#ifdef DEBUG
276
0
  if (debug)
277
0
      printf("hpgps: lencode: %d timecode:%s\n",
278
0
       pp->lencode, pp->a_lastcode);
279
0
#endif
280
281
  /*
282
   * If there's no characters in the reply, we can quit now
283
   */
284
0
  if (pp->lencode == 0)
285
0
      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
0
  if (up->linecnt-- > 0) {
302
0
    if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
303
0
      *up->lastptr++ = '\n';
304
0
      memcpy(up->lastptr, pp->a_lastcode, pp->lencode);
305
0
      up->lastptr += pp->lencode;
306
0
    }
307
0
    if (up->linecnt == 0) 
308
0
        record_clock_stats(&peer->srcadr, up->statscrn);
309
               
310
0
    return;
311
0
  }
312
313
0
  record_clock_stats(&peer->srcadr, pp->a_lastcode);
314
0
  pp->lastrec = trtmp;
315
            
316
0
  up->lastptr = up->statscrn;
317
0
  *up->lastptr = '\0';
318
0
  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
0
  strlcpy(prompt, pp->a_lastcode, sizeof(prompt));
338
0
  tcp = strrchr(pp->a_lastcode,'>');
339
0
  if (tcp == NULL)
340
0
      tcp = pp->a_lastcode; 
341
0
  else
342
0
      tcp++;
343
0
  prompt[tcp - pp->a_lastcode] = '\0';
344
0
  while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
345
346
  /*
347
   * deal with an error indication in the prompt here
348
   */
349
0
  if (strrchr(prompt,'E') > strrchr(prompt,'s')){
350
0
#ifdef DEBUG
351
0
    if (debug)
352
0
        printf("hpgps: error indicated in prompt: %s\n", prompt);
353
0
#endif
354
0
    if (write(pp->io.fd, "*CLS\r\r", 6) != 6)
355
0
        refclock_report(peer, CEVNT_FAULT);
356
0
  }
357
358
  /*
359
   * make sure we got a timezone or timecode format and 
360
   * then process accordingly
361
   */
362
0
  m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
363
364
0
  if (m != 2){
365
0
#ifdef DEBUG
366
0
    if (debug)
367
0
        printf("hpgps: no format indicator\n");
368
0
#endif
369
0
    refclock_report(peer, CEVNT_BADREPLY);
370
0
    return;
371
0
  }
372
373
0
  switch (tcodechar1) {
374
375
0
      case '+':
376
0
      case '-':
377
0
    m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
378
0
    if (m != MTZONE) {
379
0
#ifdef DEBUG
380
0
      if (debug)
381
0
          printf("hpgps: only %d fields recognized in timezone\n", m);
382
0
#endif
383
0
      refclock_report(peer, CEVNT_BADREPLY);
384
0
      return;
385
0
    }
386
0
    if ((up->tzhour < -12) || (up->tzhour > 13) || 
387
0
        (up->tzminute < -59) || (up->tzminute > 59)){
388
0
#ifdef DEBUG
389
0
      if (debug)
390
0
          printf("hpgps: timezone %d, %d out of range\n",
391
0
           up->tzhour, up->tzminute);
392
0
#endif
393
0
      refclock_report(peer, CEVNT_BADREPLY);
394
0
      return;
395
0
    }
396
0
    return;
397
398
0
      case 'T':
399
0
    break;
400
401
0
      default:
402
0
#ifdef DEBUG
403
0
    if (debug)
404
0
        printf("hpgps: unrecognized reply format %c%c\n",
405
0
         tcodechar1, tcodechar2);
406
0
#endif
407
0
    refclock_report(peer, CEVNT_BADREPLY);
408
0
    return;
409
0
  } /* end of tcodechar1 switch */
410
411
412
0
  switch (tcodechar2) {
413
414
0
      case '2':
415
0
    m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
416
0
         &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
417
0
         &timequal, &freqqual, &leapchar, &servchar, &syncchar,
418
0
         &expectedsm);
419
0
    n = NTCODET2;
420
421
0
    if (m != MTCODET2){
422
0
#ifdef DEBUG
423
0
      if (debug)
424
0
          printf("hpgps: only %d fields recognized in timecode\n", m);
425
0
#endif
426
0
      refclock_report(peer, CEVNT_BADREPLY);
427
0
      return;
428
0
    }
429
0
    break;
430
431
0
      default:
432
0
#ifdef DEBUG
433
0
    if (debug)
434
0
        printf("hpgps: unrecognized timecode format %c%c\n",
435
0
         tcodechar1, tcodechar2);
436
0
#endif
437
0
    refclock_report(peer, CEVNT_BADREPLY);
438
0
    return;
439
0
  } /* 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
0
  tcodechksm = 0;
447
0
  while (n-- > 0) tcodechksm += *tcp++;
448
0
  tcodechksm &= 0x00ff;
449
450
0
  if (tcodechksm != expectedsm) {
451
0
#ifdef DEBUG
452
0
    if (debug)
453
0
        printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
454
0
         tcodechksm, expectedsm);
455
0
#endif
456
0
    refclock_report(peer, CEVNT_BADREPLY);
457
0
    return;
458
0
  }
459
460
  /* 
461
   * Compute the day of year from the yyyymmdd format.
462
   */
463
0
  if (month < 1 || month > 12 || day < 1) {
464
0
    refclock_report(peer, CEVNT_BADTIME);
465
0
    return;
466
0
  }
467
468
0
  if ( ! isleap_4(pp->year) ) {       /* Y2KFixes */
469
    /* not a leap year */
470
0
    if (day > day1tab[month - 1]) {
471
0
      refclock_report(peer, CEVNT_BADTIME);
472
0
      return;
473
0
    }
474
0
    for (i = 0; i < month - 1; i++) day += day1tab[i];
475
0
    lastday = 365;
476
0
  } else {
477
    /* a leap year */
478
0
    if (day > day2tab[month - 1]) {
479
0
      refclock_report(peer, CEVNT_BADTIME);
480
0
      return;
481
0
    }
482
0
    for (i = 0; i < month - 1; i++) day += day2tab[i];
483
0
    lastday = 366;
484
0
  }
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
0
  pp->minute -= up->tzminute;
493
0
  pp->hour -= up->tzhour;
494
495
0
  if (pp->minute < 0) {
496
0
    pp->minute += 60;
497
0
    pp->hour--;
498
0
  }
499
0
  if (pp->minute > 59) {
500
0
    pp->minute -= 60;
501
0
    pp->hour++;
502
0
  }
503
0
  if (pp->hour < 0)  {
504
0
    pp->hour += 24;
505
0
    day--;
506
0
    if (day < 1) {
507
0
      pp->year--;
508
0
      if ( isleap_4(pp->year) )    /* Y2KFixes */
509
0
          day = 366;
510
0
      else
511
0
          day = 365;
512
0
    }
513
0
  }
514
515
0
  if (pp->hour > 23) {
516
0
    pp->hour -= 24;
517
0
    day++;
518
0
    if (day > lastday) {
519
0
      pp->year++;
520
0
      day = 1;
521
0
    }
522
0
  }
523
524
0
  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
0
  if (syncchar != '0') {
532
0
    pp->leap = LEAP_NOTINSYNC;
533
0
  }
534
0
  else {
535
0
    pp->leap = LEAP_NOWARNING;
536
0
    switch (leapchar) {
537
538
0
        case '0':
539
0
      break;
540
                     
541
        /* See http://bugs.ntp.org/1090
542
         * Ignore leap announcements unless June or December.
543
         * Better would be to use :GPSTime? to find the month,
544
         * but that seems too likely to introduce other bugs.
545
         */
546
0
        case '+':
547
0
      if ((month==6) || (month==12))
548
0
          pp->leap = LEAP_ADDSECOND;
549
0
      break;
550
                     
551
0
        case '-':
552
0
      if ((month==6) || (month==12))
553
0
          pp->leap = LEAP_DELSECOND;
554
0
      break;
555
                     
556
0
        default:
557
0
#ifdef DEBUG
558
0
      if (debug)
559
0
          printf("hpgps: unrecognized leap indicator: %c\n",
560
0
           leapchar);
561
0
#endif
562
0
      refclock_report(peer, CEVNT_BADTIME);
563
0
      return;
564
0
    } /* end of leapchar switch */
565
0
  }
566
567
  /*
568
   * Process the new sample in the median filter and determine the
569
   * reference clock offset and dispersion. We use lastrec as both
570
   * the reference time and receive time in order to avoid being
571
   * cute, like setting the reference time later than the receive
572
   * time, which may cause a paranoid protocol module to chuck out
573
   * the data.
574
   */
575
0
  if (!refclock_process(pp)) {
576
0
    refclock_report(peer, CEVNT_BADTIME);
577
0
    return;
578
0
  }
579
0
  pp->lastref = pp->lastrec;
580
0
  refclock_receive(peer);
581
582
  /*
583
   * If CLK_FLAG4 is set, ask for the status screen response.
584
   */
585
0
  if (pp->sloppyclockflag & CLK_FLAG4){
586
0
    up->linecnt = 22; 
587
0
    if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15)
588
0
        refclock_report(peer, CEVNT_FAULT);
589
0
  }
590
0
}
591
592
593
/*
594
 * hpgps_poll - called by the transmit procedure
595
 */
596
static void
597
hpgps_poll(
598
  int unit,
599
  struct peer *peer
600
  )
601
0
{
602
0
  register struct hpgpsunit *up;
603
0
  struct refclockproc *pp;
604
605
  /*
606
   * Time to poll the clock. The HP 58503A responds to a
607
   * ":PTIME:TCODE?" by returning a timecode in the format specified
608
   * above. If nothing is heard from the clock for two polls,
609
   * declare a timeout and keep going.
610
   */
611
0
  pp = peer->procptr;
612
0
  up = pp->unitptr;
613
0
  if (up->pollcnt == 0)
614
0
      refclock_report(peer, CEVNT_TIMEOUT);
615
0
  else
616
0
      up->pollcnt--;
617
0
  if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) {
618
0
    refclock_report(peer, CEVNT_FAULT);
619
0
  }
620
0
  else
621
0
      pp->polls++;
622
0
}
623
624
#else
625
int refclock_hpgps_bs;
626
#endif /* REFCLOCK */