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