Coverage Report

Created: 2023-05-19 06:16

/src/ntp-dev/ntpd/refclock_as2201.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * refclock_as2201 - clock driver for the Austron 2201A GPS
3
 *  Timing Receiver
4
 */
5
#ifdef HAVE_CONFIG_H
6
#include <config.h>
7
#endif
8
9
#if defined(REFCLOCK) && defined(CLOCK_AS2201)
10
11
#include "ntpd.h"
12
#include "ntp_io.h"
13
#include "ntp_refclock.h"
14
#include "ntp_unixtime.h"
15
#include "ntp_stdlib.h"
16
17
#include <stdio.h>
18
#include <ctype.h>
19
20
/*
21
 * This driver supports the Austron 2200A/2201A GPS Receiver with
22
 * Buffered RS-232-C Interface Module. Note that the original 2200/2201
23
 * receivers will not work reliably with this driver, since the older
24
 * design cannot accept input commands at any reasonable data rate.
25
 *
26
 * The program sends a "*toc\r" to the radio and expects a response of
27
 * the form "yy:ddd:hh:mm:ss.mmm\r" where yy = year of century, ddd =
28
 * day of year, hh:mm:ss = second of day and mmm = millisecond of
29
 * second. Then, it sends statistics commands to the radio and expects
30
 * a multi-line reply showing the corresponding statistics or other
31
 * selected data. Statistics commands are sent in order as determined by
32
 * a vector of commands; these might have to be changed with different
33
 * radio options. If flag4 of the fudge configuration command is set to
34
 * 1, the statistics data are written to the clockstats file for later
35
 * processing.
36
 *
37
 * In order for this code to work, the radio must be placed in non-
38
 * interactive mode using the "off" command and with a single <cr>
39
 * response using the "term cr" command. The setting of the "echo"
40
 * and "df" commands does not matter. The radio should select UTC
41
 * timescale using the "ts utc" command.
42
 *
43
 * There are two modes of operation for this driver. The first with
44
 * default configuration is used with stock kernels and serial-line
45
 * drivers and works with almost any machine. In this mode the driver
46
 * assumes the radio captures a timestamp upon receipt of the "*" that
47
 * begins the driver query. Accuracies in this mode are in the order of
48
 * a millisecond or two and the receiver can be connected to only one
49
 * host.
50
 *
51
 * The second mode of operation can be used for SunOS kernels that have
52
 * been modified with the ppsclock streams module included in this
53
 * distribution. The mode is enabled if flag3 of the fudge configuration
54
 * command has been set to 1. In this mode a precise timestamp is
55
 * available using a gadget box and 1-pps signal from the receiver. This
56
 * improves the accuracy to the order of a few tens of microseconds. In
57
 * addition, the serial output and 1-pps signal can be bussed to more
58
 * than one hosts, but only one of them should be connected to the
59
 * radio input data line. 
60
 */
61
62
/*
63
 * GPS Definitions
64
 */
65
0
#define SMAX    200  /* statistics buffer length */
66
#define DEVICE    "/dev/gps%d" /* device name and unit */
67
0
#define SPEED232  B9600  /* uart speed (9600 baud) */
68
0
#define PRECISION (-20)  /* precision assumed (about 1 us) */
69
0
#define REFID   "GPS\0"  /* reference ID */
70
0
#define DESCRIPTION "Austron 2201A GPS Receiver" /* WRU */
71
72
0
#define LENTOC    19  /* yy:ddd:hh:mm:ss.mmm timecode lngth */
73
74
/*
75
 * AS2201 unit control structure.
76
 */
77
struct as2201unit {
78
  char  *lastptr; /* statistics buffer pointer */
79
  char  stats[SMAX];  /* statistics buffer */
80
  int linect;   /* count of lines remaining */
81
  int index;    /* current statistics command */
82
};
83
84
/*
85
 * Radio commands to extract statitistics
86
 *
87
 * A command consists of an ASCII string terminated by a <cr> (\r). The
88
 * command list consist of a sequence of commands terminated by a null
89
 * string ("\0"). One command from the list is sent immediately
90
 * following each received timecode (*toc\r command) and the ASCII
91
 * strings received from the radio are saved along with the timecode in
92
 * the clockstats file. Subsequent commands are sent at each timecode,
93
 * with the last one in the list followed by the first one. The data
94
 * received from the radio consist of ASCII strings, each terminated by
95
 * a <cr> (\r) character. The number of strings for each command is
96
 * specified as the first line of output as an ASCII-encode number. Note
97
 * that the ETF command requires the Input Buffer Module and the LORAN
98
 * commands require the LORAN Assist Module. However, if these modules
99
 * are not installed, the radio and this driver will continue to operate
100
 * successfuly, but no data will be captured for these commands.
101
 */
