xref: /freebsd/contrib/ntp/ntpd/refclock_fg.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
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 	(int);
37 static	int 	fg_start 	(int, struct peer *);
38 static	void	fg_shutdown	(int, struct peer *);
39 static	void	fg_poll		(int, struct peer *);
40 static	void	fg_receive	(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  * fg_start - open the device and initialize data for processing
90  */
91 static int
92 fg_start(
93 	int unit,
94 	struct peer *peer
95 	)
96 {
97 	struct refclockproc *pp;
98 	struct fgunit *up;
99 	int fd;
100 	char device[20];
101 
102 
103 	/*
104 	 * Open device file for reading.
105 	 */
106 	snprintf(device, sizeof(device), DEVICE, unit);
107 
108 	DPRINTF(1, ("starting FG with device %s\n",device));
109 
110 	fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK);
111 	if (fd <= 0)
112 		return (0);
113 
114 	/*
115 	 * Allocate and initialize unit structure
116 	 */
117 
118 	up = emalloc(sizeof(struct fgunit));
119 	memset(up, 0, sizeof(struct fgunit));
120 	pp = peer->procptr;
121 	pp->unitptr = up;
122 	pp->io.clock_recv = fg_receive;
123 	pp->io.srcclock = peer;
124 	pp->io.datalen = 0;
125 	pp->io.fd = fd;
126  	if (!io_addclock(&pp->io)) {
127 		close(fd);
128 		pp->io.fd = -1;
129 		return 0;
130 	}
131 
132 
133 	/*
134 	 * Initialize miscellaneous variables
135 	 */
136 	peer->precision = PRECISION;
137 	pp->clockdesc = DESCRIPTION;
138 	memcpy(&pp->refid, REFID, 3);
139 	up->pollnum = 0;
140 
141 	/*
142 	 * Setup dating station to use GPS receiver.
143 	 * GPS receiver should work before this operation.
144 	 */
145 	if(!fg_init(pp->io.fd))
146 		refclock_report(peer, CEVNT_FAULT);
147 
148 	return (1);
149 }
150 
151 
152 /*
153  * fg_shutdown - shut down the clock
154  */
155 static void
156 fg_shutdown(
157 	int unit,
158 	struct peer *peer
159 	)
160 {
161 	struct refclockproc *pp;
162 	struct fgunit *up;
163 
164 	pp = peer->procptr;
165 	up = pp->unitptr;
166 	if (pp->io.fd != -1)
167 		io_closeclock(&pp->io);
168 	if (up != NULL)
169 		free(up);
170 }
171 
172 
173 /*
174  * fg_poll - called by the transmit procedure
175  */
176 static void
177 fg_poll(
178 	int unit,
179 	struct peer *peer
180 	)
181 {
182 	struct refclockproc *pp;
183 
184 	pp = peer->procptr;
185 
186 	/*
187 	 * Time to poll the clock. The FG clock responds to a
188 	 * "<DLE>D<DLE><CR>" by returning a timecode in the format specified
189 	 * above. If nothing is heard from the clock for two polls,
190 	 * declare a timeout and keep going.
191 	 */
192 
193 	if (write(pp->io.fd, fgdate, LENFG) != LENFG)
194 		refclock_report(peer, CEVNT_FAULT);
195 	else
196 		pp->polls++;
197 
198 	/*
199 	if (pp->coderecv == pp->codeproc) {
200 		refclock_report(peer, CEVNT_TIMEOUT);
201 		return;
202 	}
203 	*/
204 
205 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
206 
207 	return;
208 
209 }
210 
211 /*
212  * fg_receive - receive data from the serial interface
213  */
214 static void
215 fg_receive(
216 	struct recvbuf *rbufp
217 	)
218 {
219 	struct refclockproc *pp;
220 	struct fgunit *up;
221 	struct peer *peer;
222 	char *bpt;
223 
224 	/*
225 	 * Initialize pointers and read the timecode and timestamp
226 	 * We can't use gtlin function because we need bynary data in buf */
227 
228 	peer = rbufp->recv_peer;
229 	pp = peer->procptr;
230 	up = pp->unitptr;
231 
232 	/*
233 	 * Below hug to implement receiving of status information
234 	 */
235 	if(!up->pollnum) {
236 		up->pollnum++;
237 		return;
238 	}
239 
240 
241 	if (rbufp->recv_length < (LENFG - 2)) {
242 		refclock_report(peer, CEVNT_BADREPLY);
243 		return; /* The reply is invalid discard it. */
244 	}
245 
246 	/* Below I trying to find a correct reply in buffer.
247 	 * Sometime GPS reply located in the beginning of buffer,
248 	 * sometime you can find it with some offset.
249 	 */
250 
251 	bpt = (char *)rbufp->recv_buffer;
252 	while (*bpt != '\x10')
253 		bpt++;
254 
255 #define BP2(x) ( bpt[x] & 15 )
256 #define BP1(x) (( bpt[x] & 240 ) >> 4)
257 
258 	pp->year = BP1(2) * 10 + BP2(2);
259 
260 	if (pp->year == 94) {
261 		refclock_report(peer, CEVNT_BADREPLY);
262 		if (!fg_init(pp->io.fd))
263 			refclock_report(peer, CEVNT_FAULT);
264 		return;
265 		 /* GPS is just powered up. The date is invalid -
266 		 discarding it. Initilize GPS one more time */
267 		/* Sorry - this driver will broken in 2094 ;) */
268 	}
269 
270 	if (pp->year < 99)
271 		pp->year += 100;
272 
273 	pp->year +=  1900;
274 	pp->day = 100 * BP2(3) + 10 * BP1(4) + BP2(4);
275 
276 /*
277    After Jan, 10 2000 Forum Graphic GPS receiver had a very strange
278    benahour. It doubles day number for an hours in replys after 10:10:10 UTC
279    and doubles min every hour at HH:10:ss for a minute.
280    Hope it is a problem of my unit only and not a Y2K problem of FG GPS.
281    Below small code to avoid such situation.
282 */
283 	if (up->y2kwarn > 10)
284 		pp->hour = BP1(6)*10 + BP2(6);
285 	else
286 		pp->hour = BP1(5)*10 + BP2(5);
287 
288 	if ((up->y2kwarn > 10) && (pp->hour == 10)) {
289 		pp->minute = BP1(7)*10 + BP2(7);
290 		pp->second = BP1(8)*10 + BP2(8);
291 		pp->nsec = (BP1(9)*10 + BP2(9)) * 1000000;
292 		pp->nsec += BP1(10) * 1000;
293 	} else {
294 		pp->hour = BP1(5)*10 + BP2(5);
295 		pp->minute = BP1(6)*10 + BP2(6);
296 		pp->second = BP1(7)*10 + BP2(7);
297 		pp->nsec = (BP1(8)*10 + BP2(8)) * 1000000;
298 		pp->nsec += BP1(9) * 1000;
299 	}
300 
301 	if ((pp->hour == 10) && (pp->minute == 10)) {
302 		up->y2kwarn++;
303 	}
304 
305 	snprintf(pp->a_lastcode, sizeof(pp->a_lastcode),
306 		 "%d %d %d %d %d", pp->year, pp->day, pp->hour,
307 		 pp->minute, pp->second);
308 	pp->lencode = strlen(pp->a_lastcode);
309 	/*get_systime(&pp->lastrec);*/
310 
311 #ifdef DEBUG
312 	if (debug)
313 		printf("fg: time is %04d/%03d %02d:%02d:%02d UTC\n",
314 		       pp->year, pp->day, pp->hour, pp->minute, pp->second);
315 #endif
316 	pp->disp =  (10e-6);
317 	pp->lastrec = rbufp->recv_time; /* Is it better than get_systime()? */
318 	/* pp->leap = LEAP_NOWARNING; */
319 
320 	/*
321 	 * Process the new sample in the median filter and determine the
322 	 * timecode timestamp.
323 	 */
324 
325 	if (!refclock_process(pp))
326 		refclock_report(peer, CEVNT_BADTIME);
327 	pp->lastref = pp->lastrec;
328 	refclock_receive(peer);
329 	return;
330 }
331 
332 
333 #else
334 NONEMPTY_TRANSLATION_UNIT
335 #endif /* REFCLOCK */
336