/*
 * refclock_gpstm - clock driver for the Kinimetrics Truetime GPSTM/TMD rcvr
 *    Version 1.0 (from Version 2.0 of the GOES driver, as of 03Jan94)
 */

#if defined(REFCLOCK) && (defined(GPSTM) || defined(GPSTMCLK) \
			  || defined(GPSTMPPS))

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

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

#ifdef SYS_BSDI
#undef HAVE_BSD_TTYS
#include <sys/ioctl.h>
#endif

#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

#if defined(STREAM)
#include <stropts.h>
#if defined(GPSTMCLK)
#include <clkdefs.h>
#endif /* GPSTMCLK */
#endif /* STREAM */

#if defined(GPSTMPPS)
#include <sys/ppsclock.h>
#endif /* GPSTMPPS */

#include "ntp_stdlib.h"

/*
 * Support for Kinemetrics Truetime GPS-TM/TMD Receiver
 *
 * Most of this code is copied from refclock_goes.c with thanks.
 *
 * the time code looks like follows:
 *
 * ADDD:HH:MM:SSQCL
 * A - control A
 * Q Quality indication: indicates possible error of
 *     ?     +/- 500 milliseconds            #     +/- 50 milliseconds
 *     *     +/- 5 milliseconds              .     +/- 1 millisecond
 *   space   less than 1 millisecond
 * C - Carriage return
 * L - Line feed
 * The carriage return start bit begins on 0 seconds and extends to 1 bit time.
 *
 * Flag1 set to 1 will silence the clock side of xntpd, just reading the
 * clock without trying to write to it.  This is usefull if several
 * xntpds listen to the same clock.  This has not been tested yet...
 */

/*
 * Definitions
 */
#define	MAXUNITS	4	/* max number of GPSTM units */
#define	GPSTM232	"/dev/gpstm%d"
#define	SPEED232	B9600	/* 9600 baud */

/*
 * Radio interface parameters
 */
#define	MAXDISPERSE	(FP_SECOND>>1) /* max error for synchronized clock (0.5 s as an u_fp) */
#define	PRECISION	(-20)	/* precision assumed (about 1 ms) */
#define	REFID		"GPS\0"	/* reference id */
#define	DESCRIPTION	"Kinemetrics GPS-TM/TMD Receiver" /* who we are */
#define GMT		0	/* hour offset from Greenwich */
#define	NCODES		3	/* stages of median filter */
#define BMAX		99	/* timecode buffer length */
#define	CODEDIFF	0x20000000	/* 0.125 seconds as an l_fp fraction */
#define	TIMEOUT		180	/* ping the clock if it's silent this long */

/*
 * used by the state machine
 */
enum gpstm_event {e_Init, e_F18, e_F50, e_F51, e_TS};
static enum {Base, Start, F18, F50, F51, F08} State[MAXUNITS];
static time_t Last[MAXUNITS];
static void gpstm_doevent P((int, enum gpstm_event));
static void gpstm_initstate P((int));

/*
 * Hack to avoid excercising the multiplier.  I have no pride.
 */
#define	MULBY10(x)	(((x)<<3) + ((x)<<1))

/*
 * Imported from the timer module
 */
extern U_LONG current_time;
extern struct event timerqueue[];

/*
 * Imported from ntp_loopfilter module
 */
extern int fdpps;		/* pps file descriptor */

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

/*
 * GPSTM unit control structure
 */
