1 /*        $NetBSD: refclock_zyfer.c,v 1.6 2024/08/18 20:47:19 christos Exp $    */
2 
3 /*
4  * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
5  *
6  * Harlan Stenn, Jan 2002
7  */
8 
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12 
13 #if defined(REFCLOCK) && defined(CLOCK_ZYFER)
14 
15 #include "ntpd.h"
16 #include "ntp_io.h"
17 #include "ntp_refclock.h"
18 #include "ntp_stdlib.h"
19 #include "ntp_unixtime.h"
20 #include "ntp_calgps.h"
21 
22 #include <stdio.h>
23 #include <ctype.h>
24 
25 #if defined(HAVE_TERMIOS_H)
26 # include <termios.h>
27 #elif defined(HAVE_SYS_TERMIOS_H)
28 # include <sys/termios.h>
29 #endif
30 #ifdef HAVE_SYS_PPSCLOCK_H
31 # include <sys/ppsclock.h>
32 #endif
33 
34 /*
35  * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
36  * This clock also provides PPS as well as IRIG outputs.
37  * Precision is limited by the serial driver, etc.
38  *
39  * If I was really brave I'd hack/generalize the serial driver to deal
40  * with arbitrary on-time characters.  This clock *begins* the stream with
41  * `!`, the on-time character, and the string is *not* EOL-terminated.
42  *
43  * Configure the beast for 9600, 8N1.  While I see leap-second stuff
44  * in the documentation, the published specs on the TOD format only show
45  * the seconds going to '59'.  I see no leap warning in the TOD format.
46  *
47  * The clock sends the following message once per second:
48  *
49  *        !TIME,2002,017,07,59,32,2,4,1
50  *              YYYY DDD HH MM SS m T O
51  *
52  *        !                   On-time character
53  *        YYYY                Year
54  *        DDD       001-366   Day of Year
55  *        HH        00-23     Hour
56  *        MM        00-59     Minute
57  *        SS        00-59     Second (probably 00-60)
58  *        m         1-5       Time Mode:
59  *                            1 = GPS time
60  *                            2 = UTC time
61  *                            3 = LGPS time (Local GPS)
62  *                            4 = LUTC time (Local UTC)
63  *                            5 = Manual time
64  *        T         4-9       Time Figure Of Merit:
65  *                            4         x <= 1us
66  *                            5   1us < x <= 10 us
67  *                            6  10us < x <= 100us
68  *                            7 100us < x <= 1ms
69  *                            8   1ms < x <= 10ms
70  *                            9  10ms < x
71  *        O         0-4       Operation Mode:
72  *                            0 Warm-up
73  *                            1 Time Locked
74  *                            2 Coasting
75  *                            3 Recovering
76  *                            4 Manual
77  *
78  */
79 
80 /*
81  * Interface definitions
82  */
83 #define   DEVICE              "/dev/zyfer%d" /* device name and unit */
84 #define   SPEED232  B9600     /* uart speed (9600 baud) */
85 #define   PRECISION (-20)     /* precision assumed (about 1 us) */
86 #define   REFID               "GPS\0"   /* reference ID */
87 #define   DESCRIPTION         "Zyfer GPStarplus" /* WRU */
88 
89 #define   LENZYFER  29        /* timecode length */
90 
91 /*
92  * Unit control structure
93  */
94 struct zyferunit {
95           u_char    Rcvbuf[LENZYFER + 1];
96           u_char    polled;             /* poll message flag */
97           int       pollcnt;
98           l_fp    tstamp;         /* timestamp of last poll */
99           int       Rcvptr;
100 };
101 
102 /*
103  * Function prototypes
104  */
105 static    int       zyfer_start         (int, struct peer *);
106 static    void      zyfer_shutdown      (int, struct peer *);
107 static    void      zyfer_receive       (struct recvbuf *);
108 static    void      zyfer_poll          (int, struct peer *);
109 
110 /*
111  * Transfer vector
112  */
113 struct    refclock refclock_zyfer = {
114           zyfer_start,                  /* start up driver */
115           zyfer_shutdown,               /* shut down driver */
116           zyfer_poll,                   /* transmit poll message */
117           noentry,            /* not used (old zyfer_control) */
118           noentry,            /* initialize driver (not used) */
119           noentry,            /* not used (old zyfer_buginfo) */
120           NOFLAGS                       /* not used */
121 };
122 
123 
124 /*
125  * zyfer_start - open the devices and initialize data for processing
126  */
127 static int
zyfer_start(int unit,struct peer * peer)128 zyfer_start(
129           int unit,
130           struct peer *peer
131           )
132 {
133           register struct zyferunit *up;
134           struct refclockproc *pp;
135           int fd;
136           char device[20];
137 
138           /*
139            * Open serial port.
140            * Something like LDISC_ACTS that looked for ! would be nice...
141            */
142           snprintf(device, sizeof(device), DEVICE, unit);
143           fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_RAW);
144           if (fd <= 0)
145                     return (0);
146 
147           msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device);
148 
149           /*
150            * Allocate and initialize unit structure
151            */
152           up = emalloc(sizeof(struct zyferunit));
153           memset(up, 0, sizeof(struct zyferunit));
154           pp = peer->procptr;
155           pp->io.clock_recv = zyfer_receive;
156           pp->io.srcclock = peer;
157           pp->io.datalen = 0;
158           pp->io.fd = fd;
159           if (!io_addclock(&pp->io)) {
160                     close(fd);
161                     pp->io.fd = -1;
162                     free(up);
163                     return (0);
164           }
165           pp->unitptr = up;
166 
167           /*
168            * Initialize miscellaneous variables
169            */
170           peer->precision = PRECISION;
171           pp->clockdesc = DESCRIPTION;
172           memcpy((char *)&pp->refid, REFID, 4);
173           up->pollcnt = 2;
174           up->polled = 0;               /* May not be needed... */
175 
176           return (1);
177 }
178 
179 
180 /*
181  * zyfer_shutdown - shut down the clock
182  */
183 static void
zyfer_shutdown(int unit,struct peer * peer)184 zyfer_shutdown(
185           int unit,
186           struct peer *peer
187           )
188 {
189           register struct zyferunit *up;
190           struct refclockproc *pp;
191 
192           pp = peer->procptr;
193           up = pp->unitptr;
194           if (pp->io.fd != -1)
195                     io_closeclock(&pp->io);
196           if (up != NULL)
197                     free(up);
198 }
199 
200 
201 /*
202  * zyfer_receive - receive data from the serial interface
203  */
204 static void
zyfer_receive(struct recvbuf * rbufp)205 zyfer_receive(
206           struct recvbuf *rbufp
207           )
208 {
209           register struct zyferunit *up;
210           struct refclockproc *pp;
211           struct peer *peer;
212           int tmode;                    /* Time mode */
213           int tfom;           /* Time Figure Of Merit */
214           int omode;                    /* Operation mode */
215           u_char *p;
216 
217           TCivilDate          tsdoy;
218           TNtpDatum tsntp;
219           l_fp                tfrac;
220 
221           peer = rbufp->recv_peer;
222           pp = peer->procptr;
223           up = pp->unitptr;
224           p = (u_char *) &rbufp->recv_space;
225           /*
226            * If lencode is 0:
227            * - if *rbufp->recv_space is !
228            * - - call refclock_gtlin to get things going
229            * - else flush
230            * else stuff it on the end of lastcode
231            * If we don't have LENZYFER bytes
232            * - wait for more data
233            * Crack the beast, and if it's OK, process it.
234            *
235            * We use refclock_gtlin() because we might use LDISC_CLK.
236            *
237            * Under FreeBSD, we get the ! followed by two 14-byte packets.
238            */
239 
240           if (pp->lencode >= LENZYFER)
241                     pp->lencode = 0;
242 
243           if (!pp->lencode) {
244                     if (*p == '!')
245                               pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
246                                                                  BMAX, &pp->lastrec);
247                     else
248                               return;
249           } else {
250                     memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
251                     pp->lencode += rbufp->recv_length;
252                     pp->a_lastcode[pp->lencode] = '\0';
253           }
254 
255           if (pp->lencode < LENZYFER)
256                     return;
257 
258           record_clock_stats(&peer->srcadr, pp->a_lastcode);
259 
260           /*
261            * We get down to business, check the timecode format and decode
262            * its contents. If the timecode has invalid length or is not in
263            * proper format, we declare bad format and exit.
264            */
265 
266           if (pp->lencode != LENZYFER) {
267                     refclock_report(peer, CEVNT_BADTIME);
268                     return;
269           }
270 
271           /*
272            * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
273            */
274           if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
275                        &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second,
276                        &tmode, &tfom, &omode) != 8) {
277                     refclock_report(peer, CEVNT_BADREPLY);
278                     return;
279           }
280 
281           if (tmode != 2) {
282                     refclock_report(peer, CEVNT_BADTIME);
283                     return;
284           }
285 
286           /* Should we make sure tfom is 4? */
287 
288           if (omode != 1) {
289                     pp->leap = LEAP_NOTINSYNC;
290                     return;
291           }
292 
293           /* treat GPS input as subject to era warps */
294           ZERO(tsdoy);
295           ZERO(tfrac);
296 
297           tsdoy.year    = pp->year;
298           tsdoy.yearday = pp->day;
299           tsdoy.hour    = pp->hour;
300           tsdoy.minute  = pp->minute;
301           tsdoy.second  = pp->second;
302 
303           /* note: We kept 'month' and 'monthday' zero above. That forces
304            * day-of-year based calculation now:
305            */
306           tsntp = gpsntp_from_calendar(&tsdoy, tfrac);
307           tfrac = ntpfp_from_ntpdatum(&tsntp);
308           refclock_process_offset(pp, tfrac, pp->lastrec, pp->fudgetime1);
309 
310           /*
311            * Good place for record_clock_stats()
312            */
313           up->pollcnt = 2;
314 
315           if (up->polled) {
316                     up->polled = 0;
317                     refclock_receive(peer);
318           }
319 }
320 
321 
322 /*
323  * zyfer_poll - called by the transmit procedure
324  */
325 static void
zyfer_poll(int unit,struct peer * peer)326 zyfer_poll(
327           int unit,
328           struct peer *peer
329           )
330 {
331           register struct zyferunit *up;
332           struct refclockproc *pp;
333 
334           /*
335            * We don't really do anything here, except arm the receiving
336            * side to capture a sample and check for timeouts.
337            */
338           pp = peer->procptr;
339           up = pp->unitptr;
340           if (!up->pollcnt)
341                     refclock_report(peer, CEVNT_TIMEOUT);
342           else
343                     up->pollcnt--;
344           pp->polls++;
345           up->polled = 1;
346 }
347 
348 #else
349 NONEMPTY_TRANSLATION_UNIT
350 #endif /* REFCLOCK */
351