1*eabc0478Schristos /* $NetBSD: refclock_as2201.c,v 1.6 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_as2201 - clock driver for the Austron 2201A GPS 5abb0f93cSkardel * Timing Receiver 6abb0f93cSkardel */ 7abb0f93cSkardel #ifdef HAVE_CONFIG_H 8abb0f93cSkardel #include <config.h> 9abb0f93cSkardel #endif 10abb0f93cSkardel 11abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_AS2201) 12abb0f93cSkardel 13abb0f93cSkardel #include "ntpd.h" 14abb0f93cSkardel #include "ntp_io.h" 15abb0f93cSkardel #include "ntp_refclock.h" 16abb0f93cSkardel #include "ntp_unixtime.h" 17abb0f93cSkardel #include "ntp_stdlib.h" 18abb0f93cSkardel 19abb0f93cSkardel #include <stdio.h> 20abb0f93cSkardel #include <ctype.h> 21abb0f93cSkardel 22abb0f93cSkardel /* 23abb0f93cSkardel * This driver supports the Austron 2200A/2201A GPS Receiver with 24abb0f93cSkardel * Buffered RS-232-C Interface Module. Note that the original 2200/2201 25abb0f93cSkardel * receivers will not work reliably with this driver, since the older 26abb0f93cSkardel * design cannot accept input commands at any reasonable data rate. 27abb0f93cSkardel * 28abb0f93cSkardel * The program sends a "*toc\r" to the radio and expects a response of 29abb0f93cSkardel * the form "yy:ddd:hh:mm:ss.mmm\r" where yy = year of century, ddd = 30abb0f93cSkardel * day of year, hh:mm:ss = second of day and mmm = millisecond of 31abb0f93cSkardel * second. Then, it sends statistics commands to the radio and expects 32abb0f93cSkardel * a multi-line reply showing the corresponding statistics or other 33abb0f93cSkardel * selected data. Statistics commands are sent in order as determined by 34abb0f93cSkardel * a vector of commands; these might have to be changed with different 35abb0f93cSkardel * radio options. If flag4 of the fudge configuration command is set to 36abb0f93cSkardel * 1, the statistics data are written to the clockstats file for later 37abb0f93cSkardel * processing. 38abb0f93cSkardel * 39abb0f93cSkardel * In order for this code to work, the radio must be placed in non- 40abb0f93cSkardel * interactive mode using the "off" command and with a single <cr> 41abb0f93cSkardel * response using the "term cr" command. The setting of the "echo" 42abb0f93cSkardel * and "df" commands does not matter. The radio should select UTC 43abb0f93cSkardel * timescale using the "ts utc" command. 44abb0f93cSkardel * 45abb0f93cSkardel * There are two modes of operation for this driver. The first with 46abb0f93cSkardel * default configuration is used with stock kernels and serial-line 47abb0f93cSkardel * drivers and works with almost any machine. In this mode the driver 48abb0f93cSkardel * assumes the radio captures a timestamp upon receipt of the "*" that 49abb0f93cSkardel * begins the driver query. Accuracies in this mode are in the order of 50abb0f93cSkardel * a millisecond or two and the receiver can be connected to only one 51abb0f93cSkardel * host. 52abb0f93cSkardel * 53abb0f93cSkardel * The second mode of operation can be used for SunOS kernels that have 54abb0f93cSkardel * been modified with the ppsclock streams module included in this 55abb0f93cSkardel * distribution. The mode is enabled if flag3 of the fudge configuration 56abb0f93cSkardel * command has been set to 1. In this mode a precise timestamp is 57abb0f93cSkardel * available using a gadget box and 1-pps signal from the receiver. This 58abb0f93cSkardel * improves the accuracy to the order of a few tens of microseconds. In 59abb0f93cSkardel * addition, the serial output and 1-pps signal can be bussed to more 60abb0f93cSkardel * than one hosts, but only one of them should be connected to the 61abb0f93cSkardel * radio input data line. 62abb0f93cSkardel */ 63abb0f93cSkardel 64abb0f93cSkardel /* 65abb0f93cSkardel * GPS Definitions 66abb0f93cSkardel */ 67abb0f93cSkardel #define SMAX 200 /* statistics buffer length */ 68abb0f93cSkardel #define DEVICE "/dev/gps%d" /* device name and unit */ 69abb0f93cSkardel #define SPEED232 B9600 /* uart speed (9600 baud) */ 70abb0f93cSkardel #define PRECISION (-20) /* precision assumed (about 1 us) */ 71abb0f93cSkardel #define REFID "GPS\0" /* reference ID */ 72abb0f93cSkardel #define DESCRIPTION "Austron 2201A GPS Receiver" /* WRU */ 73abb0f93cSkardel 74abb0f93cSkardel #define LENTOC 19 /* yy:ddd:hh:mm:ss.mmm timecode lngth */ 75abb0f93cSkardel 76abb0f93cSkardel /* 77abb0f93cSkardel * AS2201 unit control structure. 78abb0f93cSkardel */ 79abb0f93cSkardel struct as2201unit { 80abb0f93cSkardel char *lastptr; /* statistics buffer pointer */ 81abb0f93cSkardel char stats[SMAX]; /* statistics buffer */ 82abb0f93cSkardel int linect; /* count of lines remaining */ 83abb0f93cSkardel int index; /* current statistics command */ 84abb0f93cSkardel }; 85abb0f93cSkardel 86abb0f93cSkardel /* 87abb0f93cSkardel * Radio commands to extract statitistics 88abb0f93cSkardel * 89abb0f93cSkardel * A command consists of an ASCII string terminated by a <cr> (\r). The 90abb0f93cSkardel * command list consist of a sequence of commands terminated by a null 91abb0f93cSkardel * string ("\0"). One command from the list is sent immediately 92abb0f93cSkardel * following each received timecode (*toc\r command) and the ASCII 93abb0f93cSkardel * strings received from the radio are saved along with the timecode in 94abb0f93cSkardel * the clockstats file. Subsequent commands are sent at each timecode, 95abb0f93cSkardel * with the last one in the list followed by the first one. The data 96abb0f93cSkardel * received from the radio consist of ASCII strings, each terminated by 97abb0f93cSkardel * a <cr> (\r) character. The number of strings for each command is 98abb0f93cSkardel * specified as the first line of output as an ASCII-encode number. Note 99abb0f93cSkardel * that the ETF command requires the Input Buffer Module and the LORAN 100abb0f93cSkardel * commands require the LORAN Assist Module. However, if these modules 101abb0f93cSkardel * are not installed, the radio and this driver will continue to operate 102abb0f93cSkardel * successfuly, but no data will be captured for these commands. 103abb0f93cSkardel */ 104abb0f93cSkardel static char stat_command[][30] = { 105abb0f93cSkardel "ITF\r", /* internal time/frequency */ 106abb0f93cSkardel "ETF\r", /* external time/frequency */ 107abb0f93cSkardel "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ 108abb0f93cSkardel "LORAN TDATA\r", /* LORAN signal data */ 109abb0f93cSkardel "ID;OPT;VER\r", /* model; options; software version */ 110abb0f93cSkardel 111abb0f93cSkardel "ITF\r", /* internal time/frequency */ 112abb0f93cSkardel "ETF\r", /* external time/frequency */ 113abb0f93cSkardel "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ 114abb0f93cSkardel "TRSTAT\r", /* satellite tracking status */ 115abb0f93cSkardel "POS;PPS;PPSOFF\r", /* position, pps source, offsets */ 116abb0f93cSkardel 117abb0f93cSkardel "ITF\r", /* internal time/frequency */ 118abb0f93cSkardel "ETF\r", /* external time/frequency */ 119abb0f93cSkardel "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ 120abb0f93cSkardel "LORAN TDATA\r", /* LORAN signal data */ 121abb0f93cSkardel "UTC\r", /* UTC leap info */ 122abb0f93cSkardel 123abb0f93cSkardel "ITF\r", /* internal time/frequency */ 124abb0f93cSkardel "ETF\r", /* external time/frequency */ 125abb0f93cSkardel "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ 126abb0f93cSkardel "TRSTAT\r", /* satellite tracking status */ 127abb0f93cSkardel "OSC;ET;TEMP\r", /* osc type; tune volts; oven temp */ 128abb0f93cSkardel "\0" /* end of table */ 129abb0f93cSkardel }; 130abb0f93cSkardel 131abb0f93cSkardel /* 132abb0f93cSkardel * Function prototypes 133abb0f93cSkardel */ 134abb0f93cSkardel static int as2201_start (int, struct peer *); 135abb0f93cSkardel static void as2201_shutdown (int, struct peer *); 136abb0f93cSkardel static void as2201_receive (struct recvbuf *); 137abb0f93cSkardel static void as2201_poll (int, struct peer *); 138abb0f93cSkardel 139abb0f93cSkardel /* 140abb0f93cSkardel * Transfer vector 141abb0f93cSkardel */ 142abb0f93cSkardel struct refclock refclock_as2201 = { 143abb0f93cSkardel as2201_start, /* start up driver */ 144abb0f93cSkardel as2201_shutdown, /* shut down driver */ 145abb0f93cSkardel as2201_poll, /* transmit poll message */ 146abb0f93cSkardel noentry, /* not used (old as2201_control) */ 147abb0f93cSkardel noentry, /* initialize driver (not used) */ 148abb0f93cSkardel noentry, /* not used (old as2201_buginfo) */ 149abb0f93cSkardel NOFLAGS /* not used */ 150abb0f93cSkardel }; 151abb0f93cSkardel 152abb0f93cSkardel 153abb0f93cSkardel /* 154abb0f93cSkardel * as2201_start - open the devices and initialize data for processing 155abb0f93cSkardel */ 156abb0f93cSkardel static int 157abb0f93cSkardel as2201_start( 158abb0f93cSkardel int unit, 159abb0f93cSkardel struct peer *peer 160abb0f93cSkardel ) 161abb0f93cSkardel { 162abb0f93cSkardel register struct as2201unit *up; 163abb0f93cSkardel struct refclockproc *pp; 164abb0f93cSkardel int fd; 165abb0f93cSkardel char gpsdev[20]; 166abb0f93cSkardel 167abb0f93cSkardel /* 168abb0f93cSkardel * Open serial port. Use CLK line discipline, if available. 169abb0f93cSkardel */ 170f003fb54Skardel snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit); 171*eabc0478Schristos fd = refclock_open(&peer->srcadr, gpsdev, SPEED232, LDISC_CLK); 1728585484eSchristos if (fd <= 0) 173abb0f93cSkardel return (0); 174abb0f93cSkardel 175abb0f93cSkardel /* 176abb0f93cSkardel * Allocate and initialize unit structure 177abb0f93cSkardel */ 1788585484eSchristos up = emalloc_zero(sizeof(*up)); 179abb0f93cSkardel pp = peer->procptr; 180abb0f93cSkardel pp->io.clock_recv = as2201_receive; 1818585484eSchristos pp->io.srcclock = peer; 182abb0f93cSkardel pp->io.datalen = 0; 183abb0f93cSkardel pp->io.fd = fd; 184abb0f93cSkardel if (!io_addclock(&pp->io)) { 185f003fb54Skardel close(fd); 186f003fb54Skardel pp->io.fd = -1; 187abb0f93cSkardel free(up); 188abb0f93cSkardel return (0); 189abb0f93cSkardel } 1908585484eSchristos pp->unitptr = up; 191abb0f93cSkardel 192abb0f93cSkardel /* 193abb0f93cSkardel * Initialize miscellaneous variables 194abb0f93cSkardel */ 195abb0f93cSkardel peer->precision = PRECISION; 196abb0f93cSkardel pp->clockdesc = DESCRIPTION; 197abb0f93cSkardel memcpy((char *)&pp->refid, REFID, 4); 198abb0f93cSkardel up->lastptr = up->stats; 199abb0f93cSkardel up->index = 0; 200abb0f93cSkardel return (1); 201abb0f93cSkardel } 202abb0f93cSkardel 203abb0f93cSkardel 204abb0f93cSkardel /* 205abb0f93cSkardel * as2201_shutdown - shut down the clock 206abb0f93cSkardel */ 207abb0f93cSkardel static void 208abb0f93cSkardel as2201_shutdown( 209abb0f93cSkardel int unit, 210abb0f93cSkardel struct peer *peer 211abb0f93cSkardel ) 212abb0f93cSkardel { 213abb0f93cSkardel register struct as2201unit *up; 214abb0f93cSkardel struct refclockproc *pp; 215abb0f93cSkardel 216abb0f93cSkardel pp = peer->procptr; 2178585484eSchristos up = pp->unitptr; 218f003fb54Skardel if (-1 != pp->io.fd) 219abb0f93cSkardel io_closeclock(&pp->io); 220f003fb54Skardel if (NULL != up) 221abb0f93cSkardel free(up); 222abb0f93cSkardel } 223abb0f93cSkardel 224abb0f93cSkardel 225abb0f93cSkardel /* 226abb0f93cSkardel * as2201__receive - receive data from the serial interface 227abb0f93cSkardel */ 228abb0f93cSkardel static void 229abb0f93cSkardel as2201_receive( 230abb0f93cSkardel struct recvbuf *rbufp 231abb0f93cSkardel ) 232abb0f93cSkardel { 233abb0f93cSkardel register struct as2201unit *up; 234abb0f93cSkardel struct refclockproc *pp; 235abb0f93cSkardel struct peer *peer; 236abb0f93cSkardel l_fp trtmp; 2378585484eSchristos size_t octets; 238abb0f93cSkardel 239abb0f93cSkardel /* 240abb0f93cSkardel * Initialize pointers and read the timecode and timestamp. 241abb0f93cSkardel */ 2428585484eSchristos peer = rbufp->recv_peer; 243abb0f93cSkardel pp = peer->procptr; 2448585484eSchristos up = pp->unitptr; 245abb0f93cSkardel pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 246abb0f93cSkardel #ifdef DEBUG 247abb0f93cSkardel if (debug) 248abb0f93cSkardel printf("gps: timecode %d %d %s\n", 249abb0f93cSkardel up->linect, pp->lencode, pp->a_lastcode); 250abb0f93cSkardel #endif 251abb0f93cSkardel if (pp->lencode == 0) 252abb0f93cSkardel return; 253abb0f93cSkardel 254abb0f93cSkardel /* 255abb0f93cSkardel * If linect is greater than zero, we must be in the middle of a 256abb0f93cSkardel * statistics operation, so simply tack the received data at the 257abb0f93cSkardel * end of the statistics string. If not, we could either have 258abb0f93cSkardel * just received the timecode itself or a decimal number 259abb0f93cSkardel * indicating the number of following lines of the statistics 260abb0f93cSkardel * reply. In the former case, write the accumulated statistics 261abb0f93cSkardel * data to the clockstats file and continue onward to process 262abb0f93cSkardel * the timecode; in the later case, save the number of lines and 263abb0f93cSkardel * quietly return. 264abb0f93cSkardel */ 265abb0f93cSkardel if (pp->sloppyclockflag & CLK_FLAG2) 266abb0f93cSkardel pp->lastrec = trtmp; 267abb0f93cSkardel if (up->linect > 0) { 268abb0f93cSkardel up->linect--; 269abb0f93cSkardel if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2) 270abb0f93cSkardel return; 271abb0f93cSkardel *up->lastptr++ = ' '; 2728585484eSchristos memcpy(up->lastptr, pp->a_lastcode, 1 + pp->lencode); 273abb0f93cSkardel up->lastptr += pp->lencode; 274abb0f93cSkardel return; 275abb0f93cSkardel } else { 276abb0f93cSkardel if (pp->lencode == 1) { 277abb0f93cSkardel up->linect = atoi(pp->a_lastcode); 278abb0f93cSkardel return; 279abb0f93cSkardel } else { 280abb0f93cSkardel record_clock_stats(&peer->srcadr, up->stats); 281abb0f93cSkardel #ifdef DEBUG 282abb0f93cSkardel if (debug) 283abb0f93cSkardel printf("gps: stat %s\n", up->stats); 284abb0f93cSkardel #endif 285abb0f93cSkardel } 286abb0f93cSkardel } 287abb0f93cSkardel up->lastptr = up->stats; 288abb0f93cSkardel *up->lastptr = '\0'; 289abb0f93cSkardel 290abb0f93cSkardel /* 291abb0f93cSkardel * We get down to business, check the timecode format and decode 292abb0f93cSkardel * its contents. If the timecode has invalid length or is not in 293abb0f93cSkardel * proper format, we declare bad format and exit. 294abb0f93cSkardel */ 295abb0f93cSkardel if (pp->lencode < LENTOC) { 296abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 297abb0f93cSkardel return; 298abb0f93cSkardel } 299abb0f93cSkardel 300abb0f93cSkardel /* 301abb0f93cSkardel * Timecode format: "yy:ddd:hh:mm:ss.mmm" 302abb0f93cSkardel */ 303abb0f93cSkardel if (sscanf(pp->a_lastcode, "%2d:%3d:%2d:%2d:%2d.%3ld", &pp->year, 304abb0f93cSkardel &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec) 305abb0f93cSkardel != 6) { 306abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 307abb0f93cSkardel return; 308abb0f93cSkardel } 309abb0f93cSkardel pp->nsec *= 1000000; 310abb0f93cSkardel 311abb0f93cSkardel /* 312abb0f93cSkardel * Test for synchronization (this is a temporary crock). 313abb0f93cSkardel */ 314abb0f93cSkardel if (pp->a_lastcode[2] != ':') 315abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 316abb0f93cSkardel else 317abb0f93cSkardel pp->leap = LEAP_NOWARNING; 318abb0f93cSkardel 319abb0f93cSkardel /* 320abb0f93cSkardel * Process the new sample in the median filter and determine the 321abb0f93cSkardel * timecode timestamp. 322abb0f93cSkardel */ 323abb0f93cSkardel if (!refclock_process(pp)) { 324abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 325abb0f93cSkardel return; 326abb0f93cSkardel } 327abb0f93cSkardel 328abb0f93cSkardel /* 329abb0f93cSkardel * If CLK_FLAG4 is set, initialize the statistics buffer and 330abb0f93cSkardel * send the next command. If not, simply write the timecode to 331abb0f93cSkardel * the clockstats file. 332abb0f93cSkardel */ 3338585484eSchristos if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2) 3348585484eSchristos return; 3358585484eSchristos memcpy(up->lastptr, pp->a_lastcode, pp->lencode); 336abb0f93cSkardel up->lastptr += pp->lencode; 337abb0f93cSkardel if (pp->sloppyclockflag & CLK_FLAG4) { 3388585484eSchristos octets = strlen(stat_command[up->index]); 3398585484eSchristos if ((int)(up->lastptr - up->stats + 1 + octets) > SMAX - 2) 3408585484eSchristos return; 341abb0f93cSkardel *up->lastptr++ = ' '; 3428585484eSchristos memcpy(up->lastptr, stat_command[up->index], octets); 3438585484eSchristos up->lastptr += octets - 1; 344abb0f93cSkardel *up->lastptr = '\0'; 345*eabc0478Schristos refclock_write(peer, stat_command[up->index], 346*eabc0478Schristos strlen(stat_command[up->index]), 347*eabc0478Schristos "command"); 348abb0f93cSkardel up->index++; 349abb0f93cSkardel if (*stat_command[up->index] == '\0') 350abb0f93cSkardel up->index = 0; 351abb0f93cSkardel } 352abb0f93cSkardel } 353abb0f93cSkardel 354abb0f93cSkardel 355abb0f93cSkardel /* 356abb0f93cSkardel * as2201_poll - called by the transmit procedure 357abb0f93cSkardel * 358abb0f93cSkardel * We go to great pains to avoid changing state here, since there may be 359abb0f93cSkardel * more than one eavesdropper receiving the same timecode. 360abb0f93cSkardel */ 361abb0f93cSkardel static void 362abb0f93cSkardel as2201_poll( 363abb0f93cSkardel int unit, 364abb0f93cSkardel struct peer *peer 365abb0f93cSkardel ) 366abb0f93cSkardel { 367abb0f93cSkardel struct refclockproc *pp; 368abb0f93cSkardel 369abb0f93cSkardel /* 370abb0f93cSkardel * Send a "\r*toc\r" to get things going. We go to great pains 371abb0f93cSkardel * to avoid changing state, since there may be more than one 372abb0f93cSkardel * eavesdropper watching the radio. 373abb0f93cSkardel */ 374abb0f93cSkardel pp = peer->procptr; 375abb0f93cSkardel if (write(pp->io.fd, "\r*toc\r", 6) != 6) { 376abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 377abb0f93cSkardel } else { 378abb0f93cSkardel pp->polls++; 379abb0f93cSkardel if (!(pp->sloppyclockflag & CLK_FLAG2)) 380abb0f93cSkardel get_systime(&pp->lastrec); 381abb0f93cSkardel } 382abb0f93cSkardel if (pp->coderecv == pp->codeproc) { 383abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 384abb0f93cSkardel return; 385abb0f93cSkardel } 386abb0f93cSkardel refclock_receive(peer); 387abb0f93cSkardel } 388abb0f93cSkardel 389abb0f93cSkardel #else 390*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 391abb0f93cSkardel #endif /* REFCLOCK */ 392