1 /*
2 * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
3 *
4 * Harlan Stenn, Jan 2002
5 */
6
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10
11 #if defined(REFCLOCK) && defined(CLOCK_ZYFER)
12
13 #include "ntpd.h"
14 #include "ntp_io.h"
15 #include "ntp_refclock.h"
16 #include "ntp_stdlib.h"
17 #include "ntp_unixtime.h"
18 #include "ntp_calgps.h"
19
20 #include <stdio.h>
21 #include <ctype.h>
22
23 #if defined(HAVE_TERMIOS_H)
24 # include <termios.h>
25 #elif defined(HAVE_SYS_TERMIOS_H)
26 # include <sys/termios.h>
27 #endif
28 #ifdef HAVE_SYS_PPSCLOCK_H
29 # include <sys/ppsclock.h>
30 #endif
31
32 /*
33 * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
34 * This clock also provides PPS as well as IRIG outputs.
35 * Precision is limited by the serial driver, etc.
36 *
37 * If I was really brave I'd hack/generalize the serial driver to deal
38 * with arbitrary on-time characters. This clock *begins* the stream with
39 * `!`, the on-time character, and the string is *not* EOL-terminated.
40 *
41 * Configure the beast for 9600, 8N1. While I see leap-second stuff
42 * in the documentation, the published specs on the TOD format only show
43 * the seconds going to '59'. I see no leap warning in the TOD format.
44 *
45 * The clock sends the following message once per second:
46 *
47 * !TIME,2002,017,07,59,32,2,4,1
48 * YYYY DDD HH MM SS m T O
49 *
50 * ! On-time character
51 * YYYY Year
52 * DDD 001-366 Day of Year
53 * HH 00-23 Hour
54 * MM 00-59 Minute
55 * SS 00-59 Second (probably 00-60)
56 * m 1-5 Time Mode:
57 * 1 = GPS time
58 * 2 = UTC time
59 * 3 = LGPS time (Local GPS)
60 * 4 = LUTC time (Local UTC)
61 * 5 = Manual time
62 * T 4-9 Time Figure Of Merit:
63 * 4 x <= 1us
64 * 5 1us < x <= 10 us
65 * 6 10us < x <= 100us
66 * 7 100us < x <= 1ms
67 * 8 1ms < x <= 10ms
68 * 9 10ms < x
69 * O 0-4 Operation Mode:
70 * 0 Warm-up
71 * 1 Time Locked
72 * 2 Coasting
73 * 3 Recovering
74 * 4 Manual
75 *
76 */
77
78 /*
79 * Interface definitions
80 */
81 #define DEVICE "/dev/zyfer%d" /* device name and unit */
82 #define SPEED232 B9600 /* uart speed (9600 baud) */
83 #define PRECISION (-20) /* precision assumed (about 1 us) */
84 #define REFID "GPS\0" /* reference ID */
85 #define DESCRIPTION "Zyfer GPStarplus" /* WRU */
86
87 #define LENZYFER 29 /* timecode length */
88
89 /*
90 * Unit control structure
91 */
92 struct zyferunit {
93 u_char Rcvbuf[LENZYFER + 1];
94 u_char polled; /* poll message flag */
95 int pollcnt;
96 l_fp tstamp; /* timestamp of last poll */
97 int Rcvptr;
98 };
99
100 /*
101 * Function prototypes
102 */
103 static int zyfer_start (int, struct peer *);
104 static void zyfer_shutdown (int, struct peer *);
105 static void zyfer_receive (struct recvbuf *);
106 static void zyfer_poll (int, struct peer *);
107
108 /*
109 * Transfer vector
110 */
111 struct refclock refclock_zyfer = {
112 zyfer_start, /* start up driver */
113 zyfer_shutdown, /* shut down driver */
114 zyfer_poll, /* transmit poll message */
115 noentry, /* not used (old zyfer_control) */
116 noentry, /* initialize driver (not used) */
117 noentry, /* not used (old zyfer_buginfo) */
118 NOFLAGS /* not used */
119 };
120
121
122 /*
123 * zyfer_start - open the devices and initialize data for processing
124 */
125 static int
zyfer_start(int unit,struct peer * peer)126 zyfer_start(
127 int unit,
128 struct peer *peer
129 )
130 {
131 register struct zyferunit *up;
132 struct refclockproc *pp;
133 int fd;
134 char device[20];
135
136 /*
137 * Open serial port.
138 * Something like LDISC_ACTS that looked for ! would be nice...
139 */
140 snprintf(device, sizeof(device), DEVICE, unit);
141 fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_RAW);
142 if (fd <= 0)
143 return (0);
144
145 msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device);
146
147 /*
148 * Allocate and initialize unit structure
149 */
150 up = emalloc(sizeof(struct zyferunit));
151 memset(up, 0, sizeof(struct zyferunit));
152 pp = peer->procptr;
153 pp->io.clock_recv = zyfer_receive;
154 pp->io.srcclock = peer;
155 pp->io.datalen = 0;
156 pp->io.fd = fd;
157 if (!io_addclock(&pp->io)) {
158 close(fd);
159 pp->io.fd = -1;
160 free(up);
161 return (0);
162 }
163 pp->unitptr = up;
164
165 /*
166 * Initialize miscellaneous variables
167 */
168 peer->precision = PRECISION;
169 pp->clockdesc = DESCRIPTION;
170 memcpy((char *)&pp->refid, REFID, 4);
171 up->pollcnt = 2;
172 up->polled = 0; /* May not be needed... */
173
174 return (1);
175 }
176
177
178 /*
179 * zyfer_shutdown - shut down the clock
180 */
181 static void
zyfer_shutdown(int unit,struct peer * peer)182 zyfer_shutdown(
183 int unit,
184 struct peer *peer
185 )
186 {
187 register struct zyferunit *up;
188 struct refclockproc *pp;
189
190 pp = peer->procptr;
191 up = pp->unitptr;
192 if (pp->io.fd != -1)
193 io_closeclock(&pp->io);
194 if (up != NULL)
195 free(up);
196 }
197
198
199 /*
200 * zyfer_receive - receive data from the serial interface
201 */
202 static void
zyfer_receive(struct recvbuf * rbufp)203 zyfer_receive(
204 struct recvbuf *rbufp
205 )
206 {
207 register struct zyferunit *up;
208 struct refclockproc *pp;
209 struct peer *peer;
210 int tmode; /* Time mode */
211 int tfom; /* Time Figure Of Merit */
212 int omode; /* Operation mode */
213 u_char *p;
214
215 TCivilDate tsdoy;
216 TNtpDatum tsntp;
217 l_fp tfrac;
218
219 peer = rbufp->recv_peer;
220 pp = peer->procptr;
221 up = pp->unitptr;
222 p = (u_char *) &rbufp->recv_space;
223 /*
224 * If lencode is 0:
225 * - if *rbufp->recv_space is !
226 * - - call refclock_gtlin to get things going
227 * - else flush
228 * else stuff it on the end of lastcode
229 * If we don't have LENZYFER bytes
230 * - wait for more data
231 * Crack the beast, and if it's OK, process it.
232 *
233 * We use refclock_gtlin() because we might use LDISC_CLK.
234 *
235 * Under FreeBSD, we get the ! followed by two 14-byte packets.
236 */
237
238 if (pp->lencode >= LENZYFER)
239 pp->lencode = 0;
240
241 if (!pp->lencode) {
242 if (*p == '!')
243 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
244 BMAX, &pp->lastrec);
245 else
246 return;
247 } else {
248 memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
249 pp->lencode += rbufp->recv_length;
250 pp->a_lastcode[pp->lencode] = '\0';
251 }
252
253 if (pp->lencode < LENZYFER)
254 return;
255
256 record_clock_stats(&peer->srcadr, pp->a_lastcode);
257
258 /*
259 * We get down to business, check the timecode format and decode
260 * its contents. If the timecode has invalid length or is not in
261 * proper format, we declare bad format and exit.
262 */
263
264 if (pp->lencode != LENZYFER) {
265 refclock_report(peer, CEVNT_BADTIME);
266 return;
267 }
268
269 /*
270 * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
271 */
272 if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
273 &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second,
274 &tmode, &tfom, &omode) != 8) {
275 refclock_report(peer, CEVNT_BADREPLY);
276 return;
277 }
278
279 if (tmode != 2) {
280 refclock_report(peer, CEVNT_BADTIME);
281 return;
282 }
283
284 /* Should we make sure tfom is 4? */
285
286 if (omode != 1) {
287 pp->leap = LEAP_NOTINSYNC;
288 return;
289 }
290
291 /* treat GPS input as subject to era warps */
292 ZERO(tsdoy);
293 ZERO(tfrac);
294
295 tsdoy.year = pp->year;
296 tsdoy.yearday = pp->day;
297 tsdoy.hour = pp->hour;
298 tsdoy.minute = pp->minute;
299 tsdoy.second = pp->second;
300
301 /* note: We kept 'month' and 'monthday' zero above. That forces
302 * day-of-year based calculation now:
303 */
304 tsntp = gpsntp_from_calendar(&tsdoy, tfrac);
305 tfrac = ntpfp_from_ntpdatum(&tsntp);
306 refclock_process_offset(pp, tfrac, pp->lastrec, pp->fudgetime1);
307
308 /*
309 * Good place for record_clock_stats()
310 */
311 up->pollcnt = 2;
312
313 if (up->polled) {
314 up->polled = 0;
315 refclock_receive(peer);
316 }
317 }
318
319
320 /*
321 * zyfer_poll - called by the transmit procedure
322 */
323 static void
zyfer_poll(int unit,struct peer * peer)324 zyfer_poll(
325 int unit,
326 struct peer *peer
327 )
328 {
329 register struct zyferunit *up;
330 struct refclockproc *pp;
331
332 /*
333 * We don't really do anything here, except arm the receiving
334 * side to capture a sample and check for timeouts.
335 */
336 pp = peer->procptr;
337 up = pp->unitptr;
338 if (!up->pollcnt)
339 refclock_report(peer, CEVNT_TIMEOUT);
340 else
341 up->pollcnt--;
342 pp->polls++;
343 up->polled = 1;
344 }
345
346 #else
347 int refclock_zyfer_bs;
348 #endif /* REFCLOCK */
349