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