xref: /freebsd/contrib/ntp/ntpd/refclock_ulink.c (revision 5129159789cc9d7bc514e4546b88e3427695002d)
1 /*
2  * refclock_ulink - clock driver for Ultralink Model 320 WWVB receivers
3  * By Dave Strout <dstrout@linuxfoundary.com>
4  *
5  * Latest version is always on www.linuxfoundary.com
6  *
7  * Based on the Spectracom driver
8  */
9 
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13 
14 #if defined(REFCLOCK) && defined(CLOCK_ULINK)
15 
16 #include <stdio.h>
17 #include <ctype.h>
18 #include <sys/time.h>
19 #include <time.h>
20 
21 #include "ntpd.h"
22 #include "ntp_io.h"
23 #include "ntp_refclock.h"
24 #include "ntp_calendar.h"
25 #include "ntp_stdlib.h"
26 
27 /*
28  * This driver supports the Ultralink Model 320 WWVB receiver.  The Model 320 is
29  * an RS-232 powered unit which consists of two parts:  a DB-25 shell that contains
30  * a microprocessor, and an approx 2"x4" plastic box that contains the antenna.
31  * The two are connected by a 6-wire RJ-25 cable of length up to 1000'.  The
32  * microprocessor steals power from the RS-232 port, which means that the port must
33  * be kept open all of the time.  The unit also has an internal clock for loss of signal
34  * periods.  Claimed accuracy is 0.1 sec.
35  *
36  * The timecode format is:
37  *
38  *  <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
39  *
40  * where:
41  *
42  * S = 'S' -- sync'd in last hour, '0'-'9' - hours x 10 since last update, else '?'
43  * Q = Number of correlating time-frames, from 0 to 5
44  * R = 'R' -- reception in progress, 'N' -- Noisy reception, ' ' -- standby mode
45  * YYYY = year from 1990 to 2089
46  * DDD = current day from 1 to 366
47  * + = '+' if current year is a leap year, else ' '
48  * HH = UTC hour 0 to 23
49  * MM = Minutes of current hour from 0 to 59
50  * SS = Seconds of current minute from 0 to 59
51  * mm = 10's milliseconds of the current second from 00 to 99
52  * L  = Leap second pending at end of month -- 'I' = inset, 'D'=delete
53  * T  = DST <-> STD transition indicators
54  *
55  * Note that this driver does not do anything with the L or T flags.
56  *
57  * The M320 also has a 'U' command which returns UT1 correction information.  It
58  * is not used in this driver.
59  *
60  */
61 
62 /*
63  * Interface definitions
64  */
65 #define	DEVICE		"/dev/ulink%d" /* device name and unit */
66 #define	SPEED232	B9600	/* uart speed (9600 baud) */
67 #define	PRECISION	(-13)	/* precision assumed (about 100 us) */
68 #define	REFID		"M320"	/* reference ID */
69 #define	DESCRIPTION	"Ultralink WWVB Receiver" /* WRU */
70 
71 #define	LENWWVB0	28	/* format 0 timecode length */
72 #define	LENWWVB2	24	/* format 2 timecode length */
73 #define LENWWVB3        29      /* format 3 timecode length */
74 
75 #define MONLIN		15	/* number of monitoring lines */
76 
77 /*
78  * ULINK unit control structure
79  */
80 struct ulinkunit {
81 	u_char	tcswitch;	/* timecode switch */
82 	l_fp	laststamp;	/* last receive timestamp */
83 	u_char	lasthour;	/* last hour (for monitor) */
84 	u_char	linect;		/* count ignored lines (for monitor */
85 };
86 
87 /*
88  * Function prototypes
89  */
90 static	int	ulink_start	P((int, struct peer *));
91 static	void	ulink_shutdown	P((int, struct peer *));
92 static	void	ulink_receive	P((struct recvbuf *));
93 static	void	ulink_poll	P((int, struct peer *));
94 static  int     fd; /* We need to keep the serial port open to power the ULM320 */
95 
96 /*
97  * Transfer vector
98  */
99 struct	refclock refclock_ulink = {
100 	ulink_start,		/* start up driver */
101 	ulink_shutdown,		/* shut down driver */
102 	ulink_poll,		/* transmit poll message */
103 	noentry,		/* not used (old wwvb_control) */
104 	noentry,		/* initialize driver (not used) */
105 	noentry,		/* not used (old wwvb_buginfo) */
106 	NOFLAGS			/* not used */
107 };
108 
109 
110 /*
111  * ulink_start - open the devices and initialize data for processing
112  */
113 static int
114 ulink_start(
115 	int unit,
116 	struct peer *peer
117 	)
118 {
119 	register struct ulinkunit *up;
120 	struct refclockproc *pp;
121 	char device[20];
122 	fprintf(stderr, "Starting Ulink driver\n");
123 	/*
124 	 * Open serial port. Use CLK line discipline, if available.
125 	 */
126 	(void)sprintf(device, DEVICE, unit);
127 	if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
128 		return (0);
129 
130 	/*
131 	 * Allocate and initialize unit structure
132 	 */
133 	if (!(up = (struct ulinkunit *)
134 	      emalloc(sizeof(struct ulinkunit)))) {
135 		(void) close(fd);
136 		return (0);
137 	}
138 	memset((char *)up, 0, sizeof(struct ulinkunit));
139 	pp = peer->procptr;
140 	pp->unitptr = (caddr_t)up;
141 	pp->io.clock_recv = ulink_receive;
142 	pp->io.srcclock = (caddr_t)peer;
143 	pp->io.datalen = 0;
144 	pp->io.fd = fd;
145 	if (!io_addclock(&pp->io)) {
146 		(void) close(fd);
147 		free(up);
148 		return (0);
149 	}
150 
151 	/*
152 	 * Initialize miscellaneous variables
153 	 */
154 	peer->precision = PRECISION;
155 	peer->flags |= FLAG_BURST;
156 	peer->burst = NSTAGE;
157 	pp->clockdesc = DESCRIPTION;
158 	memcpy((char *)&pp->refid, REFID, 4);
159 	return (1);
160 }
161 
162 
163 /*
164  * ulink_shutdown - shut down the clock
165  */
166 static void
167 ulink_shutdown(
168 	int unit,
169 	struct peer *peer
170 	)
171 {
172 	register struct ulinkunit *up;
173 	struct refclockproc *pp;
174 
175 	pp = peer->procptr;
176 	up = (struct ulinkunit *)pp->unitptr;
177 	io_closeclock(&pp->io);
178 	free(up);
179 	close(fd);
180 }
181 
182 
183 /*
184  * ulink_receive - receive data from the serial interface
185  */
186 static void
187 ulink_receive(
188 	struct recvbuf *rbufp
189 	)
190 {
191 	struct ulinkunit *up;
192 	struct refclockproc *pp;
193 	struct peer *peer;
194 
195 	l_fp	trtmp;		/* arrival timestamp */
196 	char	syncchar;	/* synchronization indicator */
197 	char	qualchar;	/* quality indicator */
198 	char    modechar;       /* Modes: 'R'=rx, 'N'=noise, ' '=standby */
199 	char	leapchar;	/* leap indicator */
200 	int	temp;		/* int temp */
201 
202 	/*
203 	 * Initialize pointers and read the timecode and timestamp
204 	 */
205 	peer = (struct peer *)rbufp->recv_srcclock;
206 	pp = peer->procptr;
207 	up = (struct ulinkunit *)pp->unitptr;
208 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
209 
210 	/*
211 	 * Note we get a buffer and timestamp for both a <cr> and <lf>,
212 	 * but only the <cr> timestamp is retained. Note: in format 0 on
213 	 * a Netclock/2 or upgraded 8170 the start bit is delayed 100
214 	 * +-50 us relative to the pps; however, on an unmodified 8170
215 	 * the start bit can be delayed up to 10 ms. In format 2 the
216 	 * reading precision is only to the millisecond. Thus, unless
217 	 * you have a pps gadget and don't have to have the year, format
218 	 * 0 provides the lowest jitter.
219 	 */
220 	if (temp == 0) {
221 		if (up->tcswitch == 0) {
222 			up->tcswitch = 1;
223 			up->laststamp = trtmp;
224 		} else
225 		    up->tcswitch = 0;
226 		return;
227 	}
228 	pp->lencode = temp;
229 	pp->lastrec = up->laststamp;
230 	up->laststamp = trtmp;
231 	up->tcswitch = 1;
232 #ifdef DEBUG
233 	if (debug)
234 		printf("ulink: timecode %d %s\n", pp->lencode,
235 		    pp->a_lastcode);
236 #endif
237 
238 	/*
239 	 * We get down to business, check the timecode format and decode
240 	 * its contents. This code uses the timecode length to determine
241 	 * whether format 0 or format 2. If the timecode has invalid
242 	 * length or is not in proper format, we declare bad format and
243 	 * exit.
244 	 */
245 	syncchar = qualchar = leapchar = ' ';
246 	pp->msec = 0;
247 
248 	/*
249 	 * Timecode format SQRYYYYDDD+HH:MM:SS.mmLT
250 	 */
251 	sscanf(pp->a_lastcode, "%c%c%c%4d%3d%c%2d:%2d:%2d.%2d",
252 	       &syncchar, &qualchar, &modechar, &pp->year, &pp->day,
253 	       &leapchar,&pp->hour, &pp->minute, &pp->second,&pp->msec);
254 
255 	pp->msec *= 10; /* M320 returns 10's of msecs */
256 	qualchar = ' ';
257 
258 	/*
259 	 * Decode synchronization, quality and leap characters. If
260 	 * unsynchronized, set the leap bits accordingly and exit.
261 	 * Otherwise, set the leap bits according to the leap character.
262 	 * Once synchronized, the dispersion depends only on the
263 	 * quality character.
264 	 */
265 	pp->disp = .001;
266 	pp->leap = LEAP_NOWARNING;
267 
268 	/*
269 	 * Process the new sample in the median filter and determine the
270 	 * timecode timestamp.
271 	 */
272 	if (!refclock_process(pp))
273 		refclock_report(peer, CEVNT_BADTIME);
274 }
275 
276 
277 /*
278  * ulink_poll - called by the transmit procedure
279  */
280 static void
281 ulink_poll(
282 	int unit,
283 	struct peer *peer
284 	)
285 {
286 	register struct ulinkunit *up;
287 	struct refclockproc *pp;
288 	char pollchar;
289 
290 	pp = peer->procptr;
291 	up = (struct ulinkunit *)pp->unitptr;
292 	pollchar = 'T';
293 	if (write(pp->io.fd, &pollchar, 1) != 1)
294 		refclock_report(peer, CEVNT_FAULT);
295 	else
296 		pp->polls++;
297 	if (peer->burst > 0)
298 		return;
299 	if (pp->coderecv == pp->codeproc) {
300 		refclock_report(peer, CEVNT_TIMEOUT);
301 		return;
302 	}
303 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
304 	refclock_receive(peer);
305 	peer->burst = NSTAGE;
306 
307 	/*
308 	 * If the monitor flag is set (flag4), we dump the internal
309 	 * quality table at the first timecode beginning the day.
310 	 */
311 	if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour <
312 	    (int)up->lasthour)
313 		up->linect = MONLIN;
314 	up->lasthour = pp->hour;
315 }
316 
317 #else
318 int refclock_ulink_bs;
319 #endif /* REFCLOCK */
320