154359Sroberto/*
254359Sroberto * refclock_as2201 - clock driver for the Austron 2201A GPS
354359Sroberto *	Timing Receiver
454359Sroberto */
554359Sroberto#ifdef HAVE_CONFIG_H
654359Sroberto#include <config.h>
754359Sroberto#endif
854359Sroberto
954359Sroberto#if defined(REFCLOCK) && defined(CLOCK_AS2201)
1054359Sroberto
1154359Sroberto#include "ntpd.h"
1254359Sroberto#include "ntp_io.h"
1354359Sroberto#include "ntp_refclock.h"
1454359Sroberto#include "ntp_unixtime.h"
1554359Sroberto#include "ntp_stdlib.h"
1654359Sroberto
1782498Sroberto#include <stdio.h>
1882498Sroberto#include <ctype.h>
1982498Sroberto
2054359Sroberto/*
2154359Sroberto * This driver supports the Austron 2200A/2201A GPS Receiver with
2254359Sroberto * Buffered RS-232-C Interface Module. Note that the original 2200/2201
2354359Sroberto * receivers will not work reliably with this driver, since the older
2454359Sroberto * design cannot accept input commands at any reasonable data rate.
2554359Sroberto *
2654359Sroberto * The program sends a "*toc\r" to the radio and expects a response of
2754359Sroberto * the form "yy:ddd:hh:mm:ss.mmm\r" where yy = year of century, ddd =
2854359Sroberto * day of year, hh:mm:ss = second of day and mmm = millisecond of
2954359Sroberto * second. Then, it sends statistics commands to the radio and expects
3054359Sroberto * a multi-line reply showing the corresponding statistics or other
3154359Sroberto * selected data. Statistics commands are sent in order as determined by
3254359Sroberto * a vector of commands; these might have to be changed with different
3354359Sroberto * radio options. If flag4 of the fudge configuration command is set to
3454359Sroberto * 1, the statistics data are written to the clockstats file for later
3554359Sroberto * processing.
3654359Sroberto *
3754359Sroberto * In order for this code to work, the radio must be placed in non-
3854359Sroberto * interactive mode using the "off" command and with a single <cr>
3954359Sroberto * response using the "term cr" command. The setting of the "echo"
4054359Sroberto * and "df" commands does not matter. The radio should select UTC
4154359Sroberto * timescale using the "ts utc" command.
4254359Sroberto *
4354359Sroberto * There are two modes of operation for this driver. The first with
4454359Sroberto * default configuration is used with stock kernels and serial-line
4554359Sroberto * drivers and works with almost any machine. In this mode the driver
4654359Sroberto * assumes the radio captures a timestamp upon receipt of the "*" that
4754359Sroberto * begins the driver query. Accuracies in this mode are in the order of
4854359Sroberto * a millisecond or two and the receiver can be connected to only one
4954359Sroberto * host.
5054359Sroberto *
5154359Sroberto * The second mode of operation can be used for SunOS kernels that have
5254359Sroberto * been modified with the ppsclock streams module included in this
5354359Sroberto * distribution. The mode is enabled if flag3 of the fudge configuration
5454359Sroberto * command has been set to 1. In this mode a precise timestamp is
5554359Sroberto * available using a gadget box and 1-pps signal from the receiver. This
5654359Sroberto * improves the accuracy to the order of a few tens of microseconds. In
5754359Sroberto * addition, the serial output and 1-pps signal can be bussed to more
5854359Sroberto * than one hosts, but only one of them should be connected to the
5954359Sroberto * radio input data line.
6054359Sroberto */
6154359Sroberto
6254359Sroberto/*
6354359Sroberto * GPS Definitions
6454359Sroberto */
6554359Sroberto#define SMAX		200	/* statistics buffer length */
6654359Sroberto#define	DEVICE		"/dev/gps%d" /* device name and unit */
6754359Sroberto#define	SPEED232	B9600	/* uart speed (9600 baud) */
6854359Sroberto#define	PRECISION	(-20)	/* precision assumed (about 1 us) */
6954359Sroberto#define	REFID		"GPS\0"	/* reference ID */
7054359Sroberto#define	DESCRIPTION	"Austron 2201A GPS Receiver" /* WRU */
7154359Sroberto
7254359Sroberto#define	LENTOC		19	/* yy:ddd:hh:mm:ss.mmm timecode lngth */
7354359Sroberto
7454359Sroberto/*
7554359Sroberto * AS2201 unit control structure.
7654359Sroberto */
7754359Srobertostruct as2201unit {
7854359Sroberto	char	*lastptr;	/* statistics buffer pointer */
7954359Sroberto	char	stats[SMAX];	/* statistics buffer */
8054359Sroberto	int	linect;		/* count of lines remaining */
8154359Sroberto	int	index;		/* current statistics command */
8254359Sroberto};
8354359Sroberto
8454359Sroberto/*
8554359Sroberto * Radio commands to extract statitistics
8654359Sroberto *
8754359Sroberto * A command consists of an ASCII string terminated by a <cr> (\r). The
8854359Sroberto * command list consist of a sequence of commands terminated by a null
8954359Sroberto * string ("\0"). One command from the list is sent immediately
9054359Sroberto * following each received timecode (*toc\r command) and the ASCII
9154359Sroberto * strings received from the radio are saved along with the timecode in
9254359Sroberto * the clockstats file. Subsequent commands are sent at each timecode,
9354359Sroberto * with the last one in the list followed by the first one. The data
9454359Sroberto * received from the radio consist of ASCII strings, each terminated by
9554359Sroberto * a <cr> (\r) character. The number of strings for each command is
9654359Sroberto * specified as the first line of output as an ASCII-encode number. Note
9754359Sroberto * that the ETF command requires the Input Buffer Module and the LORAN
9854359Sroberto * commands require the LORAN Assist Module. However, if these modules
9954359Sroberto * are not installed, the radio and this driver will continue to operate
10054359Sroberto * successfuly, but no data will be captured for these commands.
10154359Sroberto */
10254359Srobertostatic char stat_command[][30] = {
10354359Sroberto	"ITF\r",		/* internal time/frequency */
10454359Sroberto	"ETF\r",		/* external time/frequency */
10554359Sroberto	"LORAN ENSEMBLE\r",	/* GPS/LORAN ensemble statistics */
10654359Sroberto	"LORAN TDATA\r",	/* LORAN signal data */
10754359Sroberto	"ID;OPT;VER\r",		/* model; options; software version */
10854359Sroberto
10954359Sroberto	"ITF\r",		/* internal time/frequency */
11054359Sroberto	"ETF\r",		/* external time/frequency */
11154359Sroberto	"LORAN ENSEMBLE\r",	/* GPS/LORAN ensemble statistics */
11254359Sroberto	"TRSTAT\r",		/* satellite tracking status */
11354359Sroberto	"POS;PPS;PPSOFF\r",	/* position, pps source, offsets */
11454359Sroberto
11554359Sroberto	"ITF\r",		/* internal time/frequency */
11654359Sroberto	"ETF\r",		/* external time/frequency */
11754359Sroberto	"LORAN ENSEMBLE\r",	/* GPS/LORAN ensemble statistics */
11854359Sroberto	"LORAN TDATA\r",	/* LORAN signal data */
11954359Sroberto	"UTC\r",			/* UTC leap info */
12054359Sroberto
12154359Sroberto	"ITF\r",		/* internal time/frequency */
12254359Sroberto	"ETF\r",		/* external time/frequency */
12354359Sroberto	"LORAN ENSEMBLE\r",	/* GPS/LORAN ensemble statistics */
12454359Sroberto	"TRSTAT\r",		/* satellite tracking status */
12554359Sroberto	"OSC;ET;TEMP\r",	/* osc type; tune volts; oven temp */
12654359Sroberto	"\0"			/* end of table */
12754359Sroberto};
12854359Sroberto
12954359Sroberto/*
13054359Sroberto * Function prototypes
13154359Sroberto */
132280849Scystatic	int	as2201_start	(int, struct peer *);
133280849Scystatic	void	as2201_shutdown	(int, struct peer *);
134280849Scystatic	void	as2201_receive	(struct recvbuf *);
135280849Scystatic	void	as2201_poll	(int, struct peer *);
13654359Sroberto
13754359Sroberto/*
13854359Sroberto * Transfer vector
13954359Sroberto */
14054359Srobertostruct	refclock refclock_as2201 = {
14154359Sroberto	as2201_start,		/* start up driver */
14254359Sroberto	as2201_shutdown,	/* shut down driver */
14354359Sroberto	as2201_poll,		/* transmit poll message */
14454359Sroberto	noentry,		/* not used (old as2201_control) */
14554359Sroberto	noentry,		/* initialize driver (not used) */
14654359Sroberto	noentry,		/* not used (old as2201_buginfo) */
14754359Sroberto	NOFLAGS			/* not used */
14854359Sroberto};
14954359Sroberto
15054359Sroberto
15154359Sroberto/*
15254359Sroberto * as2201_start - open the devices and initialize data for processing
15354359Sroberto */
15454359Srobertostatic int
15554359Srobertoas2201_start(
15654359Sroberto	int unit,
15754359Sroberto	struct peer *peer
15854359Sroberto	)
15954359Sroberto{
16054359Sroberto	register struct as2201unit *up;
16154359Sroberto	struct refclockproc *pp;
16254359Sroberto	int fd;
16354359Sroberto	char gpsdev[20];
16454359Sroberto
16554359Sroberto	/*
16654359Sroberto	 * Open serial port. Use CLK line discipline, if available.
16754359Sroberto	 */
168280849Scy	snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit);
169280849Scy	fd = refclock_open(gpsdev, SPEED232, LDISC_CLK);
170280849Scy	if (fd <= 0)
17154359Sroberto		return (0);
17254359Sroberto
17354359Sroberto	/*
17454359Sroberto	 * Allocate and initialize unit structure
17554359Sroberto	 */
176280849Scy	up = emalloc_zero(sizeof(*up));
17754359Sroberto	pp = peer->procptr;
17854359Sroberto	pp->io.clock_recv = as2201_receive;
179280849Scy	pp->io.srcclock = peer;
18054359Sroberto	pp->io.datalen = 0;
18154359Sroberto	pp->io.fd = fd;
18254359Sroberto	if (!io_addclock(&pp->io)) {
183280849Scy		close(fd);
184280849Scy		pp->io.fd = -1;
18554359Sroberto		free(up);
18654359Sroberto		return (0);
18754359Sroberto	}
188280849Scy	pp->unitptr = up;
18954359Sroberto
19054359Sroberto	/*
19154359Sroberto	 * Initialize miscellaneous variables
19254359Sroberto	 */
19354359Sroberto	peer->precision = PRECISION;
19454359Sroberto	pp->clockdesc = DESCRIPTION;
19554359Sroberto	memcpy((char *)&pp->refid, REFID, 4);
19654359Sroberto	up->lastptr = up->stats;
19754359Sroberto	up->index = 0;
19854359Sroberto	return (1);
19954359Sroberto}
20054359Sroberto
20154359Sroberto
20254359Sroberto/*
20354359Sroberto * as2201_shutdown - shut down the clock
20454359Sroberto */
20554359Srobertostatic void
20654359Srobertoas2201_shutdown(
20754359Sroberto	int unit,
20854359Sroberto	struct peer *peer
20954359Sroberto	)
21054359Sroberto{
21154359Sroberto	register struct as2201unit *up;
21254359Sroberto	struct refclockproc *pp;
21354359Sroberto
21454359Sroberto	pp = peer->procptr;
215280849Scy	up = pp->unitptr;
216280849Scy	if (-1 != pp->io.fd)
217280849Scy		io_closeclock(&pp->io);
218280849Scy	if (NULL != up)
219280849Scy		free(up);
22054359Sroberto}
22154359Sroberto
22254359Sroberto
22354359Sroberto/*
22454359Sroberto * as2201__receive - receive data from the serial interface
22554359Sroberto */
22654359Srobertostatic void
22754359Srobertoas2201_receive(
22854359Sroberto	struct recvbuf *rbufp
22954359Sroberto	)
23054359Sroberto{
23154359Sroberto	register struct as2201unit *up;
23254359Sroberto	struct refclockproc *pp;
23354359Sroberto	struct peer *peer;
23454359Sroberto	l_fp trtmp;
235280849Scy	size_t octets;
23654359Sroberto
23754359Sroberto	/*
23854359Sroberto	 * Initialize pointers and read the timecode and timestamp.
23954359Sroberto	 */
240280849Scy	peer = rbufp->recv_peer;
24154359Sroberto	pp = peer->procptr;
242280849Scy	up = pp->unitptr;
24354359Sroberto	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
24454359Sroberto#ifdef DEBUG
24554359Sroberto	if (debug)
24654359Sroberto	    printf("gps: timecode %d %d %s\n",
24754359Sroberto		   up->linect, pp->lencode, pp->a_lastcode);
24854359Sroberto#endif
24954359Sroberto	if (pp->lencode == 0)
25054359Sroberto	    return;
25154359Sroberto
25254359Sroberto	/*
25354359Sroberto	 * If linect is greater than zero, we must be in the middle of a
25454359Sroberto	 * statistics operation, so simply tack the received data at the
25554359Sroberto	 * end of the statistics string. If not, we could either have
25654359Sroberto	 * just received the timecode itself or a decimal number
25754359Sroberto	 * indicating the number of following lines of the statistics
25854359Sroberto	 * reply. In the former case, write the accumulated statistics
25954359Sroberto	 * data to the clockstats file and continue onward to process
26054359Sroberto	 * the timecode; in the later case, save the number of lines and
26154359Sroberto	 * quietly return.
26254359Sroberto	 */
26354359Sroberto	if (pp->sloppyclockflag & CLK_FLAG2)
26454359Sroberto		pp->lastrec = trtmp;
26554359Sroberto	if (up->linect > 0) {
26654359Sroberto		up->linect--;
26754359Sroberto		if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2)
26854359Sroberto		    return;
26954359Sroberto		*up->lastptr++ = ' ';
270280849Scy		memcpy(up->lastptr, pp->a_lastcode, 1 + pp->lencode);
27154359Sroberto		up->lastptr += pp->lencode;
27254359Sroberto		return;
27354359Sroberto	} else {
27454359Sroberto		if (pp->lencode == 1) {
27554359Sroberto			up->linect = atoi(pp->a_lastcode);
27654359Sroberto			return;
27754359Sroberto		} else {
27854359Sroberto			record_clock_stats(&peer->srcadr, up->stats);
27954359Sroberto#ifdef DEBUG
28054359Sroberto			if (debug)
28154359Sroberto			    printf("gps: stat %s\n", up->stats);
28254359Sroberto#endif
28354359Sroberto		}
28454359Sroberto	}
28554359Sroberto	up->lastptr = up->stats;
28654359Sroberto	*up->lastptr = '\0';
28754359Sroberto
28854359Sroberto	/*
28954359Sroberto	 * We get down to business, check the timecode format and decode
29054359Sroberto	 * its contents. If the timecode has invalid length or is not in
29154359Sroberto	 * proper format, we declare bad format and exit.
29254359Sroberto	 */
29354359Sroberto	if (pp->lencode < LENTOC) {
29454359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
29554359Sroberto		return;
29654359Sroberto	}
29754359Sroberto
29854359Sroberto	/*
29954359Sroberto	 * Timecode format: "yy:ddd:hh:mm:ss.mmm"
30054359Sroberto	 */
301132451Sroberto	if (sscanf(pp->a_lastcode, "%2d:%3d:%2d:%2d:%2d.%3ld", &pp->year,
302132451Sroberto		   &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec)
30354359Sroberto	    != 6) {
30454359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
30554359Sroberto		return;
30654359Sroberto	}
307132451Sroberto	pp->nsec *= 1000000;
30854359Sroberto
30954359Sroberto	/*
31054359Sroberto	 * Test for synchronization (this is a temporary crock).
31154359Sroberto	 */
31254359Sroberto	if (pp->a_lastcode[2] != ':')
31354359Sroberto		pp->leap = LEAP_NOTINSYNC;
31454359Sroberto	else
31554359Sroberto		pp->leap = LEAP_NOWARNING;
31654359Sroberto
31754359Sroberto	/*
31854359Sroberto	 * Process the new sample in the median filter and determine the
31954359Sroberto	 * timecode timestamp.
32054359Sroberto	 */
32154359Sroberto	if (!refclock_process(pp)) {
32254359Sroberto		refclock_report(peer, CEVNT_BADTIME);
32354359Sroberto		return;
32454359Sroberto	}
32554359Sroberto
32654359Sroberto	/*
32754359Sroberto	 * If CLK_FLAG4 is set, initialize the statistics buffer and
32854359Sroberto	 * send the next command. If not, simply write the timecode to
32954359Sroberto	 * the clockstats file.
33054359Sroberto	 */
331280849Scy	if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2)
332280849Scy	    return;
333280849Scy	memcpy(up->lastptr, pp->a_lastcode, pp->lencode);
33454359Sroberto	up->lastptr += pp->lencode;
33554359Sroberto	if (pp->sloppyclockflag & CLK_FLAG4) {
336280849Scy		octets = strlen(stat_command[up->index]);
337280849Scy		if ((int)(up->lastptr - up->stats + 1 + octets) > SMAX - 2)
338280849Scy		    return;
33954359Sroberto		*up->lastptr++ = ' ';
340280849Scy		memcpy(up->lastptr, stat_command[up->index], octets);
341280849Scy		up->lastptr += octets - 1;
34254359Sroberto		*up->lastptr = '\0';
34354359Sroberto		(void)write(pp->io.fd, stat_command[up->index],
34454359Sroberto		    strlen(stat_command[up->index]));
34554359Sroberto		up->index++;
34654359Sroberto		if (*stat_command[up->index] == '\0')
34754359Sroberto			up->index = 0;
34854359Sroberto	}
34954359Sroberto}
35054359Sroberto
35154359Sroberto
35254359Sroberto/*
35354359Sroberto * as2201_poll - called by the transmit procedure
35454359Sroberto *
35554359Sroberto * We go to great pains to avoid changing state here, since there may be
35654359Sroberto * more than one eavesdropper receiving the same timecode.
35754359Sroberto */
35854359Srobertostatic void
35954359Srobertoas2201_poll(
36054359Sroberto	int unit,
36154359Sroberto	struct peer *peer
36254359Sroberto	)
36354359Sroberto{
36454359Sroberto	struct refclockproc *pp;
36554359Sroberto
36654359Sroberto	/*
36754359Sroberto	 * Send a "\r*toc\r" to get things going. We go to great pains
36854359Sroberto	 * to avoid changing state, since there may be more than one
36954359Sroberto	 * eavesdropper watching the radio.
37054359Sroberto	 */
37154359Sroberto	pp = peer->procptr;
37254359Sroberto	if (write(pp->io.fd, "\r*toc\r", 6) != 6) {
37354359Sroberto		refclock_report(peer, CEVNT_FAULT);
37454359Sroberto	} else {
37554359Sroberto		pp->polls++;
37654359Sroberto		if (!(pp->sloppyclockflag & CLK_FLAG2))
37754359Sroberto			get_systime(&pp->lastrec);
37854359Sroberto	}
37954359Sroberto        if (pp->coderecv == pp->codeproc) {
38054359Sroberto                refclock_report(peer, CEVNT_TIMEOUT);
38154359Sroberto                return;
38254359Sroberto        }
38354359Sroberto        refclock_receive(peer);
38454359Sroberto}
38554359Sroberto
38654359Sroberto#else
38754359Srobertoint refclock_as2201_bs;
38854359Sroberto#endif /* REFCLOCK */
389