struct gpstm_unit {
	struct peer *peer;		/* associated peer structure */
	struct refclockio io;		/* given to the I/O handler */
	l_fp lastrec;			/* last receive time */
	l_fp lastref;			/* last timecode time */
	l_fp offset[NCODES];		/* recent sample offsets */
	char lastcode[BMAX];		/* last timecode received */
	u_short polled;			/* Hand in a time sample? */
	u_char lencode;			/* length of last timecode */
	U_LONG lasttime;		/* last time clock heard from */
	u_char unit;			/* unit number for this guy */
	u_char status;			/* clock status */
	u_char lastevent;		/* last clock event */
	u_char reason;			/* reason for last abort */
	u_char year;			/* year of eternity */
	u_short day;			/* day of year */
	u_char hour;			/* hour of day */
	u_char minute;			/* minute of hour */
	u_char second;			/* seconds of minute */
	u_char leap;			/* leap indicators */
	u_short msec;			/* millisecond of second */
	u_char quality;			/* quality character */
	U_LONG yearstart;		/* start of current year */
	/*
	 * Status tallies
 	 */
	U_LONG polls;			/* polls sent */
	U_LONG noreply;			/* no replies to polls */
	U_LONG coderecv;		/* timecodes received */
	U_LONG badformat;		/* bad format */
	U_LONG baddata;			/* bad data */
	U_LONG timestarted;		/* time we started this */
};

/*
 * Data space for the unit structures.  Note that we allocate these on
 * the fly, but never give them back.
 */
static struct gpstm_unit *gpstm_units[MAXUNITS];
static u_char unitinuse[MAXUNITS];

/*
 * Keep the fudge factors separately so they can be set even
 * when no clock is configured.
 */
static l_fp fudgefactor1[MAXUNITS];
static l_fp fudgefactor2[MAXUNITS];
static u_char stratumtouse[MAXUNITS];
static u_char readonlyclockflag[MAXUNITS];
static U_LONG refid[MAXUNITS];

/*
 * Function prototypes
 */
static	void	gpstm_init	P((void));
static	int	gpstm_start	P((u_int, struct peer *));
static	void	gpstm_shutdown	P((int));
static	void	gpstm_rep_event	P((struct gpstm_unit *, int));
static	void	gpstm_receive	P((struct recvbuf *));
static	char	gpstm_process	P((struct gpstm_unit *, l_fp *, u_fp *));
static	void	gpstm_poll	P((int, struct peer *));
static	void	gpstm_control	P((u_int, struct refclockstat *,
				   struct refclockstat *));
static	void	gpstm_buginfo	P((int, struct refclockbug *));
static	void	gpstm_send	P((struct gpstm_unit *, char *));

struct	refclock refclock_gpstm = {
	gpstm_start, gpstm_shutdown, gpstm_poll,
	gpstm_control, gpstm_init, gpstm_buginfo, NOFLAGS
};

/*
 * gpstm_init - initialize internal driver data
 */
static void
gpstm_init()
{
	register int i;
	/*
	 * Just zero the data arrays
	 */
	memset((char *)gpstm_units, 0, sizeof gpstm_units);
	memset((char *)unitinuse, 0, sizeof unitinuse);

	/*
	 * Initialize fudge factors to default.
	 */
	for (i = 0; i < MAXUNITS; i++) {
		fudgefactor1[i].l_ui = 0;
		fudgefactor1[i].l_uf = 0;
		fudgefactor2[i].l_ui = 0;
		fudgefactor2[i].l_uf = 0;
		stratumtouse[i] = 0;
		readonlyclockflag[i] = 0;
		memcpy((char *)&refid[i], REFID, 4);
	}
}


/*
 * gpstm_start - open the device and initialize data for processing
 */
static int
gpstm_start(unit, peer)
	u_int unit;
	struct peer *peer;
{
	register struct gpstm_unit *gpstm;
	register int i;
	int fd232;
	char dev[20];

	/*
	 * Check configuration info
	 */
	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "gpstm_start: unit %d invalid", unit);
		return 0;
	}
	if (unitinuse[unit]) {
		syslog(LOG_ERR, "gpstm_start: unit %d in use", unit);
		return 0;
	}

	/*
	 * Open serial port
	 */
	(void) sprintf(dev, GPSTM232, unit);
	fd232 = open(dev, O_RDWR, 0777);
	if (fd232 == -1) {
		syslog(LOG_ERR, "gpstm_start: open of %s: %m", dev);
		return 0;
	}

