option-tester: Add many changes
[ntpsec.git] / ntpd / refclock_zyfer.c
blob3b81472a0571865a59a6272803496f1bfb351ba4
1 /*
2 * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
4 * Harlan Stenn, Jan 2002
5 */
7 #include "config.h"
8 #include "ntp.h"
9 #include "ntpd.h"
10 #include "ntp_io.h"
11 #include "ntp_refclock.h"
12 #include "ntp_stdlib.h"
14 #include <stdio.h>
15 #include <ctype.h>
17 #include <termios.h>
20 * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
21 * This clock also provides PPS as well as IRIG outputs.
22 * Precision is limited by the serial driver, etc.
24 * If I was really brave I'd hack/generalize the serial driver to deal
25 * with arbitrary on-time characters. This clock *begins* the stream with
26 * `!`, the on-time character, and the string is *not* EOL-terminated.
28 * Configure the beast for 9600, 8N1. While I see leap-second stuff
29 * in the documentation, the published specs on the TOD format only show
30 * the seconds going to '59'. I see no leap warning in the TOD format.
32 * The clock sends the following message once per second:
34 * !TIME,2002,017,07,59,32,2,4,1
35 * YYYY DDD HH MM SS m T O
37 * ! On-time character
38 * YYYY Year
39 * DDD 001-366 Day of Year
40 * HH 00-23 Hour
41 * MM 00-59 Minute
42 * SS 00-59 Second (probably 00-60)
43 * m 1-5 Time Mode:
44 * 1 = GPS time
45 * 2 = UTC time
46 * 3 = LGPS time (Local GPS)
47 * 4 = LUTC time (Local UTC)
48 * 5 = Manual time
49 * T 4-9 Time Figure Of Merit:
50 * 4 x <= 1us
51 * 5 1us < x <= 10 us
52 * 6 10us < x <= 100us
53 * 7 100us < x <= 1ms
54 * 8 1ms < x <= 10ms
55 * 9 10ms < x
56 * O 0-4 Operation Mode:
57 * 0 Warm-up
58 * 1 Time Locked
59 * 2 Coasting
60 * 3 Recovering
61 * 4 Manual
66 * Interface definitions
68 #define DEVICE "/dev/zyfer%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 NAME "ZYFER" /* shortname */
73 #define DESCRIPTION "Zyfer GPStarplus" /* WRU */
75 #define LENZYFER 29 /* timecode length */
78 * Unit control structure
80 struct zyferunit {
81 uint8_t Rcvbuf[LENZYFER + 1];
82 uint8_t polled; /* poll message flag */
83 int pollcnt;
84 int Rcvptr;
88 * Function prototypes
90 static bool zyfer_start (int, struct peer *);
91 static void zyfer_receive (struct recvbuf *);
92 static void zyfer_poll (int, struct peer *);
95 * Transfer vector
97 struct refclock refclock_zyfer = {
98 NAME, /* basename of driver */
99 zyfer_start, /* start up driver */
100 NULL, /* shut down driver in the standard way */
101 zyfer_poll, /* transmit poll message */
102 NULL, /* not used (old zyfer_control) */
103 NULL, /* initialize driver (not used) */
104 NULL /* timer - not used */
109 * zyfer_start - open the devices and initialize data for processing
111 static bool
112 zyfer_start(
113 int unit,
114 struct peer *peer
117 struct zyferunit *up;
118 struct refclockproc *pp;
119 int fd;
120 char device[20];
123 * Open serial port.
124 * Something like LDISC_ACTS that looked for ! would be nice...
126 snprintf(device, sizeof(device), DEVICE, unit);
127 fd = refclock_open(peer->cfg.path ? peer->cfg.path : device,
128 peer->cfg.baud ? peer->cfg.baud : SPEED232,
129 LDISC_RAW);
130 if (fd <= 0)
131 /* coverity[leaked_handle] */
132 return false;
134 msyslog(LOG_NOTICE, "REFCLOCK: zyfer(%d) fd: %d", unit, fd);
137 * Allocate and initialize unit structure
139 up = emalloc_zero(sizeof(struct zyferunit));
140 pp = peer->procptr;
141 pp->io.clock_recv = zyfer_receive;
142 pp->io.srcclock = peer;
143 pp->io.datalen = 0;
144 pp->io.fd = fd;
145 if (!io_addclock(&pp->io)) {
146 close(fd);
147 pp->io.fd = -1;
148 free(up);
149 return false;
151 pp->unitptr = up;
154 * Initialize miscellaneous variables
156 peer->precision = PRECISION;
157 pp->clockname = NAME;
158 pp->clockdesc = DESCRIPTION;
159 memcpy((char *)&pp->refid, REFID, REFIDLEN);
160 peer->sstclktype = CTL_SST_TS_UHF;
161 up->pollcnt = 2;
162 up->polled = 0; /* May not be needed... */
164 return true;
169 * zyfer_receive - receive data from the serial interface
171 static void
172 zyfer_receive(
173 struct recvbuf *rbufp
176 struct zyferunit *up;
177 struct refclockproc *pp;
178 struct peer *peer;
179 int tmode; /* Time mode */
180 int tfom; /* Time Figure Of Merit */
181 int omode; /* Operation mode */
182 uint8_t *p;
184 peer = rbufp->recv_peer;
185 pp = peer->procptr;
186 up = pp->unitptr;
187 p = (uint8_t *) &rbufp->recv_buffer;
189 * If lencode is 0:
190 * - if *rbufp->recv_buffer is !
191 * - - call refclock_gtlin to get things going
192 * - else flush
193 * else stuff it on the end of lastcode
194 * If we don't have LENZYFER bytes
195 * - wait for more data
196 * Crack the beast, and if it's OK, process it.
198 * We use refclock_gtlin() because we might use LDISC_CLK.
200 * Under FreeBSD, we get the ! followed by two 14-byte packets.
203 if (pp->lencode >= LENZYFER)
204 pp->lencode = 0;
206 if (!pp->lencode) {
207 if (*p == '!')
208 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
209 BMAX, &pp->lastrec);
210 else
211 return;
212 } else {
213 memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
214 pp->lencode += (int)rbufp->recv_length;
215 pp->a_lastcode[pp->lencode] = '\0';
218 if (pp->lencode < LENZYFER)
219 return;
221 record_clock_stats(peer, pp->a_lastcode);
224 * We get down to business, check the timecode format and decode
225 * its contents. If the timecode has invalid length or is not in
226 * proper format, we declare bad format and exit.
229 if (pp->lencode != LENZYFER) {
230 refclock_report(peer, CEVNT_BADTIME);
231 return;
235 * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
237 if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
238 &pp->year, &pp->yday, &pp->hour, &pp->minute, &pp->second,
239 &tmode, &tfom, &omode) != 8) {
240 refclock_report(peer, CEVNT_BADREPLY);
241 return;
244 if (tmode != 2) {
245 refclock_report(peer, CEVNT_BADTIME);
246 return;
249 /* Should we make sure tfom is 4? */
251 if (omode != 1) {
252 pp->leap = LEAP_NOTINSYNC;
253 return;
256 if (!refclock_process(pp)) {
257 refclock_report(peer, CEVNT_BADTIME);
258 return;
262 * Good place for record_clock_stats()
264 up->pollcnt = 2;
266 if (up->polled) {
267 up->polled = 0;
268 refclock_receive(peer);
274 * zyfer_poll - called by the transmit procedure
276 static void
277 zyfer_poll(
278 int unit,
279 struct peer *peer
282 struct zyferunit *up;
283 struct refclockproc *pp;
285 UNUSED_ARG(unit);
288 * We don't really do anything here, except arm the receiving
289 * side to capture a sample and check for timeouts.
291 pp = peer->procptr;
292 up = pp->unitptr;
293 if (!up->pollcnt) {
294 refclock_report(peer, CEVNT_TIMEOUT);
295 } else {
296 up->pollcnt--;
298 pp->polls++;
299 up->polled = 1;