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