#if defined(HAVE_SYSV_TTYS)
	/*
	 * System V serial line parameters (termio interface)
	 *
	 */
    {	struct termio ttyb;
	if (ioctl(fd232, TCGETA, &ttyb) < 0) {
                syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, TCGETA): %m", dev);
                goto screwed;
        }
        ttyb.c_iflag = IGNBRK|IGNPAR|ICRNL;
        ttyb.c_oflag = 0;
        ttyb.c_cflag = SPEED232|CS8|CLOCAL|CREAD;
        ttyb.c_lflag = ICANON;
	ttyb.c_cc[VERASE] = ttyb.c_cc[VKILL] = '\0';
        if (ioctl(fd232, TCSETA, &ttyb) < 0) {
                syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, TCSETA): %m", dev);
                goto screwed;
        }
    }
#endif /* HAVE_SYSV_TTYS */
#if defined(HAVE_TERMIOS)
	/*
	 * POSIX serial line parameters (termios interface)
	 *
	 * The GPSTMCLK option provides timestamping at the driver level. 
	 * It requires the tty_clk streams module.
	 *
	 * The GPSTMPPS option provides timestamping at the driver level.
	 * It uses a 1-pps signal and level converter (gadget box) and
	 * requires the ppsclock streams module and SunOS 4.1.1 or
	 * later.
	 */
    {	struct termios ttyb, *ttyp;
	ttyp = &ttyb;

	if (tcgetattr(fd232, ttyp) < 0) {
                syslog(LOG_ERR,
		    "gpstm_start: tcgetattr(%s): %m", dev);
                goto screwed;
        }
        ttyp->c_iflag = IGNBRK|IGNPAR|ICRNL;
        ttyp->c_oflag = 0;
        ttyp->c_cflag = SPEED232|CS8|CLOCAL|CREAD;
        ttyp->c_lflag = ICANON;
	ttyp->c_cc[VERASE] = ttyp->c_cc[VKILL] = '\0';
        if (tcsetattr(fd232, TCSANOW, ttyp) < 0) {
                syslog(LOG_ERR,
		    "gpstm_start: tcsetattr(%s): %m", dev);
                goto screwed;
        }
        if (tcflush(fd232, TCIOFLUSH) < 0) {
                syslog(LOG_ERR,
		    "gpstm_start: tcflush(%s): %m", dev);
                goto screwed;
        }
#if defined(STREAM)
#if defined(GPSTMCLK)
	if (ioctl(fd232, I_PUSH, "clk") < 0)
		syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, I_PUSH, clk): %m", dev);
	if (ioctl(fd232, CLK_SETSTR, "\n") < 0)
		syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, CLK_SETSTR): %m", dev);
#endif /* GPSTMCLK */
#if defined(GPSTMPPS)
	if (ioctl(fd232, I_PUSH, "ppsclock") < 0)
		syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, I_PUSH, ppsclock): %m", dev);
	else
		fdpps = fd232;
#endif /* GPSTMPPS */
#endif /* STREAM */
    }
#endif /* HAVE_TERMIOS */
#if defined(HAVE_BSD_TTYS)
	/*
	 * 4.3bsd serial line parameters (sgttyb interface)
	 *
	 * The GPSTMCLK option provides timestamping at the driver level. 
	 * It requires the tty_clk line discipline and 4.3bsd or later.
	 */
    {	struct sgttyb ttyb;
#if defined(GPSTMCLK)
	int ldisc = CLKLDISC;
#endif /* GPSTMCLK */

	if (ioctl(fd232, TIOCGETP, &ttyb) < 0) {
		syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, TIOCGETP): %m", dev);
		goto screwed;
	}
	ttyb.sg_ispeed = ttyb.sg_ospeed = SPEED232;
#if defined(GPSTMCLK)
	ttyb.sg_erase = ttyb.sg_kill = '\r';
	ttyb.sg_flags = RAW;
#else
	ttyb.sg_erase = ttyb.sg_kill = '\0';
	ttyb.sg_flags = EVENP|ODDP|CRMOD;
#endif /* GPSTMCLK */
	if (ioctl(fd232, TIOCSETP, &ttyb) < 0) {
		syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, TIOCSETP): %m", dev);
		goto screwed;
	}
