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