]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/ntp/ntpd/refclock_as2201.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / ntp / ntpd / refclock_as2201.c
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 #define SMAX            200     /* statistics buffer length */
66 #define DEVICE          "/dev/gps%d" /* device name and unit */
67 #define SPEED232        B9600   /* uart speed (9600 baud) */
68 #define PRECISION       (-20)   /* precision assumed (about 1 us) */
69 #define REFID           "GPS\0" /* reference ID */
70 #define DESCRIPTION     "Austron 2201A GPS Receiver" /* WRU */
71
72 #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    P((int, struct peer *));
133 static  void    as2201_shutdown P((int, struct peer *));
134 static  void    as2201_receive  P((struct recvbuf *));
135 static  void    as2201_poll     P((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 {
160         register struct as2201unit *up;
161         struct refclockproc *pp;
162         int fd;
163         char gpsdev[20];
164
165         /*
166          * Open serial port. Use CLK line discipline, if available.
167          */
168         (void)sprintf(gpsdev, DEVICE, unit);
169         if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_CLK)))
170                 return (0);
171
172         /*
173          * Allocate and initialize unit structure
174          */
175         if (!(up = (struct as2201unit *)
176               emalloc(sizeof(struct as2201unit)))) {
177                 (void) close(fd);
178                 return (0);
179         }
180         memset((char *)up, 0, sizeof(struct as2201unit));
181         pp = peer->procptr;
182         pp->io.clock_recv = as2201_receive;
183         pp->io.srcclock = (caddr_t)peer;
184         pp->io.datalen = 0;
185         pp->io.fd = fd;
186         if (!io_addclock(&pp->io)) {
187                 (void) close(fd);
188                 free(up);
189                 return (0);
190         }
191         pp->unitptr = (caddr_t)up;
192
193         /*
194          * Initialize miscellaneous variables
195          */
196         peer->precision = PRECISION;
197         peer->burst = NSTAGE;
198         pp->clockdesc = DESCRIPTION;
199         memcpy((char *)&pp->refid, REFID, 4);
200         up->lastptr = up->stats;
201         up->index = 0;
202         return (1);
203 }
204
205
206 /*
207  * as2201_shutdown - shut down the clock
208  */
209 static void
210 as2201_shutdown(
211         int unit,
212         struct peer *peer
213         )
214 {
215         register struct as2201unit *up;
216         struct refclockproc *pp;
217
218         pp = peer->procptr;
219         up = (struct as2201unit *)pp->unitptr;
220         io_closeclock(&pp->io);
221         free(up);
222 }
223
224
225 /*
226  * as2201__receive - receive data from the serial interface
227  */
228 static void
229 as2201_receive(
230         struct recvbuf *rbufp
231         )
232 {
233         register struct as2201unit *up;
234         struct refclockproc *pp;
235         struct peer *peer;
236         l_fp trtmp;
237
238         /*
239          * Initialize pointers and read the timecode and timestamp.
240          */
241         peer = (struct peer *)rbufp->recv_srcclock;
242         pp = peer->procptr;
243         up = (struct as2201unit *)pp->unitptr;
244         pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
245 #ifdef DEBUG
246         if (debug)
247             printf("gps: timecode %d %d %s\n",
248                    up->linect, pp->lencode, pp->a_lastcode);
249 #endif
250         if (pp->lencode == 0)
251             return;
252
253         /*
254          * If linect is greater than zero, we must be in the middle of a
255          * statistics operation, so simply tack the received data at the
256          * end of the statistics string. If not, we could either have
257          * just received the timecode itself or a decimal number
258          * indicating the number of following lines of the statistics
259          * reply. In the former case, write the accumulated statistics
260          * data to the clockstats file and continue onward to process
261          * the timecode; in the later case, save the number of lines and
262          * quietly return.
263          */
264         if (pp->sloppyclockflag & CLK_FLAG2)
265                 pp->lastrec = trtmp;
266         if (up->linect > 0) {
267                 up->linect--;
268                 if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2)
269                     return;
270                 *up->lastptr++ = ' ';
271                 (void)strcpy(up->lastptr, pp->a_lastcode);
272                 up->lastptr += pp->lencode;
273                 return;
274         } else {
275                 if (pp->lencode == 1) {
276                         up->linect = atoi(pp->a_lastcode);
277                         return;
278                 } else {
279                         record_clock_stats(&peer->srcadr, up->stats);
280 #ifdef DEBUG
281                         if (debug)
282                             printf("gps: stat %s\n", up->stats);
283 #endif
284                 }
285         }
286         up->lastptr = up->stats;
287         *up->lastptr = '\0';
288
289         /*
290          * We get down to business, check the timecode format and decode
291          * its contents. If the timecode has invalid length or is not in
292          * proper format, we declare bad format and exit.
293          */
294         if (pp->lencode < LENTOC) {
295                 refclock_report(peer, CEVNT_BADREPLY);
296                 return;
297         }
298
299         /*
300          * Timecode format: "yy:ddd:hh:mm:ss.mmm"
301          */
302         if (sscanf(pp->a_lastcode, "%2d:%3d:%2d:%2d:%2d.%3ld", &pp->year,
303                    &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec)
304             != 6) {
305                 refclock_report(peer, CEVNT_BADREPLY);
306                 return;
307         }
308         pp->nsec *= 1000000;
309
310         /*
311          * Test for synchronization (this is a temporary crock).
312          */
313         if (pp->a_lastcode[2] != ':')
314                 pp->leap = LEAP_NOTINSYNC;
315         else
316                 pp->leap = LEAP_NOWARNING;
317
318         /*
319          * Process the new sample in the median filter and determine the
320          * timecode timestamp.
321          */
322         if (!refclock_process(pp)) {
323                 refclock_report(peer, CEVNT_BADTIME);
324                 return;
325         }
326
327         /*
328          * If CLK_FLAG4 is set, initialize the statistics buffer and
329          * send the next command. If not, simply write the timecode to
330          * the clockstats file.
331          */
332         (void)strcpy(up->lastptr, pp->a_lastcode);
333         up->lastptr += pp->lencode;
334         if (pp->sloppyclockflag & CLK_FLAG4) {
335                 *up->lastptr++ = ' ';
336                 (void)strcpy(up->lastptr, stat_command[up->index]);
337                 up->lastptr += strlen(stat_command[up->index]);
338                 up->lastptr--;
339                 *up->lastptr = '\0';
340                 (void)write(pp->io.fd, stat_command[up->index],
341                     strlen(stat_command[up->index]));
342                 up->index++;
343                 if (*stat_command[up->index] == '\0')
344                         up->index = 0;
345         }
346 }
347
348
349 /*
350  * as2201_poll - called by the transmit procedure
351  *
352  * We go to great pains to avoid changing state here, since there may be
353  * more than one eavesdropper receiving the same timecode.
354  */
355 static void
356 as2201_poll(
357         int unit,
358         struct peer *peer
359         )
360 {
361         struct refclockproc *pp;
362
363         /*
364          * Send a "\r*toc\r" to get things going. We go to great pains
365          * to avoid changing state, since there may be more than one
366          * eavesdropper watching the radio.
367          */
368         pp = peer->procptr;
369         if (write(pp->io.fd, "\r*toc\r", 6) != 6) {
370                 refclock_report(peer, CEVNT_FAULT);
371         } else {
372                 pp->polls++;
373                 if (!(pp->sloppyclockflag & CLK_FLAG2))
374                         get_systime(&pp->lastrec);
375         }
376         if (peer->burst > 0)
377                 return;
378         if (pp->coderecv == pp->codeproc) {
379                 refclock_report(peer, CEVNT_TIMEOUT);
380                 return;
381         }
382         refclock_receive(peer);
383         peer->burst = NSTAGE;
384 }
385
386 #else
387 int refclock_as2201_bs;
388 #endif /* REFCLOCK */