#if defined(GPSTMCLK)
	if (ioctl(fd232, TIOCSETD, &ldisc) < 0) {
		syslog(LOG_ERR,
		    "gpstm_start: ioctl(%s, TIOCSETD): %m", dev);
		goto screwed;
	}
#endif /* GPSTMCLK */
    }
#endif /* HAVE_BSD_TTYS */

	/*
	 * Allocate unit structure
	 */
	if (gpstm_units[unit] != 0) {
		gpstm = gpstm_units[unit];	/* The one we want is okay */
	} else {
		for (i = 0; i < MAXUNITS; i++) {
			if (!unitinuse[i] && gpstm_units[i] != 0)
				break;
		}
		if (i < MAXUNITS) {
			/*
			 * Reclaim this one
			 */
			gpstm = gpstm_units[i];
			gpstm_units[i] = 0;
		} else {
			gpstm = (struct gpstm_unit *)
			    emalloc(sizeof(struct gpstm_unit));
		}
	}
	memset((char *)gpstm, 0, sizeof(struct gpstm_unit));
	gpstm_units[unit] = gpstm;

	/*
	 * Set up the structures
	 */
	gpstm->peer = peer;
	gpstm->unit = (u_char)unit;
	gpstm->timestarted = current_time;

	gpstm->io.clock_recv = gpstm_receive;
	gpstm->io.srcclock = (caddr_t)gpstm;
	gpstm->io.datalen = 0;
	gpstm->io.fd = fd232;
	if (!io_addclock(&gpstm->io)) {
		goto screwed;
	}

	/*
	 * All done.  Initialize a few random peer variables, then
	 * return success.
	 */
	peer->precision = PRECISION;
	peer->rootdelay = 0;
	peer->rootdispersion = 0;
	peer->stratum = stratumtouse[unit];
	peer->refid = refid[unit];
	unitinuse[unit] = 1;
	gpstm_initstate(unit);
	return 1;

	/*
	 * Something broke; abandon ship
	 */
screwed:
	(void) close(fd232);
	return 0;
}

/*
 * gpstm_shutdown - shut down a clock
 */
static void
gpstm_shutdown(unit)
	int unit;
{
	register struct gpstm_unit *gpstm;

	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "gpstm_shutdown: unit %d invalid", unit);
		return;
	}
	if (!unitinuse[unit]) {
		syslog(LOG_ERR, "gpstm_shutdown: unit %d not in use", unit);
		return;
	}

	/*
	 * Tell the I/O module to turn us off.  We're history.
	 */
	gpstm = gpstm_units[unit];
	io_closeclock(&gpstm->io);
	unitinuse[unit] = 0;
}


/*
 * gpstm_rep_event - note the occurance of an event
 */
static void
gpstm_rep_event(gpstm, code)
	struct gpstm_unit *gpstm;
	int code;
{
	struct peer *peer;

	peer = gpstm->peer;
	if (gpstm->status != (u_char)code) {
		gpstm->status = (u_char)code;
		if (code != CEVNT_NOMINAL)
			gpstm->lastevent = (u_char)code;
		syslog(LOG_INFO,
		    "clock %s event %x\n", ntoa(&peer->srcadr), code);
#ifdef DEBUG
		if (debug) {
			printf("gpstm_rep_event(gpstm%d, code %d)\n",
			       gpstm->unit, code);
		}
#endif
	}
	if (code == CEVNT_BADREPLY)
		gpstm_initstate(gpstm->unit);
}


/*
 * gpstm_receive - receive data from the serial interface on a clock
 */
static void
gpstm_receive(rbufp)
	struct recvbuf *rbufp;
{
	register int i;
	register struct gpstm_unit *gpstm;
	register u_char *dpt;
	register char *cp;
	register u_char *dpend;
	l_fp tstmp;
	u_fp dispersion;

	/*
	 * Get the clock this applies to and a pointers to the data
	 */
	gpstm = (struct gpstm_unit *)rbufp->recv_srcclock;
	dpt = (u_char *)&rbufp->recv_space;

