/src/ntp-dev/ntpd/refclock_true.c
Line | Count | Source |
1 | | /* |
2 | | * refclock_true - clock driver for the Kinemetrics/TrueTime receivers |
3 | | * Receiver Version 3.0C - tested plain, with CLKLDISC |
4 | | * Development work being done: |
5 | | * - Support TL-3 WWV TOD receiver |
6 | | */ |
7 | | |
8 | | #ifdef HAVE_CONFIG_H |
9 | | #include <config.h> |
10 | | #endif |
11 | | |
12 | | #if defined(REFCLOCK) && defined(CLOCK_TRUETIME) |
13 | | |
14 | | #include <stdio.h> |
15 | | #include <ctype.h> |
16 | | |
17 | | #include "ntpd.h" |
18 | | #include "ntp_io.h" |
19 | | #include "ntp_refclock.h" |
20 | | #include "ntp_unixtime.h" |
21 | | #include "ntp_stdlib.h" |
22 | | |
23 | | /* This should be an atom clock but those are very hard to build. |
24 | | * |
25 | | * The PCL720 from P C Labs has an Intel 8253 lookalike, as well as a bunch |
26 | | * of TTL input and output pins, all brought out to the back panel. If you |
27 | | * wire a PPS signal (such as the TTL PPS coming out of a GOES or other |
28 | | * Kinemetrics/Truetime clock) to the 8253's GATE0, and then also wire the |
29 | | * 8253's OUT0 to the PCL720's INPUT3.BIT0, then we can read CTR0 to get the |
30 | | * number of uSecs since the last PPS upward swing, mediated by reading OUT0 |
31 | | * to find out if the counter has wrapped around (this happens if more than |
32 | | * 65535us (65ms) elapses between the PPS event and our being called.) |
33 | | */ |
34 | | #ifdef CLOCK_PPS720 |
35 | | # undef min /* XXX */ |
36 | | # undef max /* XXX */ |
37 | | # include <machine/inline.h> |
38 | | # include <sys/pcl720.h> |
39 | | # include <sys/i8253.h> |
40 | | # define PCL720_IOB 0x2a0 /* XXX */ |
41 | | # define PCL720_CTR 0 /* XXX */ |
42 | | #endif |
43 | | |
44 | | /* |
45 | | * Support for Kinemetrics Truetime Receivers |
46 | | * GOES: (468-DC, usable with GPS->GOES converting antenna) |
47 | | * GPS/TM-TMD: |
48 | | * XL-DC: (a 151-602-210, reported by the driver as a GPS/TM-TMD) |
49 | | * GPS-800 TCU: (an 805-957 with the RS232 Talker/Listener module) |
50 | | * TL-3: 3 channel WWV/H receiver w/ IRIG and RS-232 outputs |
51 | | * OM-DC: getting stale ("OMEGA") |
52 | | * |
53 | | * Most of this code is originally from refclock_wwvb.c with thanks. |
54 | | * It has been so mangled that wwvb is not a recognizable ancestor. |
55 | | * |
56 | | * Timcode format: ADDD:HH:MM:SSQCL |
57 | | * A - control A (this is stripped before we see it) |
58 | | * Q - Quality indication (see below) |
59 | | * C - Carriage return |
60 | | * L - Line feed |
61 | | * |
62 | | * Quality codes indicate possible error of |
63 | | * 468-DC GOES Receiver: |
64 | | * GPS-TM/TMD Receiver: (default quality codes for XL-DC) |
65 | | * ? +/- 1 milliseconds # +/- 100 microseconds |
66 | | * * +/- 10 microseconds . +/- 1 microsecond |
67 | | * space less than 1 microsecond |
68 | | * TL-3 Receiver: (default quality codes for TL-3) |
69 | | * ? unknown quality (receiver is unlocked) |
70 | | * space +/- 5 milliseconds |
71 | | * OM-DC OMEGA Receiver: (default quality codes for OMEGA) |
72 | | * WARNING OMEGA navigation system is no longer existent |
73 | | * > >+- 5 seconds |
74 | | * ? >+/- 500 milliseconds # >+/- 50 milliseconds |
75 | | * * >+/- 5 milliseconds . >+/- 1 millisecond |
76 | | * A-H less than 1 millisecond. Character indicates which station |
77 | | * is being received as follows: |
78 | | * A = Norway, B = Liberia, C = Hawaii, D = North Dakota, |
79 | | * E = La Reunion, F = Argentina, G = Australia, H = Japan. |
80 | | * |
81 | | * The carriage return start bit begins on 0 seconds and extends to 1 bit time. |
82 | | * |
83 | | * Notes on 468-DC and OMEGA receiver: |
84 | | * |
85 | | * Send the clock a 'R' or 'C' and once per second a timestamp will |
86 | | * appear. Send a 'P' to get the satellite position once (GOES only.) |
87 | | * |
88 | | * Notes on the 468-DC receiver: |
89 | | * |
90 | | * Since the old east/west satellite locations are only historical, you can't |
91 | | * set your clock propagation delay settings correctly and still use |
92 | | * automatic mode. The manual says to use a compromise when setting the |
93 | | * switches. This results in significant errors. The solution; use fudge |
94 | | * time1 and time2 to incorporate corrections. If your clock is set for |
95 | | * 50 and it should be 58 for using the west and 46 for using the east, |
96 | | * use the line |
97 | | * |
98 | | * fudge 127.127.5.0 time1 +0.008 time2 -0.004 |
99 | | * |
100 | | * This corrects the 4 milliseconds advance and 8 milliseconds retard |
101 | | * needed. The software will ask the clock which satellite it sees. |
102 | | * |
103 | | * Notes on the TrueTime TimeLink TL-3 WWV TOD receiver: |
104 | | * |
105 | | * This clock may be polled, or send one timecode per second. |
106 | | * That mode may be toggled via the front panel ("C" mode), or controlled |
107 | | * from the RS-232 port. Send the receiver "ST1" to turn it on, and |
108 | | * "ST0" to turn it off. Send "QV" to get the firmware revision (useful |
109 | | * for identifying this model.) |
110 | | * |
111 | | * Note that it can take several polling cycles, especially if the receiver |
112 | | * was in the continuous timecode mode. (It can be slow to leave that mode.) |
113 | | * |
114 | | * ntp.conf parameters: |
115 | | * time1 - offset applied to samples when reading WEST satellite (default = 0) |
116 | | * time2 - offset applied to samples when reading EAST satellite (default = 0) |
117 | | * stratum - stratum to assign to this clock (default = 0) |
118 | | * refid - refid assigned to this clock (default = "TRUE", see below) |
119 | | * flag1 - will silence the clock side of ntpd, just reading the clock |
120 | | * without trying to write to it. (default = 0) |
121 | | * flag2 - generate a debug file /tmp/true%d. |
122 | | * flag3 - enable ppsclock streams module |
123 | | * flag4 - use the PCL-720 (BSD/OS only) |
124 | | */ |
125 | | |
126 | | |
127 | | /* |
128 | | * Definitions |
129 | | */ |
130 | | #define DEVICE "/dev/true%d" |
131 | 0 | #define SPEED232 B9600 /* 9600 baud */ |
132 | | |
133 | | /* |
134 | | * Radio interface parameters |
135 | | */ |
136 | 0 | #define PRECISION (-10) /* precision assumed (about 1 ms) */ |
137 | 0 | #define REFID "TRUE" /* reference id */ |
138 | 0 | #define DESCRIPTION "Kinemetrics/TrueTime Receiver" |
139 | | |
140 | | /* |
141 | | * Tags which station (satellite) we see |
142 | | */ |
143 | 0 | #define GOES_WEST 0 /* Default to WEST satellite and apply time1 */ |
144 | 0 | #define GOES_EAST 1 /* until you discover otherwise */ |
145 | | |
146 | | /* |
147 | | * used by the state machine |
148 | | */ |
149 | | enum true_event {e_Init, e_Huh, e_F18, e_F50, e_F51, e_Satellite, |
150 | | e_TL3, e_Poll, e_Location, e_TS, e_Max}; |
151 | | const char *events[] = {"Init", "Huh", "F18", "F50", "F51", "Satellite", |
152 | | "TL3", "Poll", "Location", "TS"}; |
153 | 0 | #define eventStr(x) (((int)x<(int)e_Max) ? events[(int)x] : "?") |
154 | | |
155 | | enum true_state {s_Base, s_InqTM, s_InqTCU, s_InqOmega, s_InqGOES, |
156 | | s_InqTL3, s_Init, s_F18, s_F50, s_Start, s_Auto, s_Max}; |
157 | | const char *states[] = {"Base", "InqTM", "InqTCU", "InqOmega", "InqGOES", |
158 | | "InqTL3", "Init", "F18", "F50", "Start", "Auto"}; |
159 | 0 | #define stateStr(x) (((int)x<(int)s_Max) ? states[(int)x] : "?") |
160 | | |
161 | | enum true_type {t_unknown, t_goes, t_tm, t_tcu, t_omega, t_tl3, t_Max}; |
162 | | const char *types[] = {"unknown", "goes", "tm", "tcu", "omega", "tl3"}; |
163 | 0 | #define typeStr(x) (((int)x<(int)t_Max) ? types[(int)x] : "?") |
164 | | |
165 | | /* |
166 | | * unit control structure |
167 | | */ |
168 | | struct true_unit { |
169 | | unsigned int pollcnt; /* poll message counter */ |
170 | | unsigned int station; /* which station we are on */ |
171 | | unsigned int polled; /* Hand in a time sample? */ |
172 | | enum true_state state; /* state machine */ |
173 | | enum true_type type; /* what kind of clock is it? */ |
174 | | int unit; /* save an extra copy of this */ |
175 | | FILE *debug; /* debug logging file */ |
176 | | #ifdef CLOCK_PPS720 |
177 | | int pcl720init; /* init flag for PCL 720 */ |
178 | | #endif |
179 | | }; |
180 | | |
181 | | /* |
182 | | * Function prototypes |
183 | | */ |
184 | | static int true_start (int, struct peer *); |
185 | | static void true_shutdown (int, struct peer *); |
186 | | static void true_receive (struct recvbuf *); |
187 | | static void true_poll (int, struct peer *); |
188 | | static void true_send (struct peer *, const char *); |
189 | | static void true_doevent (struct peer *, enum true_event); |
190 | | |
191 | | #ifdef CLOCK_PPS720 |
192 | | static u_long true_sample720 (void); |
193 | | #endif |
194 | | |
195 | | /* |
196 | | * Transfer vector |
197 | | */ |
198 | | struct refclock refclock_true = { |
199 | | true_start, /* start up driver */ |
200 | | true_shutdown, /* shut down driver */ |
201 | | true_poll, /* transmit poll message */ |
202 | | noentry, /* not used (old true_control) */ |
203 | | noentry, /* initialize driver (not used) */ |
204 | | noentry, /* not used (old true_buginfo) */ |
205 | | NOFLAGS /* not used */ |
206 | | }; |
207 | | |
208 | | |
209 | | #if !defined(__STDC__) |
210 | | # define true_debug (void) |
211 | | #else |
212 | | NTP_PRINTF(2, 3) |
213 | | static void |
214 | | true_debug(struct peer *peer, const char *fmt, ...) |
215 | 0 | { |
216 | 0 | va_list ap; |
217 | 0 | int want_debugging, now_debugging; |
218 | 0 | struct refclockproc *pp; |
219 | 0 | struct true_unit *up; |
220 | |
|
221 | 0 | va_start(ap, fmt); |
222 | 0 | pp = peer->procptr; |
223 | 0 | up = pp->unitptr; |
224 | |
|
225 | 0 | want_debugging = (pp->sloppyclockflag & CLK_FLAG2) != 0; |
226 | 0 | now_debugging = (up->debug != NULL); |
227 | 0 | if (want_debugging != now_debugging) |
228 | 0 | { |
229 | 0 | if (want_debugging) { |
230 | 0 | char filename[40]; |
231 | 0 | int fd; |
232 | |
|
233 | 0 | snprintf(filename, sizeof(filename), |
234 | 0 | "/tmp/true%d.debug", up->unit); |
235 | 0 | fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, |
236 | 0 | 0600); |
237 | 0 | if (fd >= 0 && (up->debug = fdopen(fd, "w"))) { |
238 | 0 | #ifdef HAVE_SETVBUF |
239 | 0 | static char buf[BUFSIZ]; |
240 | |
|
241 | 0 | setvbuf(up->debug, buf, _IOLBF, BUFSIZ); |
242 | | #else |
243 | | setlinebuf(up->debug); |
244 | | #endif |
245 | 0 | } |
246 | 0 | } else { |
247 | 0 | fclose(up->debug); |
248 | 0 | up->debug = NULL; |
249 | 0 | } |
250 | 0 | } |
251 | |
|
252 | 0 | if (up->debug) { |
253 | 0 | fprintf(up->debug, "true%d: ", up->unit); |
254 | 0 | vfprintf(up->debug, fmt, ap); |
255 | 0 | } |
256 | 0 | va_end(ap); |
257 | 0 | } |
258 | | #endif /*STDC*/ |
259 | | |
260 | | /* |
261 | | * true_start - open the devices and initialize data for processing |
262 | | */ |
263 | | static int |
264 | | true_start( |
265 | | int unit, |
266 | | struct peer *peer |
267 | | ) |
268 | 0 | { |
269 | 0 | register struct true_unit *up; |
270 | 0 | struct refclockproc *pp; |
271 | 0 | char device[40]; |
272 | 0 | int fd; |
273 | | |
274 | | /* |
275 | | * Open serial port |
276 | | */ |
277 | 0 | snprintf(device, sizeof(device), DEVICE, unit); |
278 | 0 | fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK); |
279 | 0 | if (fd <= 0) |
280 | 0 | return 0; |
281 | | |
282 | | /* |
283 | | * Allocate and initialize unit structure |
284 | | */ |
285 | 0 | up = emalloc_zero(sizeof(*up)); |
286 | 0 | pp = peer->procptr; |
287 | 0 | pp->io.clock_recv = true_receive; |
288 | 0 | pp->io.srcclock = peer; |
289 | 0 | pp->io.datalen = 0; |
290 | 0 | pp->io.fd = fd; |
291 | 0 | if (!io_addclock(&pp->io)) { |
292 | 0 | close(fd); |
293 | 0 | pp->io.fd = -1; |
294 | 0 | free(up); |
295 | 0 | return (0); |
296 | 0 | } |
297 | 0 | pp->unitptr = up; |
298 | | |
299 | | /* |
300 | | * Initialize miscellaneous variables |
301 | | */ |
302 | 0 | peer->precision = PRECISION; |
303 | 0 | pp->clockdesc = DESCRIPTION; |
304 | 0 | memcpy(&pp->refid, REFID, 4); |
305 | 0 | up->pollcnt = 2; |
306 | 0 | up->type = t_unknown; |
307 | 0 | up->state = s_Base; |
308 | | |
309 | | /* |
310 | | * Send a CTRL-C character at the start, |
311 | | * just in case the clock is already |
312 | | * sending timecodes |
313 | | */ |
314 | 0 | true_send(peer, "\03\r"); |
315 | | |
316 | 0 | true_doevent(peer, e_Init); |
317 | |
|
318 | 0 | return (1); |
319 | 0 | } |
320 | | |
321 | | |
322 | | /* |
323 | | * true_shutdown - shut down the clock |
324 | | */ |
325 | | static void |
326 | | true_shutdown( |
327 | | int unit, |
328 | | struct peer *peer |
329 | | ) |
330 | 0 | { |
331 | 0 | register struct true_unit *up; |
332 | 0 | struct refclockproc *pp; |
333 | |
|
334 | 0 | pp = peer->procptr; |
335 | 0 | up = pp->unitptr; |
336 | 0 | if (pp->io.fd != -1) |
337 | 0 | io_closeclock(&pp->io); |
338 | 0 | if (up != NULL) |
339 | 0 | free(up); |
340 | 0 | } |
341 | | |
342 | | |
343 | | /* |
344 | | * true_receive - receive data from the serial interface on a clock |
345 | | */ |
346 | | static void |
347 | | true_receive( |
348 | | struct recvbuf *rbufp |
349 | | ) |
350 | 0 | { |
351 | 0 | register struct true_unit *up; |
352 | 0 | struct refclockproc *pp; |
353 | 0 | struct peer *peer; |
354 | 0 | u_short new_station; |
355 | 0 | char synced; |
356 | 0 | int i; |
357 | 0 | int lat, lon, off; /* GOES Satellite position */ |
358 | | /* These variables hold data until we decide to keep it */ |
359 | 0 | char rd_lastcode[BMAX]; |
360 | 0 | l_fp rd_tmp; |
361 | 0 | u_short rd_lencode; |
362 | | |
363 | | /* |
364 | | * Get the clock this applies to and pointers to the data. |
365 | | */ |
366 | 0 | peer = rbufp->recv_peer; |
367 | 0 | pp = peer->procptr; |
368 | 0 | up = pp->unitptr; |
369 | | |
370 | | /* |
371 | | * Read clock output. Automatically handles STREAMS, CLKLDISC. |
372 | | */ |
373 | 0 | rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp); |
374 | 0 | rd_lastcode[rd_lencode] = '\0'; |
375 | | |
376 | | /* |
377 | | * There is a case where <cr><lf> generates 2 timestamps. |
378 | | */ |
379 | 0 | if (rd_lencode == 0) |
380 | 0 | return; |
381 | 0 | pp->lencode = rd_lencode; |
382 | 0 | strlcpy(pp->a_lastcode, rd_lastcode, sizeof(pp->a_lastcode)); |
383 | 0 | pp->lastrec = rd_tmp; |
384 | 0 | true_debug(peer, "receive(%s) [%d]\n", pp->a_lastcode, |
385 | 0 | pp->lencode); |
386 | |
|
387 | 0 | up->pollcnt = 2; |
388 | 0 | record_clock_stats(&peer->srcadr, pp->a_lastcode); |
389 | | |
390 | | /* |
391 | | * We get down to business, check the timecode format and decode |
392 | | * its contents. This code decodes a multitude of different |
393 | | * clock messages. Timecodes are processed if needed. All replies |
394 | | * will be run through the state machine to tweak driver options |
395 | | * and program the clock. |
396 | | */ |
397 | | |
398 | | /* |
399 | | * Clock misunderstood our last command? |
400 | | */ |
401 | 0 | if (pp->a_lastcode[0] == '?' || |
402 | 0 | strcmp(pp->a_lastcode, "ERROR 05 NO SUCH FUNCTION") == 0) { |
403 | 0 | true_doevent(peer, e_Huh); |
404 | 0 | return; |
405 | 0 | } |
406 | | |
407 | | /* |
408 | | * Timecode: "nnnnn+nnn-nnn" |
409 | | * (from GOES clock when asked about satellite position) |
410 | | */ |
411 | 0 | if ((pp->a_lastcode[5] == '+' || pp->a_lastcode[5] == '-') && |
412 | 0 | (pp->a_lastcode[9] == '+' || pp->a_lastcode[9] == '-') && |
413 | 0 | sscanf(pp->a_lastcode, "%5d%*c%3d%*c%3d", &lon, &lat, &off) == 3 |
414 | 0 | ) { |
415 | 0 | const char *label = "Botch!"; |
416 | | |
417 | | /* |
418 | | * This is less than perfect. Call the (satellite) |
419 | | * either EAST or WEST and adjust slop accodingly |
420 | | * Perfectionists would recalculate the exact delay |
421 | | * and adjust accordingly... |
422 | | */ |
423 | 0 | if (lon > 7000 && lon < 14000) { |
424 | 0 | if (lon < 10000) { |
425 | 0 | new_station = GOES_EAST; |
426 | 0 | label = "EAST"; |
427 | 0 | } else { |
428 | 0 | new_station = GOES_WEST; |
429 | 0 | label = "WEST"; |
430 | 0 | } |
431 | | |
432 | 0 | if (new_station != up->station) { |
433 | 0 | double dtemp; |
434 | |
|
435 | 0 | dtemp = pp->fudgetime1; |
436 | 0 | pp->fudgetime1 = pp->fudgetime2; |
437 | 0 | pp->fudgetime2 = dtemp; |
438 | 0 | up->station = new_station; |
439 | 0 | } |
440 | 0 | } |
441 | 0 | else { |
442 | | /*refclock_report(peer, CEVNT_BADREPLY);*/ |
443 | 0 | label = "UNKNOWN"; |
444 | 0 | } |
445 | 0 | true_debug(peer, "GOES: station %s\n", label); |
446 | 0 | true_doevent(peer, e_Satellite); |
447 | 0 | return; |
448 | 0 | } |
449 | | |
450 | | /* |
451 | | * Timecode: "Fnn" |
452 | | * (from TM/TMD clock when it wants to tell us what it's up to.) |
453 | | */ |
454 | 0 | if (sscanf(pp->a_lastcode, "F%2d", &i) == 1 && i > 0 && i < 80) { |
455 | 0 | switch (i) { |
456 | 0 | case 50: |
457 | 0 | true_doevent(peer, e_F50); |
458 | 0 | break; |
459 | 0 | case 51: |
460 | 0 | true_doevent(peer, e_F51); |
461 | 0 | break; |
462 | 0 | default: |
463 | 0 | true_debug(peer, "got F%02d - ignoring\n", i); |
464 | 0 | break; |
465 | 0 | } |
466 | 0 | return; |
467 | 0 | } |
468 | | |
469 | | /* |
470 | | * Timecode: "VER xx.xx" |
471 | | * (from a TL3 when sent "QV", so id's it during initialization.) |
472 | | */ |
473 | 0 | if (pp->a_lastcode[0] == 'V' && pp->a_lastcode[1] == 'E' && |
474 | 0 | pp->a_lastcode[2] == 'R' && pp->a_lastcode[6] == '.') { |
475 | 0 | true_doevent(peer, e_TL3); |
476 | 0 | NLOG(NLOG_CLOCKSTATUS) { |
477 | 0 | msyslog(LOG_INFO, "TL3: %s", pp->a_lastcode); |
478 | 0 | } |
479 | 0 | return; |
480 | 0 | } |
481 | | |
482 | | /* |
483 | | * Timecode: " TRUETIME Mk III" or " TRUETIME XL" |
484 | | * (from a TM/TMD/XL clock during initialization.) |
485 | | */ |
486 | 0 | if (strncmp(pp->a_lastcode, " TRUETIME Mk III ", 17) == 0 || |
487 | 0 | strncmp(pp->a_lastcode, " TRUETIME XL", 12) == 0) { |
488 | 0 | true_doevent(peer, e_F18); |
489 | 0 | NLOG(NLOG_CLOCKSTATUS) { |
490 | 0 | msyslog(LOG_INFO, "TM/TMD/XL: %s", pp->a_lastcode); |
491 | 0 | } |
492 | 0 | return; |
493 | 0 | } |
494 | | |
495 | | /* |
496 | | * Timecode: "N03726428W12209421+000033" |
497 | | * 1 2 |
498 | | * index 0123456789012345678901234 |
499 | | * (from a TCU during initialization) |
500 | | */ |
501 | 0 | if ((pp->a_lastcode[0] == 'N' || pp->a_lastcode[0] == 'S') && |
502 | 0 | (pp->a_lastcode[9] == 'W' || pp->a_lastcode[9] == 'E') && |
503 | 0 | pp->a_lastcode[18] == '+') { |
504 | 0 | true_doevent(peer, e_Location); |
505 | 0 | NLOG(NLOG_CLOCKSTATUS) { |
506 | 0 | msyslog(LOG_INFO, "TCU-800: %s", pp->a_lastcode); |
507 | 0 | } |
508 | 0 | return; |
509 | 0 | } |
510 | | /* |
511 | | * Timecode: "ddd:hh:mm:ssQ" |
512 | | * 1 2 |
513 | | * index 0123456789012345678901234 |
514 | | * (from all clocks supported by this driver.) |
515 | | */ |
516 | 0 | if (pp->a_lastcode[3] == ':' && |
517 | 0 | pp->a_lastcode[6] == ':' && |
518 | 0 | pp->a_lastcode[9] == ':' && |
519 | 0 | sscanf(pp->a_lastcode, "%3d:%2d:%2d:%2d%c", |
520 | 0 | &pp->day, &pp->hour, &pp->minute, |
521 | 0 | &pp->second, &synced) == 5) { |
522 | | |
523 | | /* |
524 | | * Adjust the synchronize indicator according to timecode |
525 | | * say were OK, and then say not if we really are not OK |
526 | | */ |
527 | 0 | if (synced == '>' || synced == '#' || synced == '?' |
528 | 0 | || synced == 'X') |
529 | 0 | pp->leap = LEAP_NOTINSYNC; |
530 | 0 | else |
531 | 0 | pp->leap = LEAP_NOWARNING; |
532 | |
|
533 | 0 | true_doevent(peer, e_TS); |
534 | |
|
535 | | #ifdef CLOCK_PPS720 |
536 | | /* If it's taken more than 65ms to get here, we'll lose. */ |
537 | | if ((pp->sloppyclockflag & CLK_FLAG4) && up->pcl720init) { |
538 | | l_fp off; |
539 | | |
540 | | #ifdef CLOCK_ATOM |
541 | | /* |
542 | | * find out what time it really is. Include |
543 | | * the count from the PCL720 |
544 | | */ |
545 | | if (!clocktime(pp->day, pp->hour, pp->minute, |
546 | | pp->second, GMT, pp->lastrec.l_ui, |
547 | | &pp->yearstart, &off.l_ui)) { |
548 | | refclock_report(peer, CEVNT_BADTIME); |
549 | | return; |
550 | | } |
551 | | off.l_uf = 0; |
552 | | #endif |
553 | | |
554 | | pp->usec = true_sample720(); |
555 | | #ifdef CLOCK_ATOM |
556 | | TVUTOTSF(pp->usec, off.l_uf); |
557 | | #endif |
558 | | |
559 | | /* |
560 | | * Stomp all over the timestamp that was pulled out |
561 | | * of the input stream. It's irrelevant since we've |
562 | | * adjusted the input time to reflect now (via pp->usec) |
563 | | * rather than when the data was collected. |
564 | | */ |
565 | | get_systime(&pp->lastrec); |
566 | | #ifdef CLOCK_ATOM |
567 | | /* |
568 | | * Create a true offset for feeding to pps_sample() |
569 | | */ |
570 | | L_SUB(&off, &pp->lastrec); |
571 | | |
572 | | pps_sample(peer, &off); |
573 | | #endif |
574 | | true_debug(peer, "true_sample720: %luus\n", pp->usec); |
575 | | } |
576 | | #endif |
577 | | |
578 | | /* |
579 | | * The clock will blurt a timecode every second but we only |
580 | | * want one when polled. If we havn't been polled, bail out. |
581 | | */ |
582 | 0 | if (!up->polled) |
583 | 0 | return; |
584 | | |
585 | | /* We only call doevent if additional things need be done |
586 | | * at poll interval. Currently, its only for GOES. We also |
587 | | * call it for clock unknown so that it gets logged. |
588 | | */ |
589 | 0 | if (up->type == t_goes || up->type == t_unknown) |
590 | 0 | true_doevent(peer, e_Poll); |
591 | |
|
592 | 0 | if (!refclock_process(pp)) { |
593 | 0 | refclock_report(peer, CEVNT_BADTIME); |
594 | 0 | return; |
595 | 0 | } |
596 | | /* |
597 | | * If clock is good we send a NOMINAL message so that |
598 | | * any previous BAD messages are nullified |
599 | | */ |
600 | 0 | pp->lastref = pp->lastrec; |
601 | 0 | refclock_receive(peer); |
602 | 0 | refclock_report(peer, CEVNT_NOMINAL); |
603 | | |
604 | | /* |
605 | | * We have succedded in answering the poll. |
606 | | * Turn off the flag and return |
607 | | */ |
608 | 0 | up->polled = 0; |
609 | |
|
610 | 0 | return; |
611 | 0 | } |
612 | | |
613 | | /* |
614 | | * No match to known timecodes, report failure and return |
615 | | */ |
616 | 0 | refclock_report(peer, CEVNT_BADREPLY); |
617 | 0 | return; |
618 | 0 | } |
619 | | |
620 | | |
621 | | /* |
622 | | * true_send - time to send the clock a signal to cough up a time sample |
623 | | */ |
624 | | static void |
625 | | true_send( |
626 | | struct peer *peer, |
627 | | const char *cmd |
628 | | ) |
629 | 0 | { |
630 | 0 | struct refclockproc *pp; |
631 | |
|
632 | 0 | pp = peer->procptr; |
633 | 0 | if (!(pp->sloppyclockflag & CLK_FLAG1)) { |
634 | 0 | size_t len = strlen(cmd); |
635 | |
|
636 | 0 | true_debug(peer, "Send '%s'\n", cmd); |
637 | 0 | if (refclock_write(peer, cmd, len, NULL) != (ssize_t)len) |
638 | 0 | refclock_report(peer, CEVNT_FAULT); |
639 | 0 | else |
640 | 0 | pp->polls++; |
641 | 0 | } |
642 | 0 | } |
643 | | |
644 | | |
645 | | /* |
646 | | * state machine for initializing and controlling a clock |
647 | | */ |
648 | | static void |
649 | | true_doevent( |
650 | | struct peer *peer, |
651 | | enum true_event event |
652 | | ) |
653 | 0 | { |
654 | 0 | struct true_unit *up; |
655 | 0 | struct refclockproc *pp; |
656 | |
|
657 | 0 | pp = peer->procptr; |
658 | 0 | up = pp->unitptr; |
659 | 0 | if (event != e_TS) { |
660 | 0 | NLOG(NLOG_CLOCKSTATUS) { |
661 | 0 | msyslog(LOG_INFO, "TRUE: clock %s, state %s, event %s", |
662 | 0 | typeStr(up->type), |
663 | 0 | stateStr(up->state), |
664 | 0 | eventStr(event)); |
665 | 0 | } |
666 | 0 | } |
667 | 0 | true_debug(peer, "clock %s, state %s, event %s\n", |
668 | 0 | typeStr(up->type), stateStr(up->state), eventStr(event)); |
669 | 0 | switch (up->type) { |
670 | 0 | case t_goes: |
671 | 0 | switch (event) { |
672 | 0 | case e_Init: /* FALLTHROUGH */ |
673 | 0 | case e_Satellite: |
674 | | /* |
675 | | * Switch back to on-second time codes and return. |
676 | | */ |
677 | 0 | true_send(peer, "C"); |
678 | 0 | up->state = s_Start; |
679 | 0 | break; |
680 | 0 | case e_Poll: |
681 | | /* |
682 | | * After each poll, check the station (satellite). |
683 | | */ |
684 | 0 | true_send(peer, "P"); |
685 | | /* No state change needed. */ |
686 | 0 | break; |
687 | 0 | default: |
688 | 0 | break; |
689 | 0 | } |
690 | | /* FALLTHROUGH */ |
691 | 0 | case t_omega: |
692 | 0 | switch (event) { |
693 | 0 | case e_Init: |
694 | 0 | true_send(peer, "C"); |
695 | 0 | up->state = s_Start; |
696 | 0 | break; |
697 | 0 | case e_TS: |
698 | 0 | if (up->state != s_Start && up->state != s_Auto) { |
699 | 0 | true_send(peer, "\03\r"); |
700 | 0 | break; |
701 | 0 | } |
702 | 0 | up->state = s_Auto; |
703 | 0 | break; |
704 | 0 | default: |
705 | 0 | break; |
706 | 0 | } |
707 | 0 | break; |
708 | 0 | case t_tm: |
709 | 0 | switch (event) { |
710 | 0 | case e_Init: |
711 | 0 | true_send(peer, "F18\r"); |
712 | 0 | up->state = s_Init; |
713 | 0 | break; |
714 | 0 | case e_F18: |
715 | 0 | true_send(peer, "F50\r"); |
716 | | /* |
717 | | * Timecode: " TRUETIME Mk III" or " TRUETIME XL" |
718 | | * (from a TM/TMD/XL clock during initialization.) |
719 | | */ |
720 | 0 | if ( strcmp(pp->a_lastcode, " TRUETIME Mk III") == 0 || |
721 | 0 | strncmp(pp->a_lastcode, " TRUETIME XL", 12) == 0) { |
722 | 0 | true_doevent(peer, e_F18); |
723 | 0 | NLOG(NLOG_CLOCKSTATUS) { |
724 | 0 | msyslog(LOG_INFO, "TM/TMD/XL: %s", |
725 | 0 | pp->a_lastcode); |
726 | 0 | } |
727 | 0 | return; |
728 | 0 | } |
729 | 0 | up->state = s_F18; |
730 | 0 | break; |
731 | 0 | case e_F50: |
732 | 0 | true_send(peer, "F51\r"); |
733 | 0 | up->state = s_F50; |
734 | 0 | break; |
735 | 0 | case e_F51: |
736 | 0 | true_send(peer, "F08\r"); |
737 | 0 | up->state = s_Start; |
738 | 0 | break; |
739 | 0 | case e_TS: |
740 | 0 | if (up->state != s_Start && up->state != s_Auto) { |
741 | 0 | true_send(peer, "\03\r"); |
742 | 0 | break; |
743 | 0 | } |
744 | 0 | up->state = s_Auto; |
745 | 0 | break; |
746 | 0 | default: |
747 | 0 | break; |
748 | 0 | } |
749 | 0 | break; |
750 | 0 | case t_tcu: |
751 | 0 | switch (event) { |
752 | 0 | case e_Init: |
753 | 0 | true_send(peer, "MD3\r"); /* GPS Synch'd Gen. */ |
754 | 0 | true_send(peer, "TSU\r"); /* UTC, not GPS. */ |
755 | 0 | true_send(peer, "AU\r"); /* Auto Timestamps. */ |
756 | 0 | up->state = s_Start; |
757 | 0 | break; |
758 | 0 | case e_TS: |
759 | 0 | if (up->state != s_Start && up->state != s_Auto) { |
760 | 0 | true_send(peer, "\03\r"); |
761 | 0 | break; |
762 | 0 | } |
763 | 0 | up->state = s_Auto; |
764 | 0 | break; |
765 | 0 | default: |
766 | 0 | break; |
767 | 0 | } |
768 | 0 | break; |
769 | 0 | case t_tl3: |
770 | 0 | switch (event) { |
771 | 0 | case e_Init: |
772 | 0 | true_send(peer, "ST1"); /* Turn on continuous stream */ |
773 | 0 | break; |
774 | 0 | case e_TS: |
775 | 0 | up->state = s_Auto; |
776 | 0 | break; |
777 | 0 | default: |
778 | 0 | break; |
779 | 0 | } |
780 | 0 | break; |
781 | 0 | case t_unknown: |
782 | 0 | if (event == e_Poll) |
783 | 0 | break; |
784 | 0 | switch (up->state) { |
785 | 0 | case s_Base: |
786 | 0 | if (event != e_Init) |
787 | 0 | abort(); |
788 | 0 | true_send(peer, "P\r"); |
789 | 0 | up->state = s_InqGOES; |
790 | 0 | break; |
791 | 0 | case s_InqGOES: |
792 | 0 | switch (event) { |
793 | 0 | case e_Satellite: |
794 | 0 | up->type = t_goes; |
795 | 0 | true_doevent(peer, e_Init); |
796 | 0 | break; |
797 | 0 | case e_Init: /*FALLTHROUGH*/ |
798 | 0 | case e_Huh: |
799 | 0 | case e_TS: |
800 | 0 | true_send(peer, "ST0"); /* turn off TL3 auto */ |
801 | 0 | sleep(1); /* wait for it */ |
802 | 0 | up->state = s_InqTL3; |
803 | 0 | true_send(peer, "QV"); /* see if its a TL3 */ |
804 | 0 | break; |
805 | 0 | default: |
806 | 0 | abort(); |
807 | 0 | } |
808 | 0 | break; |
809 | 0 | case s_InqTL3: |
810 | 0 | switch (event) { |
811 | 0 | case e_TL3: |
812 | 0 | up->type = t_tl3; |
813 | 0 | up->state = s_Auto; /* Inq side-effect. */ |
814 | 0 | true_send(peer, "ST1"); /* Turn on 1/sec data */ |
815 | 0 | break; |
816 | 0 | case e_Init: /*FALLTHROUGH*/ |
817 | 0 | case e_Huh: |
818 | 0 | up->state = s_InqOmega; |
819 | 0 | true_send(peer, "C\r"); |
820 | 0 | break; |
821 | 0 | case e_TS: |
822 | 0 | up->type = t_tl3; /* Already sending data */ |
823 | 0 | up->state = s_Auto; |
824 | 0 | break; |
825 | 0 | default: |
826 | 0 | msyslog(LOG_INFO, |
827 | 0 | "TRUE: TL3 init fellthrough! (%d)", event); |
828 | 0 | break; |
829 | 0 | } |
830 | 0 | break; |
831 | 0 | case s_InqOmega: |
832 | 0 | switch (event) { |
833 | 0 | case e_TS: |
834 | 0 | up->type = t_omega; |
835 | 0 | up->state = s_Auto; /* Inq side-effect. */ |
836 | 0 | break; |
837 | 0 | case e_Init: /*FALLTHROUGH*/ |
838 | 0 | case e_Huh: |
839 | 0 | up->state = s_InqTM; |
840 | 0 | true_send(peer, "F18\r"); |
841 | 0 | break; |
842 | 0 | default: |
843 | 0 | abort(); |
844 | 0 | } |
845 | 0 | break; |
846 | 0 | case s_InqTM: |
847 | 0 | switch (event) { |
848 | 0 | case e_F18: |
849 | 0 | up->type = t_tm; |
850 | 0 | true_doevent(peer, e_Init); |
851 | 0 | break; |
852 | 0 | case e_Init: /*FALLTHROUGH*/ |
853 | 0 | case e_Huh: |
854 | 0 | true_send(peer, "PO\r"); |
855 | 0 | up->state = s_InqTCU; |
856 | 0 | break; |
857 | 0 | default: |
858 | 0 | msyslog(LOG_INFO, |
859 | 0 | "TRUE: TM/TMD init fellthrough!"); |
860 | 0 | break; |
861 | 0 | } |
862 | 0 | break; |
863 | 0 | case s_InqTCU: |
864 | 0 | switch (event) { |
865 | 0 | case e_Location: |
866 | 0 | up->type = t_tcu; |
867 | 0 | true_doevent(peer, e_Init); |
868 | 0 | break; |
869 | 0 | case e_Init: /*FALLTHROUGH*/ |
870 | 0 | case e_Huh: |
871 | 0 | up->state = s_Base; |
872 | 0 | sleep(1); /* XXX */ |
873 | 0 | break; |
874 | 0 | default: |
875 | 0 | msyslog(LOG_INFO, |
876 | 0 | "TRUE: TCU init fellthrough!"); |
877 | 0 | break; |
878 | 0 | } |
879 | 0 | break; |
880 | | /* |
881 | | * An expedient hack to prevent lint complaints, |
882 | | * these don't actually need to be used here... |
883 | | */ |
884 | 0 | case s_Init: |
885 | 0 | case s_F18: |
886 | 0 | case s_F50: |
887 | 0 | case s_Start: |
888 | 0 | case s_Auto: |
889 | 0 | case s_Max: |
890 | 0 | msyslog(LOG_INFO, "TRUE: state %s is unexpected!", |
891 | 0 | stateStr(up->state)); |
892 | 0 | } |
893 | 0 | break; |
894 | 0 | default: |
895 | 0 | msyslog(LOG_INFO, "TRUE: cannot identify refclock!"); |
896 | 0 | abort(); |
897 | | /* NOTREACHED */ |
898 | 0 | } |
899 | |
|
900 | | #ifdef CLOCK_PPS720 |
901 | | if ((pp->sloppyclockflag & CLK_FLAG4) && !up->pcl720init) { |
902 | | /* Make counter trigger on gate0, count down from 65535. */ |
903 | | pcl720_load(PCL720_IOB, PCL720_CTR, i8253_oneshot, 65535); |
904 | | /* |
905 | | * (These constants are OK since |
906 | | * they represent hardware maximums.) |
907 | | */ |
908 | | NLOG(NLOG_CLOCKINFO) { |
909 | | msyslog(LOG_NOTICE, "PCL-720 initialized"); |
910 | | } |
911 | | up->pcl720init++; |
912 | | } |
913 | | #endif |
914 | | |
915 | |
|
916 | 0 | } |
917 | | |
918 | | /* |
919 | | * true_poll - called by the transmit procedure |
920 | | */ |
921 | | static void |
922 | | true_poll( |
923 | | int unit, |
924 | | struct peer *peer |
925 | | ) |
926 | 0 | { |
927 | 0 | struct true_unit *up; |
928 | 0 | struct refclockproc *pp; |
929 | | |
930 | | /* |
931 | | * You don't need to poll this clock. It puts out timecodes |
932 | | * once per second. If asked for a timestamp, take note. |
933 | | * The next time a timecode comes in, it will be fed back. |
934 | | */ |
935 | 0 | pp = peer->procptr; |
936 | 0 | up = pp->unitptr; |
937 | 0 | if (up->pollcnt > 0) { |
938 | 0 | up->pollcnt--; |
939 | 0 | } else { |
940 | 0 | true_doevent(peer, e_Init); |
941 | 0 | refclock_report(peer, CEVNT_TIMEOUT); |
942 | 0 | } |
943 | | |
944 | | /* |
945 | | * polled every 64 seconds. Ask true_receive to hand in a |
946 | | * timestamp. |
947 | | */ |
948 | 0 | up->polled = 1; |
949 | 0 | pp->polls++; |
950 | 0 | } |
951 | | |
952 | | #ifdef CLOCK_PPS720 |
953 | | /* |
954 | | * true_sample720 - sample the PCL-720 |
955 | | */ |
956 | | static u_long |
957 | | true_sample720(void) |
958 | | { |
959 | | unsigned long f; |
960 | | |
961 | | /* We wire the PCL-720's 8253.OUT0 to bit 0 of connector 3. |
962 | | * If it is not being held low now, we did not get called |
963 | | * within 65535us. |
964 | | */ |
965 | | if (inb(pcl720_data_16_23(PCL720_IOB)) & 0x01) { |
966 | | NLOG(NLOG_CLOCKINFO) { |
967 | | msyslog(LOG_NOTICE, "PCL-720 out of synch"); |
968 | | } |
969 | | return (0); |
970 | | } |
971 | | f = (65536 - pcl720_read(PCL720_IOB, PCL720_CTR)); |
972 | | #ifdef PPS720_DEBUG |
973 | | msyslog(LOG_DEBUG, "PCL-720: %luus", f); |
974 | | #endif |
975 | | return (f); |
976 | | } |
977 | | #endif |
978 | | |
979 | | #else |
980 | | NONEMPTY_TRANSLATION_UNIT |
981 | | #endif /* REFCLOCK */ |