refclock_leitch.c revision 256281
1/*
2 * refclock_leitch - clock driver for the Leitch CSD-5300 Master Clock
3 */
4
5#ifdef HAVE_CONFIG_H
6# include <config.h>
7#endif
8
9#if defined(REFCLOCK) && defined(CLOCK_LEITCH)
10
11#include "ntpd.h"
12#include "ntp_io.h"
13#include "ntp_refclock.h"
14#include "ntp_unixtime.h"
15
16#include <stdio.h>
17#include <ctype.h>
18
19#ifdef STREAM
20#include <stropts.h>
21#if defined(LEITCHCLK)
22#include <sys/clkdefs.h>
23#endif /* LEITCHCLK */
24#endif /* STREAM */
25
26#include "ntp_stdlib.h"
27
28
29/*
30 * Driver for Leitch CSD-5300 Master Clock System
31 *
32 * COMMANDS:
33 *	DATE:	D <CR>
34 *	TIME:	T <CR>
35 *	STATUS:	S <CR>
36 *	LOOP:	L <CR>
37 *
38 * FORMAT:
39 *	DATE: YYMMDD<CR>
40 *	TIME: <CR>/HHMMSS <CR>/HHMMSS <CR>/HHMMSS <CR>/
41 *		second bondaried on the stop bit of the <CR>
42 *		second boundaries at '/' above.
43 *	STATUS: G (good), D (diag fail), T (time not provided) or
44 *		P (last phone update failed)
45 */
46#define MAXUNITS 1		/* max number of LEITCH units */
47#define LEITCHREFID	"ATOM"	/* reference id */
48#define LEITCH_DESCRIPTION "Leitch: CSD 5300 Master Clock System Driver"
49#define LEITCH232 "/dev/leitch%d"	/* name of radio device */
50#define SPEED232 B300		/* uart speed (300 baud) */
51#ifdef DEBUG
52#define leitch_send(A,M) \
53if (debug) fprintf(stderr,"write leitch %s\n",M); \
54if ((write(A->leitchio.fd,M,sizeof(M)) < 0)) {\
55	if (debug) \
56	    fprintf(stderr, "leitch_send: unit %d send failed\n", A->unit); \
57	else \
58	    msyslog(LOG_ERR, "leitch_send: unit %d send failed %m",A->unit);}
59#else
60#define leitch_send(A,M) \
61if ((write(A->leitchio.fd,M,sizeof(M)) < 0)) {\
62	msyslog(LOG_ERR, "leitch_send: unit %d send failed %m",A->unit);}
63#endif
64
65#define STATE_IDLE 0
66#define STATE_DATE 1
67#define STATE_TIME1 2
68#define STATE_TIME2 3
69#define STATE_TIME3 4
70
71/*
72 * LEITCH unit control structure
73 */
74struct leitchunit {
75	struct peer *peer;
76	struct refclockio leitchio;
77	u_char unit;
78	short year;
79	short yearday;
80	short month;
81	short day;
82	short hour;
83	short second;
84	short minute;
85	short state;
86	u_short fudge1;
87	l_fp reftime1;
88	l_fp reftime2;
89	l_fp reftime3;
90	l_fp codetime1;
91	l_fp codetime2;
92	l_fp codetime3;
93	u_long yearstart;
94};
95
96/*
97 * Function prototypes
98 */
99static	void	leitch_init	P((void));
100static	int	leitch_start	P((int, struct peer *));
101static	void	leitch_shutdown	P((int, struct peer *));
102static	void	leitch_poll	P((int, struct peer *));
103static	void	leitch_control	P((int, struct refclockstat *, struct refclockstat *, struct peer *));
104#define	leitch_buginfo	noentry
105static	void	leitch_receive	P((struct recvbuf *));
106static	void	leitch_process	P((struct leitchunit *));
107#if 0
108static	void	leitch_timeout	P((struct peer *));
109#endif
110static	int	leitch_get_date	P((struct recvbuf *, struct leitchunit *));
111static	int	leitch_get_time	P((struct recvbuf *, struct leitchunit *, int));
112static	int	days_per_year		P((int));
113
114static struct leitchunit leitchunits[MAXUNITS];
115static u_char unitinuse[MAXUNITS];
116static u_char stratumtouse[MAXUNITS];
117static u_int32 refid[MAXUNITS];
118
119static	char days_in_month [] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
120
121/*
122 * Transfer vector
123 */
124struct	refclock refclock_leitch = {
125	leitch_start, leitch_shutdown, leitch_poll,
126	leitch_control, leitch_init, leitch_buginfo, NOFLAGS
127};
128
129/*
130 * leitch_init - initialize internal leitch driver data
131 */
132static void
133leitch_init(void)
134{
135	int i;
136
137	memset((char*)leitchunits, 0, sizeof(leitchunits));
138	memset((char*)unitinuse, 0, sizeof(unitinuse));
139	for (i = 0; i < MAXUNITS; i++)
140	    memcpy((char *)&refid[i], LEITCHREFID, 4);
141}
142
143/*
144 * leitch_shutdown - shut down a LEITCH clock
145 */
146static void
147leitch_shutdown(
148	int unit,
149	struct peer *peer
150	)
151{
152#ifdef DEBUG
153	if (debug)
154	    fprintf(stderr, "leitch_shutdown()\n");
155#endif
156}
157
158/*
159 * leitch_poll - called by the transmit procedure
160 */
161static void
162leitch_poll(
163	int unit,
164	struct peer *peer
165	)
166{
167	struct leitchunit *leitch;
168
169	/* start the state machine rolling */
170
171#ifdef DEBUG
172	if (debug)
173	    fprintf(stderr, "leitch_poll()\n");
174#endif
175	if (unit >= MAXUNITS) {
176		/* XXXX syslog it */
177		return;
178	}
179
180	leitch = &leitchunits[unit];
181
182	if (leitch->state != STATE_IDLE) {
183		/* reset and wait for next poll */
184		/* XXXX syslog it */
185		leitch->state = STATE_IDLE;
186	} else {
187		leitch_send(leitch,"D\r");
188		leitch->state = STATE_DATE;
189	}
190}
191
192static void
193leitch_control(
194	int unit,
195	struct refclockstat *in,
196	struct refclockstat *out,
197	struct peer *passed_peer
198	)
199{
200	if (unit >= MAXUNITS) {
201		msyslog(LOG_ERR,
202			"leitch_control: unit %d invalid", unit);
203		return;
204	}
205
206	if (in) {
207		if (in->haveflags & CLK_HAVEVAL1)
208		    stratumtouse[unit] = (u_char)(in->fudgeval1);
209		if (in->haveflags & CLK_HAVEVAL2)
210		    refid[unit] = in->fudgeval2;
211		if (unitinuse[unit]) {
212			struct peer *peer;
213
214			peer = (&leitchunits[unit])->peer;
215			peer->stratum = stratumtouse[unit];
216			peer->refid = refid[unit];
217		}
218	}
219
220	if (out) {
221		memset((char *)out, 0, sizeof (struct refclockstat));
222		out->type = REFCLK_ATOM_LEITCH;
223		out->haveflags = CLK_HAVEVAL1 | CLK_HAVEVAL2;
224		out->fudgeval1 = (int32)stratumtouse[unit];
225		out->fudgeval2 = refid[unit];
226		out->p_lastcode = "";
227		out->clockdesc = LEITCH_DESCRIPTION;
228	}
229}
230
231/*
232 * leitch_start - open the LEITCH devices and initialize data for processing
233 */
234static int
235leitch_start(
236	int unit,
237	struct peer *peer
238	)
239{
240	struct leitchunit *leitch;
241	int fd232;
242	char leitchdev[20];
243
244	/*
245	 * Check configuration info.
246	 */
247	if (unit >= MAXUNITS) {
248		msyslog(LOG_ERR, "leitch_start: unit %d invalid", unit);
249		return (0);
250	}
251
252	if (unitinuse[unit]) {
253		msyslog(LOG_ERR, "leitch_start: unit %d in use", unit);
254		return (0);
255	}
256
257	/*
258	 * Open serial port.
259	 */
260	(void) sprintf(leitchdev, LEITCH232, unit);
261	fd232 = open(leitchdev, O_RDWR, 0777);
262	if (fd232 == -1) {
263		msyslog(LOG_ERR,
264			"leitch_start: open of %s: %m", leitchdev);
265		return (0);
266	}
267
268	leitch = &leitchunits[unit];
269	memset((char*)leitch, 0, sizeof(*leitch));
270
271#if defined(HAVE_SYSV_TTYS)
272	/*
273	 * System V serial line parameters (termio interface)
274	 *
275	 */
276	{	struct termio ttyb;
277	if (ioctl(fd232, TCGETA, &ttyb) < 0) {
278		msyslog(LOG_ERR,
279			"leitch_start: ioctl(%s, TCGETA): %m", leitchdev);
280		goto screwed;
281	}
282	ttyb.c_iflag = IGNBRK|IGNPAR|ICRNL;
283	ttyb.c_oflag = 0;
284	ttyb.c_cflag = SPEED232|CS8|CLOCAL|CREAD;
285	ttyb.c_lflag = ICANON;
286	ttyb.c_cc[VERASE] = ttyb.c_cc[VKILL] = '\0';
287	if (ioctl(fd232, TCSETA, &ttyb) < 0) {
288		msyslog(LOG_ERR,
289			"leitch_start: ioctl(%s, TCSETA): %m", leitchdev);
290		goto screwed;
291	}
292	}
293#endif /* HAVE_SYSV_TTYS */
294#if defined(HAVE_TERMIOS)
295	/*
296	 * POSIX serial line parameters (termios interface)
297	 *
298	 * The LEITCHCLK option provides timestamping at the driver level.
299	 * It requires the tty_clk streams module.
300	 */
301	{	struct termios ttyb, *ttyp;
302
303	ttyp = &ttyb;
304	if (tcgetattr(fd232, ttyp) < 0) {
305		msyslog(LOG_ERR,
306			"leitch_start: tcgetattr(%s): %m", leitchdev);
307		goto screwed;
308	}
309	ttyp->c_iflag = IGNBRK|IGNPAR|ICRNL;
310	ttyp->c_oflag = 0;
311	ttyp->c_cflag = SPEED232|CS8|CLOCAL|CREAD;
312	ttyp->c_lflag = ICANON;
313	ttyp->c_cc[VERASE] = ttyp->c_cc[VKILL] = '\0';
314	if (tcsetattr(fd232, TCSANOW, ttyp) < 0) {
315		msyslog(LOG_ERR,
316			"leitch_start: tcsetattr(%s): %m", leitchdev);
317		goto screwed;
318	}
319	if (tcflush(fd232, TCIOFLUSH) < 0) {
320		msyslog(LOG_ERR,
321			"leitch_start: tcflush(%s): %m", leitchdev);
322		goto screwed;
323	}
324	}
325#endif /* HAVE_TERMIOS */
326#ifdef STREAM
327#if defined(LEITCHCLK)
328	if (ioctl(fd232, I_PUSH, "clk") < 0)
329	    msyslog(LOG_ERR,
330		    "leitch_start: ioctl(%s, I_PUSH, clk): %m", leitchdev);
331	if (ioctl(fd232, CLK_SETSTR, "\n") < 0)
332	    msyslog(LOG_ERR,
333		    "leitch_start: ioctl(%s, CLK_SETSTR): %m", leitchdev);
334#endif /* LEITCHCLK */
335#endif /* STREAM */
336#if defined(HAVE_BSD_TTYS)
337	/*
338	 * 4.3bsd serial line parameters (sgttyb interface)
339	 *
340	 * The LEITCHCLK option provides timestamping at the driver level.
341	 * It requires the tty_clk line discipline and 4.3bsd or later.
342	 */
343	{	struct sgttyb ttyb;
344#if defined(LEITCHCLK)
345	int ldisc = CLKLDISC;
346#endif /* LEITCHCLK */
347
348	if (ioctl(fd232, TIOCGETP, &ttyb) < 0) {
349		msyslog(LOG_ERR,
350			"leitch_start: ioctl(%s, TIOCGETP): %m", leitchdev);
351		goto screwed;
352	}
353	ttyb.sg_ispeed = ttyb.sg_ospeed = SPEED232;
354#if defined(LEITCHCLK)
355	ttyb.sg_erase = ttyb.sg_kill = '\r';
356	ttyb.sg_flags = RAW;
357#else
358	ttyb.sg_erase = ttyb.sg_kill = '\0';
359	ttyb.sg_flags = EVENP|ODDP|CRMOD;
360#endif /* LEITCHCLK */
361	if (ioctl(fd232, TIOCSETP, &ttyb) < 0) {
362		msyslog(LOG_ERR,
363			"leitch_start: ioctl(%s, TIOCSETP): %m", leitchdev);
364		goto screwed;
365	}
366#if defined(LEITCHCLK)
367	if (ioctl(fd232, TIOCSETD, &ldisc) < 0) {
368		msyslog(LOG_ERR,
369			"leitch_start: ioctl(%s, TIOCSETD): %m",leitchdev);
370		goto screwed;
371	}
372#endif /* LEITCHCLK */
373	}
374#endif /* HAVE_BSD_TTYS */
375
376	/*
377	 * Set up the structures
378	 */
379	leitch->peer = peer;
380	leitch->unit = unit;
381	leitch->state = STATE_IDLE;
382	leitch->fudge1 = 15;	/* 15ms */
383
384	leitch->leitchio.clock_recv = leitch_receive;
385	leitch->leitchio.srcclock = (caddr_t) leitch;
386	leitch->leitchio.datalen = 0;
387	leitch->leitchio.fd = fd232;
388	if (!io_addclock(&leitch->leitchio)) {
389		goto screwed;
390	}
391
392	/*
393	 * All done.  Initialize a few random peer variables, then
394	 * return success.
395	 */
396	peer->precision = 0;
397	peer->stratum = stratumtouse[unit];
398	peer->refid = refid[unit];
399	unitinuse[unit] = 1;
400	return(1);
401
402	/*
403	 * Something broke; abandon ship.
404	 */
405    screwed:
406	close(fd232);
407	return(0);
408}
409
410/*
411 * leitch_receive - receive data from the serial interface on a leitch
412 * clock
413 */
414static void
415leitch_receive(
416	struct recvbuf *rbufp
417	)
418{
419	struct leitchunit *leitch = (struct leitchunit *)rbufp->recv_srcclock;
420
421#ifdef DEBUG
422	if (debug)
423	    fprintf(stderr, "leitch_recieve(%*.*s)\n",
424		    rbufp->recv_length, rbufp->recv_length,
425		    rbufp->recv_buffer);
426#endif
427	if (rbufp->recv_length != 7)
428	    return; /* The date is return with a trailing newline,
429		       discard it. */
430
431	switch (leitch->state) {
432	    case STATE_IDLE:	/* unexpected, discard and resync */
433		return;
434	    case STATE_DATE:
435		if (!leitch_get_date(rbufp,leitch)) {
436			leitch->state = STATE_IDLE;
437			break;
438		}
439		leitch_send(leitch,"T\r");
440#ifdef DEBUG
441		if (debug)
442		    fprintf(stderr, "%u\n",leitch->yearday);
443#endif
444		leitch->state = STATE_TIME1;
445		break;
446	    case STATE_TIME1:
447		if (!leitch_get_time(rbufp,leitch,1)) {
448		}
449		if (!clocktime(leitch->yearday,leitch->hour,leitch->minute,
450			       leitch->second, 1, rbufp->recv_time.l_ui,
451			       &leitch->yearstart, &leitch->reftime1.l_ui)) {
452			leitch->state = STATE_IDLE;
453			break;
454		}
455		leitch->reftime1.l_uf = 0;
456#ifdef DEBUG
457		if (debug)
458		    fprintf(stderr, "%lu\n", (u_long)leitch->reftime1.l_ui);
459#endif
460		MSUTOTSF(leitch->fudge1, leitch->reftime1.l_uf);
461		leitch->codetime1 = rbufp->recv_time;
462		leitch->state = STATE_TIME2;
463		break;
464	    case STATE_TIME2:
465		if (!leitch_get_time(rbufp,leitch,2)) {
466		}
467		if (!clocktime(leitch->yearday,leitch->hour,leitch->minute,
468			       leitch->second, 1, rbufp->recv_time.l_ui,
469			       &leitch->yearstart, &leitch->reftime2.l_ui)) {
470			leitch->state = STATE_IDLE;
471			break;
472		}
473#ifdef DEBUG
474		if (debug)
475		    fprintf(stderr, "%lu\n", (u_long)leitch->reftime2.l_ui);
476#endif
477		MSUTOTSF(leitch->fudge1, leitch->reftime2.l_uf);
478		leitch->codetime2 = rbufp->recv_time;
479		leitch->state = STATE_TIME3;
480		break;
481	    case STATE_TIME3:
482		if (!leitch_get_time(rbufp,leitch,3)) {
483		}
484		if (!clocktime(leitch->yearday,leitch->hour,leitch->minute,
485			       leitch->second, GMT, rbufp->recv_time.l_ui,
486			       &leitch->yearstart, &leitch->reftime3.l_ui)) {
487			leitch->state = STATE_IDLE;
488			break;
489		}
490#ifdef DEBUG
491		if (debug)
492		    fprintf(stderr, "%lu\n", (u_long)leitch->reftime3.l_ui);
493#endif
494		MSUTOTSF(leitch->fudge1, leitch->reftime3.l_uf);
495		leitch->codetime3 = rbufp->recv_time;
496		leitch_process(leitch);
497		leitch->state = STATE_IDLE;
498		break;
499	    default:
500		msyslog(LOG_ERR,
501			"leitech_receive: invalid state %d unit %d",
502			leitch->state, leitch->unit);
503	}
504}
505
506/*
507 * leitch_process - process a pile of samples from the clock
508 *
509 * This routine uses a three-stage median filter to calculate offset and
510 * dispersion. reduce jitter. The dispersion is calculated as the span
511 * of the filter (max - min), unless the quality character (format 2) is
512 * non-blank, in which case the dispersion is calculated on the basis of
513 * the inherent tolerance of the internal radio oscillator, which is
514 * +-2e-5 according to the radio specifications.
515 */
516static void
517leitch_process(
518	struct leitchunit *leitch
519	)
520{
521	l_fp off;
522	l_fp tmp_fp;
523      /*double doffset;*/
524
525	off = leitch->reftime1;
526	L_SUB(&off,&leitch->codetime1);
527	tmp_fp = leitch->reftime2;
528	L_SUB(&tmp_fp,&leitch->codetime2);
529	if (L_ISGEQ(&off,&tmp_fp))
530	    off = tmp_fp;
531	tmp_fp = leitch->reftime3;
532	L_SUB(&tmp_fp,&leitch->codetime3);
533
534	if (L_ISGEQ(&off,&tmp_fp))
535	    off = tmp_fp;
536      /*LFPTOD(&off, doffset);*/
537	refclock_receive(leitch->peer);
538}
539
540/*
541 * days_per_year
542 */
543static int
544days_per_year(
545	int year
546	)
547{
548	if (year%4) {	/* not a potential leap year */
549		return (365);
550	} else {
551		if (year % 100) {	/* is a leap year */
552			return (366);
553		} else {
554			if (year % 400) {
555				return (365);
556			} else {
557				return (366);
558			}
559		}
560	}
561}
562
563static int
564leitch_get_date(
565	struct recvbuf *rbufp,
566	struct leitchunit *leitch
567	)
568{
569	int i;
570
571	if (rbufp->recv_length < 6)
572	    return(0);
573#undef  BAD    /* confict: defined as (-1) in AIX sys/param.h */
574#define BAD(A) (rbufp->recv_buffer[A] < '0') || (rbufp->recv_buffer[A] > '9')
575	if (BAD(0)||BAD(1)||BAD(2)||BAD(3)||BAD(4)||BAD(5))
576	    return(0);
577#define ATOB(A) ((rbufp->recv_buffer[A])-'0')
578	leitch->year = ATOB(0)*10 + ATOB(1);
579	leitch->month = ATOB(2)*10 + ATOB(3);
580	leitch->day = ATOB(4)*10 + ATOB(5);
581
582	/* sanity checks */
583	if (leitch->month > 12)
584	    return(0);
585	if (leitch->day > days_in_month[leitch->month-1])
586	    return(0);
587
588	/* calculate yearday */
589	i = 0;
590	leitch->yearday = leitch->day;
591
592	while ( i < (leitch->month-1) )
593	    leitch->yearday += days_in_month[i++];
594
595	if ((days_per_year((leitch->year>90?1900:2000)+leitch->year)==365) &&
596	    leitch->month > 2)
597	    leitch->yearday--;
598
599	return(1);
600}
601
602/*
603 * leitch_get_time
604 */
605static int
606leitch_get_time(
607	struct recvbuf *rbufp,
608	struct leitchunit *leitch,
609	int which
610	)
611{
612	if (BAD(0)||BAD(1)||BAD(2)||BAD(3)||BAD(4)||BAD(5))
613	    return(0);
614	leitch->hour = ATOB(0)*10 +ATOB(1);
615	leitch->minute = ATOB(2)*10 +ATOB(3);
616	leitch->second = ATOB(4)*10 +ATOB(5);
617
618	if ((leitch->hour > 23) || (leitch->minute > 60) ||
619	    (leitch->second > 60))
620	    return(0);
621	return(1);
622}
623
624#else
625int refclock_leitch_bs;
626#endif /* REFCLOCK */
627