	/*
	 * Edit timecode to remove control chars
	 */
	dpend = dpt + rbufp->recv_length;
	cp = gpstm->lastcode;
	while (dpt < dpend) {
		if ((*cp = 0x7f & *dpt++) >= ' ') cp++; 
#ifdef GPSTMCLK
		else if (*cp == '\r') {
			if (dpend - dpt < 8) {
				/* short timestamp */
				return;
			}
			if (!buftvtots(dpt,&gpstm->lastrec)) {
				/* screwy timestamp */
				return;
			}
			dpt += 8;
		}
#endif
	}
	*cp = '\0';
	gpstm->lencode = cp - gpstm->lastcode;
	if (gpstm->lencode == 0)
		return;
#ifndef GPSTMCLK
	gpstm->lastrec = rbufp->recv_time;
#endif /* GPSTMCLK */
#if !defined(GPSTMCLK) && !defined(GPSTMPPS) && defined(TIOCMODT)
	do {
		auto	struct timeval	cur, now;
		register long		usec;

		if (ioctl(gpstm->io.fd, TIOCMODT, &cur) < 0) {
			syslog(LOG_ERR, "TIOCMODT: %m");
#ifdef DEBUG
			if (debug) perror("TIOCMODT");
			break;
#endif
		}
		if (cur.tv_sec == 0) {
			/* no timestamps yet */
			if (debug) printf("MODT tv_sec == 0\n");
			break;
		}

		gettimeofday(&now, NULL);
		usec = 1000000 * (now.tv_sec - cur.tv_sec)
			+ (now.tv_usec - cur.tv_usec);
#ifdef DEBUG
		if (debug) printf("lastmodem: delay=%d us\n", usec);
#endif
		if (usec < 0 || usec > 10000) {
			/* time warp or stale timestamp */
			break;
		}
		if (!buftvtots((char *)&cur, &gpstm->lastrec)) {
			/* screwy timestamp */
			break;
		}
	} while (0);
#endif /*TIOCMODT*/

#ifdef DEBUG
	if (debug)
        	printf("gpstm: timecode %d %s\n",
		       gpstm->lencode, gpstm->lastcode);
#endif

	cp = gpstm->lastcode;
	gpstm->leap = 0;
	if ((cp[0] == 'F' && isdigit(cp[1]) && isdigit(cp[2]))
	    || (cp[0] == ' ' && cp[1] == 'T' && cp[2] == 'R')) {
		enum gpstm_event event;

		syslog(LOG_NOTICE, "gpstm%d: \"%s\"", gpstm->unit, cp);
		if (cp[1] == '5' && cp[2] == '0')
			event = e_F50;
		else if (cp[1] == '5' && cp[2] == '1')
			event = e_F51;
		else if (!strncmp(" TRUETIME Mk III", cp, 16))
			event = e_F18;
		else {
			gpstm_rep_event(gpstm, CEVNT_BADREPLY);
			return;
		}
		gpstm_doevent(gpstm->unit, event);
		return;
	} else if (gpstm->lencode == 13) {
		/*
	 	 * Check timecode format 0
	 	 */
		if (!isdigit(cp[0])	/* day of year */
		    || !isdigit(cp[1])
		    || !isdigit(cp[2])
		    || cp[3] != ':'	/* : separator */
		    || !isdigit(cp[4])	/* hours */
		    || !isdigit(cp[5])
		    || cp[6] != ':'	/* : separator */
		    || !isdigit(cp[7])	/* minutes */
		    || !isdigit(cp[8])
		    || cp[9] != ':'	/* : separator */
		    || !isdigit(cp[10])	/* seconds */
		    || !isdigit(cp[11]))
		{
			gpstm->badformat++;
			gpstm_rep_event(gpstm, CEVNT_BADREPLY);
			return;
		}

		/*
		 * Convert format 0 and check values 
		 */
		gpstm->year = 0;		/* fake */
		gpstm->day = cp[0] - '0';
		gpstm->day = MULBY10(gpstm->day) + cp[1] - '0';
		gpstm->day = MULBY10(gpstm->day) + cp[2] - '0';
		gpstm->hour = MULBY10(cp[4] - '0') + cp[5] - '0';
		gpstm->minute = MULBY10(cp[7] - '0') + cp[8] -  '0';
		gpstm->second = MULBY10(cp[10] - '0') + cp[11] - '0';
		gpstm->msec = 0;

		if (cp[12] != ' ' && cp[12] != '.' && cp[12] != '*')
			gpstm->leap = LEAP_NOTINSYNC;
		else
			gpstm->lasttime = current_time;

		if (gpstm->day < 1 || gpstm->day > 366) {
			gpstm->baddata++;
			gpstm_rep_event(gpstm, CEVNT_BADDATE);
			return;
		}
		if (gpstm->hour > 23 || gpstm->minute > 59
		    || gpstm->second > 59) {
			gpstm->baddata++;
			gpstm_rep_event(gpstm, CEVNT_BADTIME);
			return;
		}
		gpstm_doevent(gpstm->unit, e_TS);
	} else {
		gpstm_rep_event(gpstm, CEVNT_BADREPLY);
		return;
	}

