/*
 * refclock_arc - clock driver for ARCRON MSF receivers
 */

/*
Code by Derek Mulcahy, <derek@toybox.demon.co.uk>, 1997.

THIS CODE IS SUPPLIED AS IS, WITH NO WARRANTY OF ANY KIND.  USE AT
YOUR OWN RISK.

Orginally developed and used with xntp3-5.85.

This code may be freely copied and used and incorporated in other
systems providing the disclaimer and notice of authorship are
reproduced.


Author's original note:

I enclose my xntp driver for the Galleon Systems Arc MSF receiver.

It works (after a fashion) on both Solaris-1 and Solaris-2.

I am currently using xntp3-5.85.  I have been running the code for
about 7 months without any problems.  Even coped with the change to BST!

I had to do some funky things to read from the clock because it uses the
power from the receive lines to drive the transmit lines.  This makes the
code look a bit stupid but it works.  I also had to put in some delays to
allow for the turnaround time from receive to transmit.  These delays
are between characters when requesting a time stamp so that shouldn't affect
the results too drastically.

...

The bottom line is that it works but could easily be improved.  You are
free to do what you will with the code.  I haven't been able to determine
how good the clock is.  I think that this requires a known good clock
to compare it against. 
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(ARC)

#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>

#if defined(HAVE_BSD_TTYS)
#include <sgtty.h>
#endif /* HAVE_BSD_TTYS */

#if defined(HAVE_SYSV_TTYS)
#include <termio.h>
#endif /* HAVE_SYSV_TTYS */

#if defined(HAVE_TERMIOS)
#include <termios.h>
#endif

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"

/*
 * This driver supports the ARCRON MSF Radio Controlled Clock
 */

/*
 * Interface definitions
 */
#define	DEVICE		"/dev/arc%d" /* device name and unit */
#define	SPEED		B300	/* uart speed (300 baud) */
#define	PRECISION	(-7)	/* precision  (~10 ms) */
#define	REFID		"ARC"	/* reference ID */
#define	DESCRIPTION	"ARCRON MSF Receiver"

#define	NSAMPLES	3	/* stages of median filter */
#define	LENARC		16	/* format 0 timecode length */

static int moff[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

/*
 * Imported from ntp_timer module
 */
extern	u_long	current_time;	/* current time (s) */

/*
 * Imported from ntpd module
 */
extern	int	debug;		/* global debug flag */

/*
 * ARC unit control structure
 */
struct arcunit {
    int	pollcnt;	/* poll message counter */
    l_fp lastrec; 	/* time tag for the receive time (system) */
    int status;		/* clock status */
    int quality;	/* quality of reception 0 .. 5 */
};

/*
 * Function prototypes
 */
static	int	arc_start	P((int, struct peer *));
static	void	arc_shutdown	P((int, struct peer *));
static	void	arc_receive	P((struct recvbuf *));
static	void	arc_poll	P((int, struct peer *));

/*
 * Transfer vector
 */
struct	refclock refclock_arc = {
	arc_start,		/* start up driver */
	arc_shutdown,		/* shut down driver */
	arc_poll,		/* transmit poll message */
	noentry,		/* not used (old arc_control) */
	noentry,		/* initialize driver (not used) */
	noentry,		/* not used (old arc_buginfo) */
	NOFLAGS			/* not used */
};


/*
 * arc_start - open the devices and initialize data for processing
 */
static int
arc_start(unit, peer)
	int unit;
	struct peer *peer;
{
	register struct arcunit *up;
	struct refclockproc *pp;
	int fd;
	char device[20];
#ifdef HAVE_TERMIOS
	struct termios arg;
#endif

	/*
	 * Open serial port. Use CLK line discipline, if available.
	 */
	(void)sprintf(device, DEVICE, unit);
#ifdef TTYCLK
	if (!(fd = refclock_open(device, SPEED, LDISC_CLK)))
		return (0);
#else
	fd = open("/dev/arc0",O_RDWR);

	fcntl(fd, F_SETFL, 0); /* clear the descriptor flags */

	if (debug)
	    printf("Opening RS232 port with file descriptor %d\n", fd);

#ifdef HAVE_TERMIOS

	arg.c_iflag = IGNBRK | ISTRIP;
	arg.c_oflag = 0;
	arg.c_cflag = B300 | CS8 | CREAD | CLOCAL | CSTOPB;
	arg.c_lflag = 0;
	arg.c_cc[VMIN] = 1;
	arg.c_cc[VTIME] = 0;

	tcsetattr(fd, TCSANOW, &arg);

#else

	syslog(LOG_ERR, "Datum_PTS: Termios not supported in this driver");
	(void)close(fd);

	return 0;

#endif
#endif /* TTYCLK */

	up = (struct arcunit *) emalloc(sizeof(struct arcunit));
	if (!up) {
		(void) close(fd);
		return (0);
	}
	memset((char *)up, 0, sizeof(struct arcunit));
	pp = peer->procptr;
	pp->io.clock_recv = arc_receive;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;
	pp->io.fd = fd;
	if (!io_addclock(&pp->io)) {
		(void) close(fd);
		free(up);
		return (0);
	}
	pp->unitptr = (caddr_t)up;

	/*
	 * Initialize miscellaneous variables
	 */
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy((char *)&pp->refid, REFID, 4);
	up->pollcnt = 2;
	up->quality = 6;
	return (1);
}


/*
 * arc_shutdown - shut down the clock
 */
static void
arc_shutdown(unit, peer)
	int unit;
	struct peer *peer;
{
	register struct arcunit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct arcunit *)pp->unitptr;
	io_closeclock(&pp->io);
	free(up);
}

