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