	/*
	 * The clock will blurt a timecode every second but we only
	 * want one when polled.  If we havn't been polled, bail out.
	 */
	if (!gpstm->polled)
		return;

	/*
	 * Now, compute the reference time value. Use the heavy
	 * machinery for the seconds and the millisecond field for the
	 * fraction when present.
         *
	 * this code does not yet know how to do the years
	 */
	tstmp = gpstm->lastrec;
	if (!clocktime(gpstm->day, gpstm->hour, gpstm->minute,
		       gpstm->second, GMT, tstmp.l_ui,
		       &gpstm->yearstart, &gpstm->lastref.l_ui))
	{
		gpstm->baddata++;
		gpstm_rep_event(gpstm, CEVNT_BADTIME);
		return;
	}
	MSUTOTSF(gpstm->msec, gpstm->lastref.l_uf);

	i = ((int)(gpstm->coderecv)) % NCODES;
	gpstm->offset[i] = gpstm->lastref;
	L_SUB(&gpstm->offset[i], &tstmp);
	if (gpstm->coderecv == 0)
		for (i = 1; i < NCODES; i++)
			gpstm->offset[i] = gpstm->offset[0];

	gpstm->coderecv++;

	/*
	 * Process the median filter, and pass the
	 * offset and dispersion along. 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 (!gpstm_process(gpstm, &tstmp, &dispersion)) {
		gpstm->baddata++;
		gpstm_rep_event(gpstm, CEVNT_BADTIME);
		return;
	}
	refclock_receive(gpstm->peer, &tstmp, GMT, dispersion,
			 &gpstm->lastrec, &gpstm->lastrec, gpstm->leap);

	/*
	 * We have succedded in answering the poll.  Turn off the flag
	 */
	gpstm->polled = 0;
}

/*
 * gpstm_send - time to send the clock a signal to cough up a time sample
 */
static void
gpstm_send(gpstm, cmd)
	struct gpstm_unit *gpstm;
	char *cmd;
{
#ifdef DEBUG
	if (debug) {
		printf("gpstm_send(gpstm%d): %s\n", gpstm->unit, cmd);
	}
#endif
	if (!readonlyclockflag[gpstm->unit]) {
		register int len = strlen(cmd);

		if (write(gpstm->io.fd, cmd, len) != len) {
			syslog(LOG_ERR, "gpstm_send: unit %d: %m",
			       gpstm->unit);
			gpstm_rep_event(gpstm, CEVNT_FAULT);
		}
	}
}

/*
 * state machine for initializing the clock
 */

static void
gpstm_doevent(unit, event)
	int unit;
	enum gpstm_event event;
{
	struct gpstm_unit *gpstm = gpstm_units[unit];

#ifdef DEBUG
	if (debug) {
		printf("gpstm_doevent(gpstm%d, %d)\n", unit, (int)event);
	}
#endif
	if (event == e_TS && State[unit] != F51 && State[unit] != F08) {
		gpstm_send(gpstm, "\03\r");
	}

