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