xref: /netbsd-src/external/bsd/ntp/dist/ntpd/refclock_zyfer.c (revision d16b7486a53dcb8072b60ec6fcb4373a2d0c27b7)
1 /*	$NetBSD: refclock_zyfer.c,v 1.5 2020/05/25 20:47:26 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
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(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
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
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
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 int refclock_zyfer_bs;
350 #endif /* REFCLOCK */
351