	switch (event) {
	case e_Init:
		gpstm_send(gpstm, "F18\r");
		State[unit] = Start;
		break;
	case e_F18:
		gpstm_send(gpstm, "F50\r");
		State[unit] = F18;
		break;
	case e_F50:
		gpstm_send(gpstm, "F51\r");
		State[unit] = F50;
		break;
	case e_F51:
		gpstm_send(gpstm, "F08\r");
		State[unit] = F51;
		break;
	case e_TS:
		/* nothing to send - we like this mode */
		State[unit] = F08;
		break;
	}
}

static void
gpstm_initstate(unit) {
	State[unit] = Base;		/* just in case */
	gpstm_doevent(unit, e_Init);
}

/*
 * gpstm_process - process a pile of samples from the clock
 */
static char
gpstm_process(gpstm, offset, dispersion)
	struct gpstm_unit *gpstm;
	l_fp *offset;
	u_fp *dispersion;
{
	register int i, j;
	register U_LONG tmp_ui, tmp_uf;
	int not_median1 = -1;	/* XXX correct? */
	int not_median2 = -1;	/* XXX correct? */
	int median;
	u_fp disp_tmp, disp_tmp2;

	/*
	 * This code implements a three-stage median filter. First, we
         * check if the samples are within 125 ms of each other. If not,
	 * dump the sample set. We take the median of the three offsets
	 * and use that as the sample offset. We take the maximum
	 * difference and use that as the sample dispersion. There
	 * probably is not much to be gained by a longer filter, since
	 * the clock filter in ntp_proto should do its thing.
	 */
	disp_tmp2 = 0;
	for (i = 0; i < NCODES-1; i++) {
		for (j = i+1; j < NCODES; j++) {
			tmp_ui = gpstm->offset[i].l_ui;
			tmp_uf = gpstm->offset[i].l_uf;
			M_SUB(tmp_ui, tmp_uf, gpstm->offset[j].l_ui,
				gpstm->offset[j].l_uf);
			if (M_ISNEG(tmp_ui, tmp_uf)) {
				M_NEG(tmp_ui, tmp_uf);
			}
			if (tmp_ui != 0 || tmp_uf > CODEDIFF) {
				return 0;
			}
			disp_tmp = MFPTOFP(0, tmp_uf);
			if (disp_tmp > disp_tmp2) {
				disp_tmp2 = disp_tmp;
				not_median1 = i;
				not_median2 = j;
			}
		}
	}

	/*
	 * It seems as if all are within 125 ms of each other.
	 * Now to determine the median of the three. Whlie the
	 * 125 ms check was going on, we also subtly catch the
	 * dispersion and set-up for a very easy median calculation.
	 * The largest difference between any two samples constitutes
	 * the dispersion. The sample not involve in the dispersion is
	 * the median sample. EASY!
	 */
	if (gpstm->lasttime == 0 || disp_tmp2 > MAXDISPERSE)
	    disp_tmp2 = MAXDISPERSE;
	if (not_median1 == 0) {
		if (not_median2 == 1)
		    median = 2;
		else
		    median = 1;
        } else {
		median = 0;
        }
	*offset = gpstm->offset[median];
	*dispersion = disp_tmp2;
	return 1;
}

/*
 * gpstm_poll - called by the transmit procedure
 */
static void
gpstm_poll(unit, peer)
	int unit;
	struct peer *peer;
{
	struct gpstm_unit *gpstm;

	/*
	 * You don't need to poll this clock.  It puts out timecodes
	 * once per second.  If asked for a timestamp, take note.
	 * The next time a timecode comes in, it will be fed back.
	 */
	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "gpstm_poll: unit %d invalid", unit);
		return;
	}
	if (!unitinuse[unit]) {
		syslog(LOG_ERR, "gpstm_poll: unit %d not in use", unit);
		return;
	}
	gpstm = gpstm_units[unit];
	if ((current_time - gpstm->lasttime) > 150) {
		gpstm->noreply++;
		gpstm_rep_event(gpstm_units[unit], CEVNT_TIMEOUT);
		gpstm_initstate(gpstm->unit);
	}

	/*
	 * polled every 64 seconds.  Ask our receiver to hand in a timestamp.
	 */
	gpstm->polled = 1;
	gpstm->polls++;
}

