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