xref: /freebsd/contrib/ntp/ntpd/refclock_zyfer.c (revision 96190b4fef3b4a0cc3ca0606b0c4e3e69a5e6717)
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
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
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
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
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 NONEMPTY_TRANSLATION_UNIT
348 #endif /* REFCLOCK */
349