xref: /freebsd/contrib/ntp/ntpd/refclock_leitch.c (revision 7bd6fde3)
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) \
52 if (debug) fprintf(stderr,"write leitch %s\n",M); \
53 if ((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  */
68 struct 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  */
93 static	void	leitch_init	P((void));
94 static	int	leitch_start	P((int, struct peer *));
95 static	void	leitch_shutdown	P((int, struct peer *));
96 static	void	leitch_poll	P((int, struct peer *));
97 static	void	leitch_control	P((int, struct refclockstat *, struct refclockstat *, struct peer *));
98 #define	leitch_buginfo	noentry
99 static	void	leitch_receive	P((struct recvbuf *));
100 static	void	leitch_process	P((struct leitchunit *));
101 #if 0
102 static	void	leitch_timeout	P((struct peer *));
103 #endif
104 static	int	leitch_get_date	P((struct recvbuf *, struct leitchunit *));
105 static	int	leitch_get_time	P((struct recvbuf *, struct leitchunit *, int));
106 static	int	days_per_year		P((int));
107 
108 static struct leitchunit leitchunits[MAXUNITS];
109 static u_char unitinuse[MAXUNITS];
110 static u_char stratumtouse[MAXUNITS];
111 static u_int32 refid[MAXUNITS];
112 
113 static	char days_in_month [] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
114 
115 /*
116  * Transfer vector
117  */
118 struct	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  */
126 static void
127 leitch_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  */
140 static void
141 leitch_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  */
155 static void
156 leitch_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 
186 static void
187 leitch_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  */
228 static int
229 leitch_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  */
408 static void
409 leitch_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 		leitch->reftime1.l_uf = 0;
450 #ifdef DEBUG
451 		if (debug)
452 		    fprintf(stderr, "%lu\n", (u_long)leitch->reftime1.l_ui);
453 #endif
454 		MSUTOTSF(leitch->fudge1, leitch->reftime1.l_uf);
455 		leitch->codetime1 = rbufp->recv_time;
456 		leitch->state = STATE_TIME2;
457 		break;
458 	    case STATE_TIME2:
459 		if (!leitch_get_time(rbufp,leitch,2)) {
460 		}
461 		if (!clocktime(leitch->yearday,leitch->hour,leitch->minute,
462 			       leitch->second, 1, rbufp->recv_time.l_ui,
463 			       &leitch->yearstart, &leitch->reftime2.l_ui)) {
464 			leitch->state = STATE_IDLE;
465 			break;
466 		}
467 #ifdef DEBUG
468 		if (debug)
469 		    fprintf(stderr, "%lu\n", (u_long)leitch->reftime2.l_ui);
470 #endif
471 		MSUTOTSF(leitch->fudge1, leitch->reftime2.l_uf);
472 		leitch->codetime2 = rbufp->recv_time;
473 		leitch->state = STATE_TIME3;
474 		break;
475 	    case STATE_TIME3:
476 		if (!leitch_get_time(rbufp,leitch,3)) {
477 		}
478 		if (!clocktime(leitch->yearday,leitch->hour,leitch->minute,
479 			       leitch->second, GMT, rbufp->recv_time.l_ui,
480 			       &leitch->yearstart, &leitch->reftime3.l_ui)) {
481 			leitch->state = STATE_IDLE;
482 			break;
483 		}
484 #ifdef DEBUG
485 		if (debug)
486 		    fprintf(stderr, "%lu\n", (u_long)leitch->reftime3.l_ui);
487 #endif
488 		MSUTOTSF(leitch->fudge1, leitch->reftime3.l_uf);
489 		leitch->codetime3 = rbufp->recv_time;
490 		leitch_process(leitch);
491 		leitch->state = STATE_IDLE;
492 		break;
493 	    default:
494 		msyslog(LOG_ERR,
495 			"leitech_receive: invalid state %d unit %d",
496 			leitch->state, leitch->unit);
497 	}
498 }
499 
500 /*
501  * leitch_process - process a pile of samples from the clock
502  *
503  * This routine uses a three-stage median filter to calculate offset and
504  * dispersion. reduce jitter. The dispersion is calculated as the span
505  * of the filter (max - min), unless the quality character (format 2) is
506  * non-blank, in which case the dispersion is calculated on the basis of
507  * the inherent tolerance of the internal radio oscillator, which is
508  * +-2e-5 according to the radio specifications.
509  */
510 static void
511 leitch_process(
512 	struct leitchunit *leitch
513 	)
514 {
515 	l_fp off;
516 	l_fp tmp_fp;
517       /*double doffset;*/
518 
519 	off = leitch->reftime1;
520 	L_SUB(&off,&leitch->codetime1);
521 	tmp_fp = leitch->reftime2;
522 	L_SUB(&tmp_fp,&leitch->codetime2);
523 	if (L_ISGEQ(&off,&tmp_fp))
524 	    off = tmp_fp;
525 	tmp_fp = leitch->reftime3;
526 	L_SUB(&tmp_fp,&leitch->codetime3);
527 
528 	if (L_ISGEQ(&off,&tmp_fp))
529 	    off = tmp_fp;
530       /*LFPTOD(&off, doffset);*/
531 	refclock_receive(leitch->peer);
532 }
533 
534 /*
535  * days_per_year
536  */
537 static int
538 days_per_year(
539 	int year
540 	)
541 {
542 	if (year%4) {	/* not a potential leap year */
543 		return (365);
544 	} else {
545 		if (year % 100) {	/* is a leap year */
546 			return (366);
547 		} else {
548 			if (year % 400) {
549 				return (365);
550 			} else {
551 				return (366);
552 			}
553 		}
554 	}
555 }
556 
557 static int
558 leitch_get_date(
559 	struct recvbuf *rbufp,
560 	struct leitchunit *leitch
561 	)
562 {
563 	int i;
564 
565 	if (rbufp->recv_length < 6)
566 	    return(0);
567 #undef  BAD    /* confict: defined as (-1) in AIX sys/param.h */
568 #define BAD(A) (rbufp->recv_buffer[A] < '0') || (rbufp->recv_buffer[A] > '9')
569 	if (BAD(0)||BAD(1)||BAD(2)||BAD(3)||BAD(4)||BAD(5))
570 	    return(0);
571 #define ATOB(A) ((rbufp->recv_buffer[A])-'0')
572 	leitch->year = ATOB(0)*10 + ATOB(1);
573 	leitch->month = ATOB(2)*10 + ATOB(3);
574 	leitch->day = ATOB(4)*10 + ATOB(5);
575 
576 	/* sanity checks */
577 	if (leitch->month > 12)
578 	    return(0);
579 	if (leitch->day > days_in_month[leitch->month-1])
580 	    return(0);
581 
582 	/* calculate yearday */
583 	i = 0;
584 	leitch->yearday = leitch->day;
585 
586 	while ( i < (leitch->month-1) )
587 	    leitch->yearday += days_in_month[i++];
588 
589 	if ((days_per_year((leitch->year>90?1900:2000)+leitch->year)==365) &&
590 	    leitch->month > 2)
591 	    leitch->yearday--;
592 
593 	return(1);
594 }
595 
596 /*
597  * leitch_get_time
598  */
599 static int
600 leitch_get_time(
601 	struct recvbuf *rbufp,
602 	struct leitchunit *leitch,
603 	int which
604 	)
605 {
606 	if (BAD(0)||BAD(1)||BAD(2)||BAD(3)||BAD(4)||BAD(5))
607 	    return(0);
608 	leitch->hour = ATOB(0)*10 +ATOB(1);
609 	leitch->minute = ATOB(2)*10 +ATOB(3);
610 	leitch->second = ATOB(4)*10 +ATOB(5);
611 
612 	if ((leitch->hour > 23) || (leitch->minute > 60) ||
613 	    (leitch->second > 60))
614 	    return(0);
615 	return(1);
616 }
617 
618 #else
619 int refclock_leitch_bs;
620 #endif /* REFCLOCK */
621