1 /*        $NetBSD: refclock_fg.c,v 1.6 2024/08/18 20:47:18 christos Exp $       */
2 
3 /*
4  * refclock_fg - clock driver for the Forum Graphic GPS datating station
5  */
6 
7 #ifdef HAVE_CONFIG_H
8 # include <config.h>
9 #endif
10 
11 #if defined(REFCLOCK) && defined(CLOCK_FG)
12 
13 #include "ntpd.h"
14 #include "ntp_io.h"
15 #include "ntp_refclock.h"
16 #include "ntp_calendar.h"
17 #include "ntp_stdlib.h"
18 
19 /*
20  * This driver supports the Forum Graphic GPS dating station.
21  * More information about FG GPS is available on http://www.forumgraphic.com
22  * Contact das@amt.ru for any question about this driver.
23  */
24 
25 /*
26  * Interface definitions
27  */
28 #define   DEVICE              "/dev/fgclock%d"
29 #define   PRECISION (-10)     /* precision assumed (about 1 ms) */
30 #define REFID                 "GPS"
31 #define DESCRIPTION "Forum Graphic GPS dating station"
32 #define LENFG                 26        /* timecode length */
33 #define SPEED232        B9600   /* uart speed (9600 baud) */
34 
35 /*
36  * Function prototypes
37  */
38 static    int       fg_init   (int);
39 static    int       fg_start  (int, struct peer *);
40 static    void      fg_shutdown         (int, struct peer *);
41 static    void      fg_poll             (int, struct peer *);
42 static    void      fg_receive          (struct recvbuf *);
43 
44 /*
45  * Forum Graphic unit control structure
46  */
47 
48 struct fgunit {
49           int pollnum;        /* Use peer.poll instead? */
50           int status;         /* Hug to check status information on GPS */
51           int y2kwarn;        /* Y2K bug */
52 };
53 
54 /*
55  * Queries definition
56  */
57 static char fginit[] = { 0x10, 0x48, 0x10, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58 0, 0, 0, 0, 0, 0, 0, 0, 0 };
59 static char fgdate[] = { 0x10, 0x44, 0x10, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
60 0, 0, 0, 0, 0, 0, 0, 0, 0 };
61 
62 /*
63  * Transfer vector
64  */
65 struct  refclock refclock_fg = {
66           fg_start,           /* start up driver */
67           fg_shutdown,                  /* shut down driver */
68           fg_poll,            /* transmit poll message */
69           noentry,            /* not used */
70           noentry,            /* initialize driver (not used) */
71           noentry,            /* not used */
72           NOFLAGS                       /* not used */
73 };
74 
75 /*
76  * fg_init - Initialization of FG GPS.
77  */
78 
79 static int
fg_init(int fd)80 fg_init(
81           int fd
82           )
83 {
84           if (write(fd, fginit, LENFG) != LENFG)
85                     return 0;
86 
87           return 1;
88 }
89 
90 /*
91  * fg_start - open the device and initialize data for processing
92  */
93 static int
fg_start(int unit,struct peer * peer)94 fg_start(
95           int unit,
96           struct peer *peer
97           )
98 {
99           struct refclockproc *pp;
100           struct fgunit *up;
101           int fd;
102           char device[20];
103 
104 
105           /*
106            * Open device file for reading.
107            */
108           snprintf(device, sizeof(device), DEVICE, unit);
109 
110           DPRINTF(1, ("starting FG with device %s\n",device));
111 
112           fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK);
113           if (fd <= 0)
114                     return (0);
115 
116           /*
117            * Allocate and initialize unit structure
118            */
119 
120           up = emalloc(sizeof(struct fgunit));
121           memset(up, 0, sizeof(struct fgunit));
122           pp = peer->procptr;
123           pp->unitptr = up;
124           pp->io.clock_recv = fg_receive;
125           pp->io.srcclock = peer;
126           pp->io.datalen = 0;
127           pp->io.fd = fd;
128           if (!io_addclock(&pp->io)) {
129                     close(fd);
130                     pp->io.fd = -1;
131                     return 0;
132           }
133 
134 
135           /*
136            * Initialize miscellaneous variables
137            */
138           peer->precision = PRECISION;
139           pp->clockdesc = DESCRIPTION;
140           memcpy(&pp->refid, REFID, 3);
141           up->pollnum = 0;
142 
143           /*
144            * Setup dating station to use GPS receiver.
145            * GPS receiver should work before this operation.
146            */
147           if(!fg_init(pp->io.fd))
148                     refclock_report(peer, CEVNT_FAULT);
149 
150           return (1);
151 }
152 
153 
154 /*
155  * fg_shutdown - shut down the clock
156  */
157 static void
fg_shutdown(int unit,struct peer * peer)158 fg_shutdown(
159           int unit,
160           struct peer *peer
161           )
162 {
163           struct refclockproc *pp;
164           struct fgunit *up;
165 
166           pp = peer->procptr;
167           up = pp->unitptr;
168           if (pp->io.fd != -1)
169                     io_closeclock(&pp->io);
170           if (up != NULL)
171                     free(up);
172 }
173 
174 
175 /*
176  * fg_poll - called by the transmit procedure
177  */
178 static void
fg_poll(int unit,struct peer * peer)179 fg_poll(
180           int unit,
181           struct peer *peer
182           )
183 {
184           struct refclockproc *pp;
185 
186           pp = peer->procptr;
187 
188           /*
189            * Time to poll the clock. The FG clock responds to a
190            * "<DLE>D<DLE><CR>" by returning a timecode in the format specified
191            * above. If nothing is heard from the clock for two polls,
192            * declare a timeout and keep going.
193            */
194 
195           if (write(pp->io.fd, fgdate, LENFG) != LENFG)
196                     refclock_report(peer, CEVNT_FAULT);
197           else
198                     pp->polls++;
199 
200           /*
201           if (pp->coderecv == pp->codeproc) {
202                     refclock_report(peer, CEVNT_TIMEOUT);
203                     return;
204           }
205           */
206 
207           record_clock_stats(&peer->srcadr, pp->a_lastcode);
208 
209           return;
210 
211 }
212 
213 /*
214  * fg_receive - receive data from the serial interface
215  */
216 static void
fg_receive(struct recvbuf * rbufp)217 fg_receive(
218           struct recvbuf *rbufp
219           )
220 {
221           struct refclockproc *pp;
222           struct fgunit *up;
223           struct peer *peer;
224           char *bpt;
225 
226           /*
227            * Initialize pointers and read the timecode and timestamp
228            * We can't use gtlin function because we need bynary data in buf */
229 
230           peer = rbufp->recv_peer;
231           pp = peer->procptr;
232           up = pp->unitptr;
233 
234           /*
235            * Below hug to implement receiving of status information
236            */
237           if(!up->pollnum) {
238                     up->pollnum++;
239                     return;
240           }
241 
242 
243           if (rbufp->recv_length < (LENFG - 2)) {
244                     refclock_report(peer, CEVNT_BADREPLY);
245                     return; /* The reply is invalid discard it. */
246           }
247 
248           /* Below I trying to find a correct reply in buffer.
249            * Sometime GPS reply located in the beginning of buffer,
250            * sometime you can find it with some offset.
251            */
252 
253           bpt = (char *)rbufp->recv_buffer;
254           while (*bpt != '\x10')
255                     bpt++;
256 
257 #define BP2(x) ( bpt[x] & 15 )
258 #define BP1(x) (( bpt[x] & 240 ) >> 4)
259 
260           pp->year = BP1(2) * 10 + BP2(2);
261 
262           if (pp->year == 94) {
263                     refclock_report(peer, CEVNT_BADREPLY);
264                     if (!fg_init(pp->io.fd))
265                               refclock_report(peer, CEVNT_FAULT);
266                     return;
267                      /* GPS is just powered up. The date is invalid -
268                      discarding it. Initilize GPS one more time */
269                     /* Sorry - this driver will broken in 2094 ;) */
270           }
271 
272           if (pp->year < 99)
273                     pp->year += 100;
274 
275           pp->year +=  1900;
276           pp->day = 100 * BP2(3) + 10 * BP1(4) + BP2(4);
277 
278 /*
279    After Jan, 10 2000 Forum Graphic GPS receiver had a very strange
280    benahour. It doubles day number for an hours in replys after 10:10:10 UTC
281    and doubles min every hour at HH:10:ss for a minute.
282    Hope it is a problem of my unit only and not a Y2K problem of FG GPS.
283    Below small code to avoid such situation.
284 */
285           if (up->y2kwarn > 10)
286                     pp->hour = BP1(6)*10 + BP2(6);
287           else
288                     pp->hour = BP1(5)*10 + BP2(5);
289 
290           if ((up->y2kwarn > 10) && (pp->hour == 10)) {
291                     pp->minute = BP1(7)*10 + BP2(7);
292                     pp->second = BP1(8)*10 + BP2(8);
293                     pp->nsec = (BP1(9)*10 + BP2(9)) * 1000000;
294                     pp->nsec += BP1(10) * 1000;
295           } else {
296                     pp->hour = BP1(5)*10 + BP2(5);
297                     pp->minute = BP1(6)*10 + BP2(6);
298                     pp->second = BP1(7)*10 + BP2(7);
299                     pp->nsec = (BP1(8)*10 + BP2(8)) * 1000000;
300                     pp->nsec += BP1(9) * 1000;
301           }
302 
303           if ((pp->hour == 10) && (pp->minute == 10)) {
304                     up->y2kwarn++;
305           }
306 
307           snprintf(pp->a_lastcode, sizeof(pp->a_lastcode),
308                      "%d %d %d %d %d", pp->year, pp->day, pp->hour,
309                      pp->minute, pp->second);
310           pp->lencode = strlen(pp->a_lastcode);
311           /*get_systime(&pp->lastrec);*/
312 
313 #ifdef DEBUG
314           if (debug)
315                     printf("fg: time is %04d/%03d %02d:%02d:%02d UTC\n",
316                            pp->year, pp->day, pp->hour, pp->minute, pp->second);
317 #endif
318           pp->disp =  (10e-6);
319           pp->lastrec = rbufp->recv_time; /* Is it better than get_systime()? */
320           /* pp->leap = LEAP_NOWARNING; */
321 
322           /*
323            * Process the new sample in the median filter and determine the
324            * timecode timestamp.
325            */
326 
327           if (!refclock_process(pp))
328                     refclock_report(peer, CEVNT_BADTIME);
329           pp->lastref = pp->lastrec;
330           refclock_receive(peer);
331           return;
332 }
333 
334 
335 #else
336 NONEMPTY_TRANSLATION_UNIT
337 #endif /* REFCLOCK */
338