/contrib/ntp/ntpd/refclock_wwvb.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 459 lines · 234 code · 48 blank · 177 comment · 31 complexity · 29682ed67457509b01557ac3add2c9db MD5 · raw file

  1. /*
  2. * refclock_wwvb - clock driver for Spectracom WWVB and GPS receivers
  3. */
  4. #ifdef HAVE_CONFIG_H
  5. #include <config.h>
  6. #endif
  7. #if defined(REFCLOCK) && defined(CLOCK_SPECTRACOM)
  8. #include "ntpd.h"
  9. #include "ntp_io.h"
  10. #include "ntp_refclock.h"
  11. #include "ntp_calendar.h"
  12. #include "ntp_stdlib.h"
  13. #include <stdio.h>
  14. #include <ctype.h>
  15. /*
  16. * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB
  17. * Synchronized Clocks and the Netclock/GPS Master Clock. Both the WWVB
  18. * and GPS clocks have proven reliable sources of time; however, the
  19. * WWVB clocks have proven vulnerable to high ambient conductive RF
  20. * interference. The claimed accuracy of the WWVB clocks is 100 us
  21. * relative to the broadcast signal, while the claimed accuracy of the
  22. * GPS clock is 50 ns; however, in most cases the actual accuracy is
  23. * limited by the resolution of the timecode and the latencies of the
  24. * serial interface and operating system.
  25. *
  26. * The WWVB and GPS clocks should be configured for 24-hour display,
  27. * AUTO DST off, time zone 0 (UTC), data format 0 or 2 (see below) and
  28. * baud rate 9600. If the clock is to used as the source for the IRIG
  29. * Audio Decoder (refclock_irig.c in this distribution), it should be
  30. * configured for AM IRIG output and IRIG format 1 (IRIG B with
  31. * signature control). The GPS clock can be configured either to respond
  32. * to a 'T' poll character or left running continuously.
  33. *
  34. * There are two timecode formats used by these clocks. Format 0, which
  35. * is available with both the Netclock/2 and 8170, and format 2, which
  36. * is available only with the Netclock/2, specially modified 8170 and
  37. * GPS.
  38. *
  39. * Format 0 (22 ASCII printing characters):
  40. *
  41. * <cr><lf>i ddd hh:mm:ss TZ=zz<cr><lf>
  42. *
  43. * on-time = first <cr>
  44. * hh:mm:ss = hours, minutes, seconds
  45. * i = synchronization flag (' ' = in synch, '?' = out of synch)
  46. *
  47. * The alarm condition is indicated by other than ' ' at a, which occurs
  48. * during initial synchronization and when received signal is lost for
  49. * about ten hours.
  50. *
  51. * Format 2 (24 ASCII printing characters):
  52. *
  53. * <cr><lf>iqyy ddd hh:mm:ss.fff ld
  54. *
  55. * on-time = <cr>
  56. * i = synchronization flag (' ' = in synch, '?' = out of synch)
  57. * q = quality indicator (' ' = locked, 'A'...'D' = unlocked)
  58. * yy = year (as broadcast)
  59. * ddd = day of year
  60. * hh:mm:ss.fff = hours, minutes, seconds, milliseconds
  61. *
  62. * The alarm condition is indicated by other than ' ' at a, which occurs
  63. * during initial synchronization and when received signal is lost for
  64. * about ten hours. The unlock condition is indicated by other than ' '
  65. * at q.
  66. *
  67. * The q is normally ' ' when the time error is less than 1 ms and a
  68. * character in the set 'A'...'D' when the time error is less than 10,
  69. * 100, 500 and greater than 500 ms respectively. The l is normally ' ',
  70. * but is set to 'L' early in the month of an upcoming UTC leap second
  71. * and reset to ' ' on the first day of the following month. The d is
  72. * set to 'S' for standard time 'I' on the day preceding a switch to
  73. * daylight time, 'D' for daylight time and 'O' on the day preceding a
  74. * switch to standard time. The start bit of the first <cr> is
  75. * synchronized to the indicated time as returned.
  76. *
  77. * This driver does not need to be told which format is in use - it
  78. * figures out which one from the length of the message. The driver
  79. * makes no attempt to correct for the intrinsic jitter of the radio
  80. * itself, which is a known problem with the older radios.
  81. *
  82. * Fudge Factors
  83. *
  84. * This driver can retrieve a table of quality data maintained
  85. * internally by the Netclock/2 clock. If flag4 of the fudge
  86. * configuration command is set to 1, the driver will retrieve this
  87. * table and write it to the clockstats file when the first timecode
  88. * message of a new day is received.
  89. *
  90. * PPS calibration fudge time 1: format 0 .003134, format 2 .004034
  91. */
  92. /*
  93. * Interface definitions
  94. */
  95. #define DEVICE "/dev/wwvb%d" /* device name and unit */
  96. #define SPEED232 B9600 /* uart speed (9600 baud) */
  97. #define PRECISION (-13) /* precision assumed (about 100 us) */
  98. #define REFID "WWVB" /* reference ID */
  99. #define DESCRIPTION "Spectracom WWVB/GPS Receiver" /* WRU */
  100. #define LENWWVB0 22 /* format 0 timecode length */
  101. #define LENWWVB1 22 /* format 1 timecode length */
  102. #define LENWWVB2 24 /* format 2 timecode length */
  103. #define LENWWVB3 29 /* format 3 timecode length */
  104. #define MONLIN 15 /* number of monitoring lines */
  105. /*
  106. * WWVB unit control structure
  107. */
  108. struct wwvbunit {
  109. l_fp laststamp; /* last receive timestamp */
  110. u_char lasthour; /* last hour (for monitor) */
  111. u_char linect; /* count ignored lines (for monitor */
  112. };
  113. /*
  114. * Function prototypes
  115. */
  116. static int wwvb_start P((int, struct peer *));
  117. static void wwvb_shutdown P((int, struct peer *));
  118. static void wwvb_receive P((struct recvbuf *));
  119. static void wwvb_poll P((int, struct peer *));
  120. static void wwvb_timer P((int, struct peer *));
  121. /*
  122. * Transfer vector
  123. */
  124. struct refclock refclock_wwvb = {
  125. wwvb_start, /* start up driver */
  126. wwvb_shutdown, /* shut down driver */
  127. wwvb_poll, /* transmit poll message */
  128. noentry, /* not used (old wwvb_control) */
  129. noentry, /* initialize driver (not used) */
  130. noentry, /* not used (old wwvb_buginfo) */
  131. wwvb_timer /* called once per second */
  132. };
  133. /*
  134. * wwvb_start - open the devices and initialize data for processing
  135. */
  136. static int
  137. wwvb_start(
  138. int unit,
  139. struct peer *peer
  140. )
  141. {
  142. register struct wwvbunit *up;
  143. struct refclockproc *pp;
  144. int fd;
  145. char device[20];
  146. /*
  147. * Open serial port. Use CLK line discipline, if available.
  148. */
  149. sprintf(device, DEVICE, unit);
  150. if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
  151. return (0);
  152. /*
  153. * Allocate and initialize unit structure
  154. */
  155. if (!(up = (struct wwvbunit *)
  156. emalloc(sizeof(struct wwvbunit)))) {
  157. close(fd);
  158. return (0);
  159. }
  160. memset((char *)up, 0, sizeof(struct wwvbunit));
  161. pp = peer->procptr;
  162. pp->unitptr = (caddr_t)up;
  163. pp->io.clock_recv = wwvb_receive;
  164. pp->io.srcclock = (caddr_t)peer;
  165. pp->io.datalen = 0;
  166. pp->io.fd = fd;
  167. if (!io_addclock(&pp->io)) {
  168. close(fd);
  169. free(up);
  170. return (0);
  171. }
  172. /*
  173. * Initialize miscellaneous variables
  174. */
  175. peer->precision = PRECISION;
  176. pp->clockdesc = DESCRIPTION;
  177. memcpy((char *)&pp->refid, REFID, 4);
  178. return (1);
  179. }
  180. /*
  181. * wwvb_shutdown - shut down the clock
  182. */
  183. static void
  184. wwvb_shutdown(
  185. int unit,
  186. struct peer *peer
  187. )
  188. {
  189. register struct wwvbunit *up;
  190. struct refclockproc *pp;
  191. pp = peer->procptr;
  192. up = (struct wwvbunit *)pp->unitptr;
  193. io_closeclock(&pp->io);
  194. free(up);
  195. }
  196. /*
  197. * wwvb_receive - receive data from the serial interface
  198. */
  199. static void
  200. wwvb_receive(
  201. struct recvbuf *rbufp
  202. )
  203. {
  204. struct wwvbunit *up;
  205. struct refclockproc *pp;
  206. struct peer *peer;
  207. l_fp trtmp; /* arrival timestamp */
  208. int tz; /* time zone */
  209. int day, month; /* ddd conversion */
  210. int temp; /* int temp */
  211. char syncchar; /* synchronization indicator */
  212. char qualchar; /* quality indicator */
  213. char leapchar; /* leap indicator */
  214. char dstchar; /* daylight/standard indicator */
  215. char tmpchar; /* trashbin */
  216. /*
  217. * Initialize pointers and read the timecode and timestamp
  218. */
  219. peer = (struct peer *)rbufp->recv_srcclock;
  220. pp = peer->procptr;
  221. up = (struct wwvbunit *)pp->unitptr;
  222. temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
  223. /*
  224. * Note we get a buffer and timestamp for both a <cr> and <lf>,
  225. * but only the <cr> timestamp is retained. Note: in format 0 on
  226. * a Netclock/2 or upgraded 8170 the start bit is delayed 100
  227. * +-50 us relative to the pps; however, on an unmodified 8170
  228. * the start bit can be delayed up to 10 ms. In format 2 the
  229. * reading precision is only to the millisecond. Thus, unless
  230. * you have a PPS gadget and don't have to have the year, format
  231. * 0 provides the lowest jitter.
  232. */
  233. if (temp == 0) {
  234. up->laststamp = trtmp;
  235. return;
  236. }
  237. pp->lencode = temp;
  238. pp->lastrec = up->laststamp;
  239. /*
  240. * We get down to business, check the timecode format and decode
  241. * its contents. This code uses the timecode length to determine
  242. * format 0, 2 or 3. If the timecode has invalid length or is
  243. * not in proper format, we declare bad format and exit.
  244. */
  245. syncchar = qualchar = leapchar = dstchar = ' ';
  246. tz = 0;
  247. switch (pp->lencode) {
  248. case LENWWVB0:
  249. /*
  250. * Timecode format 0: "I ddd hh:mm:ss DTZ=nn"
  251. */
  252. if (sscanf(pp->a_lastcode,
  253. "%c %3d %2d:%2d:%2d%c%cTZ=%2d",
  254. &syncchar, &pp->day, &pp->hour, &pp->minute,
  255. &pp->second, &tmpchar, &dstchar, &tz) == 8)
  256. pp->nsec = 0;
  257. break;
  258. case LENWWVB2:
  259. /*
  260. * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */
  261. if (sscanf(pp->a_lastcode,
  262. "%c%c %2d %3d %2d:%2d:%2d.%3ld %c",
  263. &syncchar, &qualchar, &pp->year, &pp->day,
  264. &pp->hour, &pp->minute, &pp->second, &pp->nsec,
  265. &leapchar) == 9)
  266. pp->nsec *= 1000000;
  267. break;
  268. case LENWWVB3:
  269. /*
  270. * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#"
  271. */
  272. if (sscanf(pp->a_lastcode,
  273. "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c",
  274. &syncchar, &pp->year, &month, &day, &pp->hour,
  275. &pp->minute, &pp->second, &dstchar, &leapchar) == 8)
  276. {
  277. pp->day = ymd2yd(pp->year, month, day);
  278. pp->nsec = 0;
  279. break;
  280. }
  281. default:
  282. /*
  283. * Unknown format: If dumping internal table, record
  284. * stats; otherwise, declare bad format.
  285. */
  286. if (up->linect > 0) {
  287. up->linect--;
  288. record_clock_stats(&peer->srcadr,
  289. pp->a_lastcode);
  290. } else {
  291. refclock_report(peer, CEVNT_BADREPLY);
  292. }
  293. return;
  294. }
  295. /*
  296. * Decode synchronization, quality and leap characters. If
  297. * unsynchronized, set the leap bits accordingly and exit.
  298. * Otherwise, set the leap bits according to the leap character.
  299. * Once synchronized, the dispersion depends only on the
  300. * quality character.
  301. */
  302. switch (qualchar) {
  303. case ' ':
  304. pp->disp = .001;
  305. pp->lastref = pp->lastrec;
  306. break;
  307. case 'A':
  308. pp->disp = .01;
  309. break;
  310. case 'B':
  311. pp->disp = .1;
  312. break;
  313. case 'C':
  314. pp->disp = .5;
  315. break;
  316. case 'D':
  317. pp->disp = MAXDISPERSE;
  318. break;
  319. default:
  320. pp->disp = MAXDISPERSE;
  321. refclock_report(peer, CEVNT_BADREPLY);
  322. break;
  323. }
  324. if (syncchar != ' ')
  325. pp->leap = LEAP_NOTINSYNC;
  326. else if (leapchar == 'L')
  327. pp->leap = LEAP_ADDSECOND;
  328. else
  329. pp->leap = LEAP_NOWARNING;
  330. /*
  331. * Process the new sample in the median filter and determine the
  332. * timecode timestamp.
  333. */
  334. if (!refclock_process(pp))
  335. refclock_report(peer, CEVNT_BADTIME);
  336. if (peer->disp > MAXDISTANCE)
  337. refclock_receive(peer);
  338. }
  339. /*
  340. * wwvb_timer - called once per second by the transmit procedure
  341. */
  342. static void
  343. wwvb_timer(
  344. int unit,
  345. struct peer *peer
  346. )
  347. {
  348. register struct wwvbunit *up;
  349. struct refclockproc *pp;
  350. char pollchar; /* character sent to clock */
  351. /*
  352. * Time to poll the clock. The Spectracom clock responds to a
  353. * 'T' by returning a timecode in the format(s) specified above.
  354. * Note there is no checking on state, since this may not be the
  355. * only customer reading the clock. Only one customer need poll
  356. * the clock; all others just listen in.
  357. */
  358. pp = peer->procptr;
  359. up = (struct wwvbunit *)pp->unitptr;
  360. if (up->linect > 0)
  361. pollchar = 'R';
  362. else
  363. pollchar = 'T';
  364. if (write(pp->io.fd, &pollchar, 1) != 1)
  365. refclock_report(peer, CEVNT_FAULT);
  366. }
  367. /*
  368. * wwvb_poll - called by the transmit procedure
  369. */
  370. static void
  371. wwvb_poll(
  372. int unit,
  373. struct peer *peer
  374. )
  375. {
  376. register struct wwvbunit *up;
  377. struct refclockproc *pp;
  378. /*
  379. * Sweep up the samples received since the last poll. If none
  380. * are received, declare a timeout and keep going.
  381. */
  382. pp = peer->procptr;
  383. up = (struct wwvbunit *)pp->unitptr;
  384. pp->polls++;
  385. /*
  386. * If the monitor flag is set (flag4), we dump the internal
  387. * quality table at the first timecode beginning the day.
  388. */
  389. if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour <
  390. (int)up->lasthour)
  391. up->linect = MONLIN;
  392. up->lasthour = pp->hour;
  393. /*
  394. * Process median filter samples. If none received, declare a
  395. * timeout and keep going.
  396. */
  397. if (pp->coderecv == pp->codeproc) {
  398. refclock_report(peer, CEVNT_TIMEOUT);
  399. return;
  400. }
  401. refclock_receive(peer);
  402. record_clock_stats(&peer->srcadr, pp->a_lastcode);
  403. #ifdef DEBUG
  404. if (debug)
  405. printf("wwvb: timecode %d %s\n", pp->lencode,
  406. pp->a_lastcode);
  407. #endif
  408. }
  409. #else
  410. int refclock_wwvb_bs;
  411. #endif /* REFCLOCK */