/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 */ |