102
static char stat_command[][30] = {
103
  "ITF\r",    /* internal time/frequency */
104
  "ETF\r",    /* external time/frequency */
105
  "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */
106
  "LORAN TDATA\r",  /* LORAN signal data */
107
  "ID;OPT;VER\r",   /* model; options; software version */
108
109
  "ITF\r",    /* internal time/frequency */
110
  "ETF\r",    /* external time/frequency */
111
  "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */
112
  "TRSTAT\r",   /* satellite tracking status */
113
  "POS;PPS;PPSOFF\r", /* position, pps source, offsets */
114
115
  "ITF\r",    /* internal time/frequency */
116
  "ETF\r",    /* external time/frequency */
117
  "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */
118
  "LORAN TDATA\r",  /* LORAN signal data */
119
  "UTC\r",      /* UTC leap info */
120
121
  "ITF\r",    /* internal time/frequency */
122
  "ETF\r",    /* external time/frequency */
123
  "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */
124
  "TRSTAT\r",   /* satellite tracking status */
125
  "OSC;ET;TEMP\r",  /* osc type; tune volts; oven temp */
126
  "\0"      /* end of table */
127
};
128
129
/*
130
 * Function prototypes
131
 */
132
static  int as2201_start  (int, struct peer *);
133
static  void  as2201_shutdown (int, struct peer *);
134
static  void  as2201_receive  (struct recvbuf *);
135
static  void  as2201_poll (int, struct peer *);
136
137
/*
138
 * Transfer vector
139
 */
140
struct  refclock refclock_as2201 = {
141
  as2201_start,   /* start up driver */
142
  as2201_shutdown,  /* shut down driver */
143
  as2201_poll,    /* transmit poll message */
144
  noentry,    /* not used (old as2201_control) */
145
  noentry,    /* initialize driver (not used) */
146
  noentry,    /* not used (old as2201_buginfo) */
147
  NOFLAGS     /* not used */
148
};
149
150
151
/*
152
 * as2201_start - open the devices and initialize data for processing
153
 */
154
static int
155
as2201_start(
156
  int unit,
157
  struct peer *peer
158
  )
159
0
{
160
0
  register struct as2201unit *up;
161
0
  struct refclockproc *pp;
162
0
  int fd;
163
0
  char gpsdev[20];
164
165
  /*
166
   * Open serial port. Use CLK line discipline, if available.
167
   */
168
0
  snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit);
169
0
  fd = refclock_open(gpsdev, SPEED232, LDISC_CLK);
170
0
  if (fd <= 0)
171
0
    return (0);
172
173
  /*
174
   * Allocate and initialize unit structure
175
   */
176
0
  up = emalloc_zero(sizeof(*up));
177
0
  pp = peer->procptr;
178
0
  pp->io.clock_recv = as2201_receive;
179
0
  pp->io.srcclock = peer;
180
0
  pp->io.datalen = 0;
181
0
  pp->io.fd = fd;
182
0
  if (!io_addclock(&pp->io)) {
183
0
    close(fd);
184
0
    pp->io.fd = -1;
185
0
    free(up);
186
0
    return (0);
187
0
  }
188
0
  pp->unitptr = up;
189
190
  /*
191
   * Initialize miscellaneous variables
192
   */
193
0
  peer->precision = PRECISION;
194
0
  pp->clockdesc = DESCRIPTION;
195
0
  memcpy((char *)&pp->refid, REFID, 4);
196
0
  up->lastptr = up->stats;
197
0
  up->index = 0;
198
0
  return (1);
199
0
}
200
201
202
/*
203
 * as2201_shutdown - shut down the clock
204
 */
205
static void
206
as2201_shutdown(
207
  int unit,
208
  struct peer *peer
209
  )
210
0
{
211
0
  register struct as2201unit *up;
212
0
  struct refclockproc *pp;
213
214
0
  pp = peer->procptr;
215
0
  up = pp->unitptr;
216
0
  if (-1 != pp->io.fd)
217
0
    io_closeclock(&pp->io);
218
0
  if (NULL != up)
219
0
    free(up);
220
0
}
221
222
223
/*
224
 * as2201__receive - receive data from the serial interface
225
 */
226
static void
227
as2201_receive(
228
  struct recvbuf *rbufp
229
  )
