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