xref: /netbsd-src/external/bsd/ntp/dist/ntpd/refclock_chronolog.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: refclock_chronolog.c,v 1.6 2024/08/18 20:47:18 christos Exp $	*/
2abb0f93cSkardel 
3abb0f93cSkardel /*
4abb0f93cSkardel  * refclock_chronolog - clock driver for Chronolog K-series WWVB receiver.
5abb0f93cSkardel  */
6abb0f93cSkardel 
7abb0f93cSkardel /*
8abb0f93cSkardel  * Must interpolate back to local time.  Very annoying.
9abb0f93cSkardel  */
10abb0f93cSkardel #define GET_LOCALTIME
11abb0f93cSkardel 
12abb0f93cSkardel #ifdef HAVE_CONFIG_H
13abb0f93cSkardel #include <config.h>
14abb0f93cSkardel #endif
15abb0f93cSkardel 
16abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_CHRONOLOG)
17abb0f93cSkardel 
18abb0f93cSkardel #include "ntpd.h"
19abb0f93cSkardel #include "ntp_io.h"
20abb0f93cSkardel #include "ntp_refclock.h"
21abb0f93cSkardel #include "ntp_calendar.h"
22abb0f93cSkardel #include "ntp_stdlib.h"
23abb0f93cSkardel 
24abb0f93cSkardel #include <stdio.h>
25abb0f93cSkardel #include <ctype.h>
26abb0f93cSkardel 
27abb0f93cSkardel /*
28abb0f93cSkardel  * This driver supports the Chronolog K-series WWVB receiver.
29abb0f93cSkardel  *
30abb0f93cSkardel  * Input format:
31abb0f93cSkardel  *
32abb0f93cSkardel  *	Y YY/MM/DD<cr><lf>
33abb0f93cSkardel  *      Z hh:mm:ss<cr><lf>
34abb0f93cSkardel  *
35abb0f93cSkardel  * YY/MM/DD -- what you'd expect.  This arrives a few seconds before the
36abb0f93cSkardel  * timestamp.
37abb0f93cSkardel  * hh:mm:ss -- what you'd expect.  We take time on the <cr>.
38abb0f93cSkardel  *
39abb0f93cSkardel  * Our Chronolog writes time out at 2400 bps 8/N/1, but it can be configured
40abb0f93cSkardel  * otherwise.  The clock seems to appear every 60 seconds, which doesn't make
41abb0f93cSkardel  * for good statistics collection.
42abb0f93cSkardel  *
43abb0f93cSkardel  * The original source of this module was the WWVB module.
44abb0f93cSkardel  */
45abb0f93cSkardel 
46abb0f93cSkardel /*
47abb0f93cSkardel  * Interface definitions
48abb0f93cSkardel  */
49abb0f93cSkardel #define	DEVICE		"/dev/chronolog%d" /* device name and unit */
50abb0f93cSkardel #define	SPEED232	B2400	/* uart speed (2400 baud) */
51abb0f93cSkardel #define	PRECISION	(-13)	/* precision assumed (about 100 us) */
52abb0f93cSkardel #define	REFID		"chronolog"	/* reference ID */
53abb0f93cSkardel #define	DESCRIPTION	"Chrono-log K" /* WRU */
54abb0f93cSkardel 
55abb0f93cSkardel #define MONLIN		15	/* number of monitoring lines */
56abb0f93cSkardel 
57abb0f93cSkardel /*
58abb0f93cSkardel  * Chrono-log unit control structure
59abb0f93cSkardel  */
60abb0f93cSkardel struct chronolog_unit {
61abb0f93cSkardel 	u_char	tcswitch;	/* timecode switch */
62abb0f93cSkardel 	l_fp	laststamp;	/* last receive timestamp */
63abb0f93cSkardel 	u_char	lasthour;	/* last hour (for monitor) */
64abb0f93cSkardel 	int   	year;	        /* Y2K-adjusted year */
65abb0f93cSkardel 	int   	day;	        /* day-of-month */
66abb0f93cSkardel         int   	month;	        /* month-of-year */
67abb0f93cSkardel };
68abb0f93cSkardel 
69abb0f93cSkardel /*
70abb0f93cSkardel  * Function prototypes
71abb0f93cSkardel  */
72abb0f93cSkardel static	int	chronolog_start		(int, struct peer *);
73abb0f93cSkardel static	void	chronolog_shutdown	(int, struct peer *);
74abb0f93cSkardel static	void	chronolog_receive	(struct recvbuf *);
75abb0f93cSkardel static	void	chronolog_poll		(int, struct peer *);
76abb0f93cSkardel 
77abb0f93cSkardel /*
78abb0f93cSkardel  * Transfer vector
79abb0f93cSkardel  */
80abb0f93cSkardel struct	refclock refclock_chronolog = {
81abb0f93cSkardel 	chronolog_start,	/* start up driver */
82abb0f93cSkardel 	chronolog_shutdown,	/* shut down driver */
83abb0f93cSkardel 	chronolog_poll,		/* poll the driver -- a nice fabrication */
84abb0f93cSkardel 	noentry,		/* not used */
85abb0f93cSkardel 	noentry,		/* not used */
86abb0f93cSkardel 	noentry,		/* not used */
87abb0f93cSkardel 	NOFLAGS			/* not used */
88abb0f93cSkardel };
89abb0f93cSkardel 
90abb0f93cSkardel 
91abb0f93cSkardel /*
92abb0f93cSkardel  * chronolog_start - open the devices and initialize data for processing
93abb0f93cSkardel  */
94abb0f93cSkardel static int
95abb0f93cSkardel chronolog_start(
96abb0f93cSkardel 	int unit,
97abb0f93cSkardel 	struct peer *peer
98abb0f93cSkardel 	)
99abb0f93cSkardel {
100abb0f93cSkardel 	register struct chronolog_unit *up;
101abb0f93cSkardel 	struct refclockproc *pp;
102abb0f93cSkardel 	int fd;
103abb0f93cSkardel 	char device[20];
104abb0f93cSkardel 
105abb0f93cSkardel 	/*
106abb0f93cSkardel 	 * Open serial port. Don't bother with CLK line discipline, since
107abb0f93cSkardel 	 * it's not available.
108abb0f93cSkardel 	 */
109f003fb54Skardel 	snprintf(device, sizeof(device), DEVICE, unit);
110abb0f93cSkardel #ifdef DEBUG
111abb0f93cSkardel 	if (debug)
112abb0f93cSkardel 		printf ("starting Chronolog with device %s\n",device);
113abb0f93cSkardel #endif
114*eabc0478Schristos 	fd = refclock_open(&peer->srcadr, device, SPEED232, 0);
1158585484eSchristos 	if (fd <= 0)
116abb0f93cSkardel 		return (0);
117abb0f93cSkardel 
118abb0f93cSkardel 	/*
119abb0f93cSkardel 	 * Allocate and initialize unit structure
120abb0f93cSkardel 	 */
1218585484eSchristos 	up = emalloc_zero(sizeof(*up));
122abb0f93cSkardel 	pp = peer->procptr;
1238585484eSchristos 	pp->unitptr = up;
124abb0f93cSkardel 	pp->io.clock_recv = chronolog_receive;
1258585484eSchristos 	pp->io.srcclock = peer;
126abb0f93cSkardel 	pp->io.datalen = 0;
127abb0f93cSkardel 	pp->io.fd = fd;
128abb0f93cSkardel 	if (!io_addclock(&pp->io)) {
129f003fb54Skardel 		close(fd);
130f003fb54Skardel 		pp->io.fd = -1;
131abb0f93cSkardel 		free(up);
132f003fb54Skardel 		pp->unitptr = NULL;
133abb0f93cSkardel 		return (0);
134abb0f93cSkardel 	}
135abb0f93cSkardel 
136abb0f93cSkardel 	/*
137abb0f93cSkardel 	 * Initialize miscellaneous variables
138abb0f93cSkardel 	 */
139abb0f93cSkardel 	peer->precision = PRECISION;
140abb0f93cSkardel 	pp->clockdesc = DESCRIPTION;
141abb0f93cSkardel 	memcpy((char *)&pp->refid, REFID, 4);
142abb0f93cSkardel 	return (1);
143abb0f93cSkardel }
144abb0f93cSkardel 
145abb0f93cSkardel 
146abb0f93cSkardel /*
147abb0f93cSkardel  * chronolog_shutdown - shut down the clock
148abb0f93cSkardel  */
149abb0f93cSkardel static void
150abb0f93cSkardel chronolog_shutdown(
151abb0f93cSkardel 	int unit,
152abb0f93cSkardel 	struct peer *peer
153abb0f93cSkardel 	)
154abb0f93cSkardel {
155abb0f93cSkardel 	register struct chronolog_unit *up;
156abb0f93cSkardel 	struct refclockproc *pp;
157abb0f93cSkardel 
158abb0f93cSkardel 	pp = peer->procptr;
1598585484eSchristos 	up = pp->unitptr;
160f003fb54Skardel 	if (-1 != pp->io.fd)
161abb0f93cSkardel 		io_closeclock(&pp->io);
162f003fb54Skardel 	if (NULL != up)
163abb0f93cSkardel 		free(up);
164abb0f93cSkardel }
165abb0f93cSkardel 
166abb0f93cSkardel 
167abb0f93cSkardel /*
168abb0f93cSkardel  * chronolog_receive - receive data from the serial interface
169abb0f93cSkardel  */
170abb0f93cSkardel static void
171abb0f93cSkardel chronolog_receive(
172abb0f93cSkardel 	struct recvbuf *rbufp
173abb0f93cSkardel 	)
174abb0f93cSkardel {
175abb0f93cSkardel 	struct chronolog_unit *up;
176abb0f93cSkardel 	struct refclockproc *pp;
177abb0f93cSkardel 	struct peer *peer;
178abb0f93cSkardel 
179abb0f93cSkardel 	l_fp	     trtmp;	/* arrival timestamp */
180abb0f93cSkardel 	int          hours;	/* hour-of-day */
181abb0f93cSkardel 	int	     minutes;	/* minutes-past-the-hour */
182abb0f93cSkardel 	int          seconds;	/* seconds */
183abb0f93cSkardel 	int	     temp;	/* int temp */
184abb0f93cSkardel 	int          got_good;	/* got a good time flag */
185abb0f93cSkardel 
186abb0f93cSkardel 	/*
187abb0f93cSkardel 	 * Initialize pointers and read the timecode and timestamp
188abb0f93cSkardel 	 */
1898585484eSchristos 	peer = rbufp->recv_peer;
190abb0f93cSkardel 	pp = peer->procptr;
1918585484eSchristos 	up = pp->unitptr;
192abb0f93cSkardel 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
193abb0f93cSkardel 
194abb0f93cSkardel 	if (temp == 0) {
195abb0f93cSkardel 		if (up->tcswitch == 0) {
196abb0f93cSkardel 			up->tcswitch = 1;
197abb0f93cSkardel 			up->laststamp = trtmp;
198abb0f93cSkardel 		} else
199abb0f93cSkardel 		    up->tcswitch = 0;
200abb0f93cSkardel 		return;
201abb0f93cSkardel 	}
202abb0f93cSkardel 	pp->lencode = temp;
203abb0f93cSkardel 	pp->lastrec = up->laststamp;
204abb0f93cSkardel 	up->laststamp = trtmp;
205abb0f93cSkardel 	up->tcswitch = 1;
206abb0f93cSkardel 
207abb0f93cSkardel #ifdef DEBUG
208abb0f93cSkardel 	if (debug)
209abb0f93cSkardel 		printf("chronolog: timecode %d %s\n", pp->lencode,
210abb0f93cSkardel 		    pp->a_lastcode);
211abb0f93cSkardel #endif
212abb0f93cSkardel 
213abb0f93cSkardel 	/*
214abb0f93cSkardel 	 * We get down to business. Check the timecode format and decode
215abb0f93cSkardel 	 * its contents. This code uses the first character to see whether
216abb0f93cSkardel 	 * we're looking at a date or a time.  We store data data across
217abb0f93cSkardel 	 * calls since it is transmitted a few seconds ahead of the
218abb0f93cSkardel 	 * timestamp.
219abb0f93cSkardel 	 */
220abb0f93cSkardel 	got_good=0;
221abb0f93cSkardel 	if (sscanf(pp->a_lastcode, "Y %d/%d/%d", &up->year,&up->month,&up->day))
222abb0f93cSkardel 	{
223abb0f93cSkardel 	    /*
224abb0f93cSkardel 	     * Y2K convert the 2-digit year
225abb0f93cSkardel 	     */
226abb0f93cSkardel 	    up->year = up->year >= 69 ? up->year : up->year + 100;
227abb0f93cSkardel 	    return;
228abb0f93cSkardel 	}
229abb0f93cSkardel 	if (sscanf(pp->a_lastcode,"Z %02d:%02d:%02d",
230abb0f93cSkardel 		   &hours,&minutes,&seconds) == 3)
231abb0f93cSkardel 	{
232abb0f93cSkardel #ifdef GET_LOCALTIME
233abb0f93cSkardel 	    struct tm  local;
234abb0f93cSkardel 	    struct tm *gmtp;
235abb0f93cSkardel 	    time_t     unixtime;
236abb0f93cSkardel 	    int        adjyear;
237abb0f93cSkardel 	    int        adjmon;
238abb0f93cSkardel 
239abb0f93cSkardel 	    /*
240abb0f93cSkardel 	     * Convert to GMT for sites that distribute localtime.  This
241abb0f93cSkardel              * means we have to do Y2K conversion on the 2-digit year;
242abb0f93cSkardel 	     * otherwise, we get the time wrong.
243abb0f93cSkardel 	     */
244abb0f93cSkardel 
245abb0f93cSkardel 	    memset(&local, 0, sizeof(local));
246abb0f93cSkardel 
247abb0f93cSkardel 	    local.tm_year  = up->year;
248abb0f93cSkardel 	    local.tm_mon   = up->month-1;
249abb0f93cSkardel 	    local.tm_mday  = up->day;
250abb0f93cSkardel 	    local.tm_hour  = hours;
251abb0f93cSkardel 	    local.tm_min   = minutes;
252abb0f93cSkardel 	    local.tm_sec   = seconds;
253abb0f93cSkardel 	    local.tm_isdst = -1;
254abb0f93cSkardel 
255abb0f93cSkardel 	    unixtime = mktime (&local);
256abb0f93cSkardel 	    if ((gmtp = gmtime (&unixtime)) == NULL)
257abb0f93cSkardel 	    {
258abb0f93cSkardel 		refclock_report (peer, CEVNT_FAULT);
259abb0f93cSkardel 		return;
260abb0f93cSkardel 	    }
261abb0f93cSkardel 	    adjyear = gmtp->tm_year+1900;
262abb0f93cSkardel 	    adjmon  = gmtp->tm_mon+1;
263abb0f93cSkardel 	    pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
264abb0f93cSkardel 	    pp->hour   = gmtp->tm_hour;
265abb0f93cSkardel 	    pp->minute = gmtp->tm_min;
266abb0f93cSkardel 	    pp->second = gmtp->tm_sec;
267abb0f93cSkardel #ifdef DEBUG
268abb0f93cSkardel 	    if (debug)
269abb0f93cSkardel 		printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
270abb0f93cSkardel 			adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
271abb0f93cSkardel 			pp->second);
272abb0f93cSkardel #endif
273abb0f93cSkardel 
274abb0f93cSkardel #else
275abb0f93cSkardel 	    /*
276abb0f93cSkardel 	     * For more rational sites distributing UTC
277abb0f93cSkardel 	     */
278abb0f93cSkardel 	    pp->day    = ymd2yd(year+1900,month,day);
279abb0f93cSkardel 	    pp->hour   = hours;
280abb0f93cSkardel 	    pp->minute = minutes;
281abb0f93cSkardel 	    pp->second = seconds;
282abb0f93cSkardel 
283abb0f93cSkardel #endif
284abb0f93cSkardel 	    got_good=1;
285abb0f93cSkardel 	}
286abb0f93cSkardel 
287abb0f93cSkardel 	if (!got_good)
288abb0f93cSkardel 	    return;
289abb0f93cSkardel 
290abb0f93cSkardel 
291abb0f93cSkardel 	/*
292abb0f93cSkardel 	 * Process the new sample in the median filter and determine the
293abb0f93cSkardel 	 * timecode timestamp.
294abb0f93cSkardel 	 */
295abb0f93cSkardel 	if (!refclock_process(pp)) {
296abb0f93cSkardel 		refclock_report(peer, CEVNT_BADTIME);
297abb0f93cSkardel 		return;
298abb0f93cSkardel 	}
299abb0f93cSkardel 	pp->lastref = pp->lastrec;
300abb0f93cSkardel 	refclock_receive(peer);
301abb0f93cSkardel 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
3028585484eSchristos 	up->lasthour = (u_char)pp->hour;
303abb0f93cSkardel }
304abb0f93cSkardel 
305abb0f93cSkardel 
306abb0f93cSkardel /*
307abb0f93cSkardel  * chronolog_poll - called by the transmit procedure
308abb0f93cSkardel  */
309abb0f93cSkardel static void
310abb0f93cSkardel chronolog_poll(
311abb0f93cSkardel 	int unit,
312abb0f93cSkardel 	struct peer *peer
313abb0f93cSkardel 	)
314abb0f93cSkardel {
315abb0f93cSkardel 	/*
316abb0f93cSkardel 	 * Time to poll the clock. The Chrono-log clock is supposed to
317abb0f93cSkardel 	 * respond to a 'T' by returning a timecode in the format(s)
318abb0f93cSkardel 	 * specified above.  Ours does (can?) not, but this seems to be
319abb0f93cSkardel 	 * an installation-specific problem.  This code is dyked out,
320abb0f93cSkardel 	 * but may be re-enabled if anyone ever finds a Chrono-log that
321abb0f93cSkardel 	 * actually listens to this command.
322abb0f93cSkardel 	 */
323abb0f93cSkardel #if 0
324abb0f93cSkardel 	register struct chronolog_unit *up;
325abb0f93cSkardel 	struct refclockproc *pp;
326abb0f93cSkardel 	char pollchar;
327abb0f93cSkardel 
328abb0f93cSkardel 	pp = peer->procptr;
3298585484eSchristos 	up = pp->unitptr;
330abb0f93cSkardel 	if (peer->burst == 0 && peer->reach == 0)
331abb0f93cSkardel 		refclock_report(peer, CEVNT_TIMEOUT);
332abb0f93cSkardel 	if (up->linect > 0)
333abb0f93cSkardel 		pollchar = 'R';
334abb0f93cSkardel 	else
335abb0f93cSkardel 		pollchar = 'T';
336abb0f93cSkardel 	if (write(pp->io.fd, &pollchar, 1) != 1)
337abb0f93cSkardel 		refclock_report(peer, CEVNT_FAULT);
338abb0f93cSkardel 	else
339abb0f93cSkardel 		pp->polls++;
340abb0f93cSkardel #endif
341abb0f93cSkardel }
342abb0f93cSkardel 
343abb0f93cSkardel #else
344*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT
345abb0f93cSkardel #endif /* REFCLOCK */
346