/contrib/ntp/ntpd/refclock_as2201.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 388 lines · 216 code · 34 blank · 138 comment · 31 complexity · 53425ef64b2b879ea9ee60285dd81d4e MD5 · raw file

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