/*
 * gpstm_control - set fudge factors, return statistics
 */
static void
gpstm_control(unit, in, out)
	u_int unit;
	struct refclockstat *in;
	struct refclockstat *out;
{
	register struct gpstm_unit *gpstm;

	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "gpstm_control: unit %d invalid", unit);
		return;
	}

	if (in != 0) {
		if (in->haveflags & CLK_HAVETIME1)
			fudgefactor1[unit] = in->fudgetime1;
		if (in->haveflags & CLK_HAVETIME2)
			fudgefactor2[unit] = in->fudgetime2;
		if (in->haveflags & CLK_HAVEVAL1)
			stratumtouse[unit] = (u_char)(in->fudgeval1);
		if (in->haveflags & CLK_HAVEVAL2)
			refid[unit] = in->fudgeval2;
		if (in->haveflags & CLK_HAVEFLAG1)
			readonlyclockflag[unit] = in->flags & CLK_FLAG1;
		if (unitinuse[unit]) {
			struct peer *peer;

			peer = gpstm_units[unit]->peer;
			peer->stratum = stratumtouse[unit];
			peer->refid = refid[unit];
		}
	}

	if (out != 0) {
		memset((char *)out, 0, sizeof (struct refclockstat));
		out->type = REFCLK_GPSTM_TRUETIME;
		out->haveflags = CLK_HAVETIME1 | CLK_HAVETIME2 | CLK_HAVEVAL1 |
		    CLK_HAVEVAL2 | CLK_HAVEFLAG1;
		out->clockdesc = DESCRIPTION;
		out->fudgetime1 = fudgefactor1[unit];
		out->fudgetime2 = fudgefactor2[unit];
		out->fudgeval1 = (LONG)stratumtouse[unit];
		out->fudgeval2 = refid[unit];
		out->flags = readonlyclockflag[unit];
		if (unitinuse[unit]) {
			gpstm = gpstm_units[unit];
			out->lencode = gpstm->lencode;
			out->lastcode = gpstm->lastcode;
			out->timereset = current_time - gpstm->timestarted;
			out->polls = gpstm->polls;
			out->noresponse = gpstm->noreply;
			out->badformat = gpstm->badformat;
			out->baddata = gpstm->baddata;
			out->lastevent = gpstm->lastevent;
			out->currentstatus = gpstm->status;
		}
	}
}

/*
 * gpstm_buginfo - return clock dependent debugging info
 */
static void
gpstm_buginfo(unit, bug)
	int unit;
	register struct refclockbug *bug;
{
	register struct gpstm_unit *gpstm;

	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "gpstm_buginfo: unit %d invalid", unit);
		return;
	}

	if (!unitinuse[unit])
		return;
	gpstm = gpstm_units[unit];

	bug->nvalues = 11;
	bug->ntimes = 5;
	if (gpstm->lasttime != 0)
		bug->values[0] = current_time - gpstm->lasttime;
	else
		bug->values[0] = 0;
	bug->values[1] = (U_LONG)gpstm->reason;
	bug->values[2] = (U_LONG)gpstm->year;
	bug->values[3] = (U_LONG)gpstm->day;
	bug->values[4] = (U_LONG)gpstm->hour;
	bug->values[5] = (U_LONG)gpstm->minute;
	bug->values[6] = (U_LONG)gpstm->second;
	bug->values[7] = (U_LONG)gpstm->msec;
	bug->values[8] = gpstm->noreply;
	bug->values[9] = gpstm->yearstart;
	bug->values[10] = gpstm->quality;
	bug->stimes = 0x1c;
	bug->times[0] = gpstm->lastref;
	bug->times[1] = gpstm->lastrec;
	bug->times[2] = gpstm->offset[0];
	bug->times[3] = gpstm->offset[1];
	bug->times[4] = gpstm->offset[2];
}

#endif /*GPSTM et al*/
