1 /* $NetBSD: refclock_as2201.c,v 1.1.1.1 2009/12/13 16:55:42 kardel 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 */ 79 struct 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 */ 104 static 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 */ 134 static int as2201_start (int, struct peer *); 135 static void as2201_shutdown (int, struct peer *); 136 static void as2201_receive (struct recvbuf *); 137 static void as2201_poll (int, struct peer *); 138 139 /* 140 * Transfer vector 141 */ 142 struct 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 */ 156 static int 157 as2201_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 (void)sprintf(gpsdev, DEVICE, unit); 171 if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_CLK))) 172 return (0); 173 174 /* 175 * Allocate and initialize unit structure 176 */ 177 if (!(up = (struct as2201unit *) 178 emalloc(sizeof(struct as2201unit)))) { 179 (void) close(fd); 180 return (0); 181 } 182 memset((char *)up, 0, sizeof(struct as2201unit)); 183 pp = peer->procptr; 184 pp->io.clock_recv = as2201_receive; 185 pp->io.srcclock = (caddr_t)peer; 186 pp->io.datalen = 0; 187 pp->io.fd = fd; 188 if (!io_addclock(&pp->io)) { 189 (void) close(fd); 190 free(up); 191 return (0); 192 } 193 pp->unitptr = (caddr_t)up; 194 195 /* 196 * Initialize miscellaneous variables 197 */ 198 peer->precision = PRECISION; 199 peer->burst = NSTAGE; 200 pp->clockdesc = DESCRIPTION; 201 memcpy((char *)&pp->refid, REFID, 4); 202 up->lastptr = up->stats; 203 up->index = 0; 204 return (1); 205 } 206 207 208 /* 209 * as2201_shutdown - shut down the clock 210 */ 211 static void 212 as2201_shutdown( 213 int unit, 214 struct peer *peer 215 ) 216 { 217 register struct as2201unit *up; 218 struct refclockproc *pp; 219 220 pp = peer->procptr; 221 up = (struct as2201unit *)pp->unitptr; 222 io_closeclock(&pp->io); 223 free(up); 224 } 225 226 227 /* 228 * as2201__receive - receive data from the serial interface 229 */ 230 static void 231 as2201_receive( 232 struct recvbuf *rbufp 233 ) 234 { 235 register struct as2201unit *up; 236 struct refclockproc *pp; 237 struct peer *peer; 238 l_fp trtmp; 239 240 /* 241 * Initialize pointers and read the timecode and timestamp. 242 */ 243 peer = (struct peer *)rbufp->recv_srcclock; 244 pp = peer->procptr; 245 up = (struct as2201unit *)pp->unitptr; 246 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 247 #ifdef DEBUG 248 if (debug) 249 printf("gps: timecode %d %d %s\n", 250 up->linect, pp->lencode, pp->a_lastcode); 251 #endif 252 if (pp->lencode == 0) 253 return; 254 255 /* 256 * If linect is greater than zero, we must be in the middle of a 257 * statistics operation, so simply tack the received data at the 258 * end of the statistics string. If not, we could either have 259 * just received the timecode itself or a decimal number 260 * indicating the number of following lines of the statistics 261 * reply. In the former case, write the accumulated statistics 262 * data to the clockstats file and continue onward to process 263 * the timecode; in the later case, save the number of lines and 264 * quietly return. 265 */ 266 if (pp->sloppyclockflag & CLK_FLAG2) 267 pp->lastrec = trtmp; 268 if (up->linect > 0) { 269 up->linect--; 270 if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2) 271 return; 272 *up->lastptr++ = ' '; 273 (void)strcpy(up->lastptr, pp->a_lastcode); 274 up->lastptr += pp->lencode; 275 return; 276 } else { 277 if (pp->lencode == 1) { 278 up->linect = atoi(pp->a_lastcode); 279 return; 280 } else { 281 record_clock_stats(&peer->srcadr, up->stats); 282 #ifdef DEBUG 283 if (debug) 284 printf("gps: stat %s\n", up->stats); 285 #endif 286 } 287 } 288 up->lastptr = up->stats; 289 *up->lastptr = '\0'; 290 291 /* 292 * We get down to business, check the timecode format and decode 293 * its contents. If the timecode has invalid length or is not in 294 * proper format, we declare bad format and exit. 295 */ 296 if (pp->lencode < LENTOC) { 297 refclock_report(peer, CEVNT_BADREPLY); 298 return; 299 } 300 301 /* 302 * Timecode format: "yy:ddd:hh:mm:ss.mmm" 303 */ 304 if (sscanf(pp->a_lastcode, "%2d:%3d:%2d:%2d:%2d.%3ld", &pp->year, 305 &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec) 306 != 6) { 307 refclock_report(peer, CEVNT_BADREPLY); 308 return; 309 } 310 pp->nsec *= 1000000; 311 312 /* 313 * Test for synchronization (this is a temporary crock). 314 */ 315 if (pp->a_lastcode[2] != ':') 316 pp->leap = LEAP_NOTINSYNC; 317 else 318 pp->leap = LEAP_NOWARNING; 319 320 /* 321 * Process the new sample in the median filter and determine the 322 * timecode timestamp. 323 */ 324 if (!refclock_process(pp)) { 325 refclock_report(peer, CEVNT_BADTIME); 326 return; 327 } 328 329 /* 330 * If CLK_FLAG4 is set, initialize the statistics buffer and 331 * send the next command. If not, simply write the timecode to 332 * the clockstats file. 333 */ 334 (void)strcpy(up->lastptr, pp->a_lastcode); 335 up->lastptr += pp->lencode; 336 if (pp->sloppyclockflag & CLK_FLAG4) { 337 *up->lastptr++ = ' '; 338 (void)strcpy(up->lastptr, stat_command[up->index]); 339 up->lastptr += strlen(stat_command[up->index]); 340 up->lastptr--; 341 *up->lastptr = '\0'; 342 (void)write(pp->io.fd, stat_command[up->index], 343 strlen(stat_command[up->index])); 344 up->index++; 345 if (*stat_command[up->index] == '\0') 346 up->index = 0; 347 } 348 } 349 350 351 /* 352 * as2201_poll - called by the transmit procedure 353 * 354 * We go to great pains to avoid changing state here, since there may be 355 * more than one eavesdropper receiving the same timecode. 356 */ 357 static void 358 as2201_poll( 359 int unit, 360 struct peer *peer 361 ) 362 { 363 struct refclockproc *pp; 364 365 /* 366 * Send a "\r*toc\r" to get things going. We go to great pains 367 * to avoid changing state, since there may be more than one 368 * eavesdropper watching the radio. 369 */ 370 pp = peer->procptr; 371 if (write(pp->io.fd, "\r*toc\r", 6) != 6) { 372 refclock_report(peer, CEVNT_FAULT); 373 } else { 374 pp->polls++; 375 if (!(pp->sloppyclockflag & CLK_FLAG2)) 376 get_systime(&pp->lastrec); 377 } 378 if (peer->burst > 0) 379 return; 380 if (pp->coderecv == pp->codeproc) { 381 refclock_report(peer, CEVNT_TIMEOUT); 382 return; 383 } 384 refclock_receive(peer); 385 peer->burst = NSTAGE; 386 } 387 388 #else 389 int refclock_as2201_bs; 390 #endif /* REFCLOCK */ 391