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