static int
send_slow(fd, s)
int fd;
char *s;
{
    while (*s) {
	if (write(fd, s++, 1) != 1)
	    return(0);
	tcdrain(fd);
	usleep(80000);
    }
    return(1);
}

/*
 * arc_receive - receive data from the serial interface
 */
static void
arc_receive(rbufp)
	struct recvbuf *rbufp;
{
	register struct arcunit *up;
	struct refclockproc *pp;
	struct peer *peer;
	l_fp trtmp;
	char c;
	int r, q;
	int i, n, wday, month, bst, status;

	/*
	 * Initialize pointers and read the timecode and timestamp
	 */
	peer = (struct peer *)rbufp->recv_srcclock;
	pp = peer->procptr;
	up = (struct arcunit *)pp->unitptr;

	if (pp->lencode < 2 && pp->lencode+rbufp->recv_length >= 2) {
	    if (debug)
		printf("stamp -->%c<--\n", rbufp->recv_buffer[0]);
	    up->lastrec = rbufp->recv_time;
	}

	for (i = 0; i < rbufp->recv_length; i++) {
	    c = rbufp->recv_buffer[i];
	    if (c != '\r' && c != 'h')
		pp->lastcode[pp->lencode++] = c;
	}

	/* handle a quality message */
	if (pp->lastcode[0] == 'g' && pp->lencode == 3) {
	    n = sscanf(pp->lastcode, "g%1d%1d", &r, &q);
	    if (n == 2 && r == 3)
		up->quality = q;
	    pp->lencode = 0;
	}

	if (pp->lencode < LENARC)
	    return;

	up->pollcnt = 2;
	pp->lastcode[pp->lencode] = '0' + up->quality;
	record_clock_stats(&peer->srcadr, pp->lastcode);
	if (debug)
        	printf("arc: timecode %d %s\n", pp->lencode, pp->lastcode);

	if (pp->lencode != LENARC) {
	    refclock_report(peer, CEVNT_BADREPLY);
	    return;
	}

	n = sscanf(pp->lastcode, "o%2d%2d%2d%1d%2d%2d%2d%1d%1d",
	    &pp->hour, &pp->minute, &pp->second,
	    &wday, &pp->day, &month, &pp->year, &bst, &status);
    
	pp->lastrec = up->lastrec;
	pp->leap = 0;
	status &= 0x4;
	if (status) {
	    pp->lasttime = current_time;
	    if (status != up->status)
		syslog(LOG_INFO, "refclock_arc: signal acquired.");
	} else {
	    if (status != up->status)
		syslog(LOG_INFO, "refclock_arc: signal lost.");
	}
	up->status = status;

	if (debug) {
	    printf("n=%d %02d:%02d:%02d %02d/%02d/%02d %1d %1d\n",
		n,
		pp->hour, pp->minute, pp->second,
		pp->day, month, pp->year, bst, status);
	}
	if (n != 9) {
	    refclock_report(peer, CEVNT_BADREPLY);
	    return;
	}

	pp->day += moff[month - 1];

	/* Good 'til 1st March 2100 */
	if (((pp->year % 4) == 0) && month > 2)
	    pp->day++;

	/* Convert to UTC if required */
	if (bst & 2) {
	    pp->hour--;
	    if (pp->hour < 0) {
		pp->hour = 23;
		pp->day--;
	    }
	}
	/*
	 * Process the new sample in the median filter and determine the
	 * reference clock offset and dispersion. We use lastrec as both
	 * the reference time and receive time in order to avoid being
	 * cute, like setting the reference time later than the receive
	 * time, which may cause a paranoid protocol module to chuck out
	 * the data.
 	 */
	if (!refclock_process(pp, NSAMPLES, NSAMPLES)) {
	    refclock_report(peer, CEVNT_BADTIME);
	    return;
	}
	trtmp = pp->lastrec;
	refclock_receive(peer, &pp->offset, 0, pp->dispersion,
	    &trtmp, &pp->lastrec, pp->leap);
#if 0
	pp->lencode = 0;
	memset(pp->lastcode, 0, sizeof(pp->lastcode));
	send_slow(pp->io.fd, "g\r");
#endif
}

/*
 * arc_poll - called by the transmit procedure
 */
static void
arc_poll(unit, peer)
	int unit;
	struct peer *peer;
{
	register struct arcunit *up;
	struct refclockproc *pp;
	static int first = 1;

	pp = peer->procptr;
	up = (struct arcunit *)pp->unitptr;
	pp->lencode = 0;
	memset(pp->lastcode, 0, sizeof(pp->lastcode));
	if (debug)
	    printf("*** poll: pollcnt=%d\n", up->pollcnt);
	if (up->pollcnt == 0) {
		refclock_report(peer, CEVNT_TIMEOUT);
		return;
	}
	up->pollcnt--;
	tcflush(pp->io.fd, TCIFLUSH);
	if (first) {
	    send_slow(pp->io.fd, "h\r");
	    first = 0;
	}
	if (!send_slow(pp->io.fd, "o\r")) {
	    refclock_report(peer, CEVNT_FAULT);
	    return;
	}
	pp->polls++;
}

#endif