230
0
{
231
0
  register struct as2201unit *up;
232
0
  struct refclockproc *pp;
233
0
  struct peer *peer;
234
0
  l_fp trtmp;
235
0
  size_t octets;
236
237
  /*
238
   * Initialize pointers and read the timecode and timestamp.
239
   */
240
0
  peer = rbufp->recv_peer;
241
0
  pp = peer->procptr;
242
0
  up = pp->unitptr;
243
0
  pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
244
0
#ifdef DEBUG
245
0
  if (debug)
246
0
      printf("gps: timecode %d %d %s\n",
247
0
       up->linect, pp->lencode, pp->a_lastcode);
248
0
#endif
249
0
  if (pp->lencode == 0)
250
0
      return;
251
252
  /*
253
   * If linect is greater than zero, we must be in the middle of a
254
   * statistics operation, so simply tack the received data at the
255
   * end of the statistics string. If not, we could either have
256
   * just received the timecode itself or a decimal number
257
   * indicating the number of following lines of the statistics
258
   * reply. In the former case, write the accumulated statistics
259
   * data to the clockstats file and continue onward to process
260
   * the timecode; in the later case, save the number of lines and
261
   * quietly return.
262
   */
263
0
  if (pp->sloppyclockflag & CLK_FLAG2)
264
0
    pp->lastrec = trtmp;
265
0
  if (up->linect > 0) {
266
0
    up->linect--;
267
0
    if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2)
268
0
        return;
269
0
    *up->lastptr++ = ' ';
270
0
    memcpy(up->lastptr, pp->a_lastcode, 1 + pp->lencode);
271
0
    up->lastptr += pp->lencode;
272
0
    return;
273
0
  } else {
274
0
    if (pp->lencode == 1) {
275
0
      up->linect = atoi(pp->a_lastcode);
276
0
      return;
277
0
    } else {
278
0
      record_clock_stats(&peer->srcadr, up->stats);
279
0
#ifdef DEBUG
280
0
      if (debug)
281
0
          printf("gps: stat %s\n", up->stats);
282
0
#endif
283
0
    }
284
0
  }
285
0
  up->lastptr = up->stats;
286
0
  *up->lastptr = '\0';
287
288
  /*
289
   * We get down to business, check the timecode format and decode
290
   * its contents. If the timecode has invalid length or is not in
291
   * proper format, we declare bad format and exit.
292
   */
293
0
  if (pp->lencode < LENTOC) {
294
0
    refclock_report(peer, CEVNT_BADREPLY);
295
0
    return;
296
0
  }
297
298
  /*
299
   * Timecode format: "yy:ddd:hh:mm:ss.mmm"
300
   */
301
0
  if (sscanf(pp->a_lastcode, "%2d:%3d:%2d:%2d:%2d.%3ld", &pp->year,
302
0
       &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec)
303
0
      != 6) {
304
0
    refclock_report(peer, CEVNT_BADREPLY);
305
0
    return;
306
0
  }
307
0
  pp->nsec *= 1000000;
308
309
  /*
310
   * Test for synchronization (this is a temporary crock).
311
   */
312
0
  if (pp->a_lastcode[2] != ':')
313
0
    pp->leap = LEAP_NOTINSYNC;
314
0
  else
315
0
    pp->leap = LEAP_NOWARNING;
316
317
  /*
318
   * Process the new sample in the median filter and determine the
319
   * timecode timestamp.
320
   */
321
0
  if (!refclock_process(pp)) {
322
0
    refclock_report(peer, CEVNT_BADTIME);
323
0
    return;
324
0
  }
325
326
  /*
327
   * If CLK_FLAG4 is set, initialize the statistics buffer and
328
   * send the next command. If not, simply write the timecode to
329
   * the clockstats file.
330
   */
331
0
  if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2)
332
0
      return;
333
0
  memcpy(up->lastptr, pp->a_lastcode, pp->lencode);
334
0
  up->lastptr += pp->lencode;
335
0
  if (pp->sloppyclockflag & CLK_FLAG4) {
336
0
    octets = strlen(stat_command[up->index]);
337
0
    if ((int)(up->lastptr - up->stats + 1 + octets) > SMAX - 2)
338
0
        return;
339
0
    *up->lastptr++ = ' ';
340
0
    memcpy(up->lastptr, stat_command[up->index], octets);
341
0
    up->lastptr += octets - 1;
342
0
    *up->lastptr = '\0';
343
0
    (void)write(pp->io.fd, stat_command[up->index],
344
0
        strlen(stat_command[up->index]));
345
0
    up->index++;
346
0
    if (*stat_command[up->index] == '\0')
347
0
      up->index = 0;
348
0
  }
349
0
}
350
351
352
/*
353
 * as2201_poll - called by the transmit procedure
354
 *
355
 * We go to great pains to avoid changing state here, since there may be
356
 * more than one eavesdropper receiving the same timecode.
357
 */
358
static void
359
as2201_poll(
360
  int unit,
361
  struct peer *peer
362
  )
363
0
{
364
0
  struct refclockproc *pp;
365
366
  /*
367
   * Send a "\r*toc\r" to get things going. We go to great pains
368
   * to avoid changing state, since there may be more than one
369
   * eavesdropper watching the radio.
370
   */
371
0
  pp = peer->procptr;
372
0
  if (write(pp->io.fd, "\r*toc\r", 6) != 6) {
373
0
    refclock_report(peer, CEVNT_FAULT);
374
0
  } else {
375
0
    pp->polls++;
376
0
    if (!(pp->sloppyclockflag & CLK_FLAG2))
377
0
      get_systime(&pp->lastrec);
378
0
  }
379
0
        if (pp->coderecv == pp->codeproc) {
380
0
                refclock_report(peer, CEVNT_TIMEOUT);
381
0
                return;
382
0
        }
383
0
        refclock_receive(peer);
384
0
}
385
386
#else
387
int refclock_as2201_bs;
388
#endif /* REFCLOCK */