xref: /netbsd-src/external/bsd/ntp/dist/ntpd/refclock_dumbclock.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: refclock_dumbclock.c,v 1.6 2024/08/18 20:47:18 christos Exp $	*/
2abb0f93cSkardel 
3abb0f93cSkardel /*
4abb0f93cSkardel  * refclock_dumbclock - clock driver for a unknown time distribution system
5abb0f93cSkardel  * that only provides hh:mm:ss (in local time, yet!).
6abb0f93cSkardel  */
7abb0f93cSkardel 
8abb0f93cSkardel /*
9abb0f93cSkardel  * Must interpolate back to local time.  Very annoying.
10abb0f93cSkardel  */
11abb0f93cSkardel #define GET_LOCALTIME
12abb0f93cSkardel 
13abb0f93cSkardel #ifdef HAVE_CONFIG_H
14abb0f93cSkardel #include <config.h>
15abb0f93cSkardel #endif
16abb0f93cSkardel 
17abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK)
18abb0f93cSkardel 
19abb0f93cSkardel #include "ntpd.h"
20abb0f93cSkardel #include "ntp_io.h"
21abb0f93cSkardel #include "ntp_refclock.h"
22abb0f93cSkardel #include "ntp_calendar.h"
23abb0f93cSkardel #include "ntp_stdlib.h"
24abb0f93cSkardel 
25abb0f93cSkardel #include <stdio.h>
26abb0f93cSkardel #include <ctype.h>
27abb0f93cSkardel 
28abb0f93cSkardel /*
29abb0f93cSkardel  * This driver supports a generic dumb clock that only outputs hh:mm:ss,
30abb0f93cSkardel  * in local time, no less.
31abb0f93cSkardel  *
32abb0f93cSkardel  * Input format:
33abb0f93cSkardel  *
34abb0f93cSkardel  *	hh:mm:ss   <cr>
35abb0f93cSkardel  *
36abb0f93cSkardel  * hh:mm:ss -- what you'd expect, with a 24 hour clock.  (Heck, that's the only
37abb0f93cSkardel  * way it could get stupider.)  We take time on the <cr>.
38abb0f93cSkardel  *
39abb0f93cSkardel  * The original source of this module was the WWVB module.
40abb0f93cSkardel  */
41abb0f93cSkardel 
42abb0f93cSkardel /*
43abb0f93cSkardel  * Interface definitions
44abb0f93cSkardel  */
45abb0f93cSkardel #define	DEVICE		"/dev/dumbclock%d" /* device name and unit */
46abb0f93cSkardel #define	SPEED232	B9600	/* uart speed (9600 baud) */
47abb0f93cSkardel #define	PRECISION	(-13)	/* precision assumed (about 100 us) */
48abb0f93cSkardel #define	REFID		"dumbclock"	/* reference ID */
49abb0f93cSkardel #define	DESCRIPTION	"Dumb clock" /* WRU */
50abb0f93cSkardel 
51abb0f93cSkardel 
52abb0f93cSkardel /*
53abb0f93cSkardel  * Insanity check.  Since the time is local, we need to make sure that during midnight
54abb0f93cSkardel  * transitions, we can convert back to Unix time.  If the conversion results in some number
55abb0f93cSkardel  * worse than this number of seconds away, assume the next day and retry.
56abb0f93cSkardel  */
57abb0f93cSkardel #define INSANE_SECONDS 3600
58abb0f93cSkardel 
59abb0f93cSkardel /*
60abb0f93cSkardel  * Dumb clock control structure
61abb0f93cSkardel  */
62abb0f93cSkardel struct dumbclock_unit {
63abb0f93cSkardel 	u_char	  tcswitch;	/* timecode switch */
64abb0f93cSkardel 	l_fp	  laststamp;	/* last receive timestamp */
65abb0f93cSkardel 	u_char	  lasthour;	/* last hour (for monitor) */
66abb0f93cSkardel 	u_char	  linect;	/* count ignored lines (for monitor */
67abb0f93cSkardel 	struct tm ymd;		/* struct tm for y/m/d only */
68abb0f93cSkardel };
69abb0f93cSkardel 
70abb0f93cSkardel /*
71abb0f93cSkardel  * Function prototypes
72abb0f93cSkardel  */
73abb0f93cSkardel static	int	dumbclock_start		(int, struct peer *);
74abb0f93cSkardel static	void	dumbclock_shutdown	(int, struct peer *);
75abb0f93cSkardel static	void	dumbclock_receive	(struct recvbuf *);
76abb0f93cSkardel #if 0
77abb0f93cSkardel static	void	dumbclock_poll		(int, struct peer *);
78abb0f93cSkardel #endif
79abb0f93cSkardel 
80abb0f93cSkardel /*
81abb0f93cSkardel  * Transfer vector
82abb0f93cSkardel  */
83abb0f93cSkardel struct	refclock refclock_dumbclock = {
84abb0f93cSkardel 	dumbclock_start,		     /* start up driver */
85abb0f93cSkardel 	dumbclock_shutdown,		     /* shut down driver */
86abb0f93cSkardel 	noentry,			     /* poll the driver -- a nice fabrication */
87abb0f93cSkardel 	noentry,			     /* not used */
88abb0f93cSkardel 	noentry,			     /* not used */
89abb0f93cSkardel 	noentry,			     /* not used */
90abb0f93cSkardel 	NOFLAGS				     /* not used */
91abb0f93cSkardel };
92abb0f93cSkardel 
93abb0f93cSkardel 
94abb0f93cSkardel /*
95abb0f93cSkardel  * dumbclock_start - open the devices and initialize data for processing
96abb0f93cSkardel  */
97abb0f93cSkardel static int
98abb0f93cSkardel dumbclock_start(
99abb0f93cSkardel 	int unit,
100abb0f93cSkardel 	struct peer *peer
101abb0f93cSkardel 	)
102abb0f93cSkardel {
103abb0f93cSkardel 	register struct dumbclock_unit *up;
104abb0f93cSkardel 	struct refclockproc *pp;
105abb0f93cSkardel 	int fd;
106abb0f93cSkardel 	char device[20];
107abb0f93cSkardel 	struct tm *tm_time_p;
108abb0f93cSkardel 	time_t     now;
109abb0f93cSkardel 
110abb0f93cSkardel 	/*
111abb0f93cSkardel 	 * Open serial port. Don't bother with CLK line discipline, since
112abb0f93cSkardel 	 * it's not available.
113abb0f93cSkardel 	 */
114f003fb54Skardel 	snprintf(device, sizeof(device), DEVICE, unit);
115abb0f93cSkardel #ifdef DEBUG
116abb0f93cSkardel 	if (debug)
117abb0f93cSkardel 		printf ("starting Dumbclock with device %s\n",device);
118abb0f93cSkardel #endif
119*eabc0478Schristos 	fd = refclock_open(&peer->srcadr, device, SPEED232, 0);
1208585484eSchristos 	if (fd <= 0)
121abb0f93cSkardel 		return (0);
122abb0f93cSkardel 
123abb0f93cSkardel 	/*
124abb0f93cSkardel 	 * Allocate and initialize unit structure
125abb0f93cSkardel 	 */
1268585484eSchristos 	up = emalloc_zero(sizeof(*up));
127abb0f93cSkardel 	pp = peer->procptr;
1288585484eSchristos 	pp->unitptr = up;
129abb0f93cSkardel 	pp->io.clock_recv = dumbclock_receive;
1308585484eSchristos 	pp->io.srcclock = peer;
131abb0f93cSkardel 	pp->io.datalen = 0;
132abb0f93cSkardel 	pp->io.fd = fd;
133abb0f93cSkardel 	if (!io_addclock(&pp->io)) {
134f003fb54Skardel 		close(fd);
135f003fb54Skardel 		pp->io.fd = -1;
136abb0f93cSkardel 		free(up);
137f003fb54Skardel 		pp->unitptr = NULL;
138abb0f93cSkardel 		return (0);
139abb0f93cSkardel 	}
140abb0f93cSkardel 
141abb0f93cSkardel 
142abb0f93cSkardel 	time(&now);
143abb0f93cSkardel #ifdef GET_LOCALTIME
144abb0f93cSkardel 	tm_time_p = localtime(&now);
145abb0f93cSkardel #else
146abb0f93cSkardel 	tm_time_p = gmtime(&now);
147abb0f93cSkardel #endif
148abb0f93cSkardel 	if (tm_time_p)
149abb0f93cSkardel 		up->ymd = *tm_time_p;
150abb0f93cSkardel 	else
151abb0f93cSkardel 		return 0;
152abb0f93cSkardel 
153abb0f93cSkardel 	/*
154abb0f93cSkardel 	 * Initialize miscellaneous variables
155abb0f93cSkardel 	 */
156abb0f93cSkardel 	peer->precision = PRECISION;
157abb0f93cSkardel 	pp->clockdesc = DESCRIPTION;
158abb0f93cSkardel 	memcpy((char *)&pp->refid, REFID, 4);
159abb0f93cSkardel 	return (1);
160abb0f93cSkardel }
161abb0f93cSkardel 
162abb0f93cSkardel 
163abb0f93cSkardel /*
164abb0f93cSkardel  * dumbclock_shutdown - shut down the clock
165abb0f93cSkardel  */
166abb0f93cSkardel static void
167abb0f93cSkardel dumbclock_shutdown(
168abb0f93cSkardel 	int unit,
169abb0f93cSkardel 	struct peer *peer
170abb0f93cSkardel 	)
171abb0f93cSkardel {
172abb0f93cSkardel 	register struct dumbclock_unit *up;
173abb0f93cSkardel 	struct refclockproc *pp;
174abb0f93cSkardel 
175abb0f93cSkardel 	pp = peer->procptr;
1768585484eSchristos 	up = pp->unitptr;
177f003fb54Skardel 	if (-1 != pp->io.fd)
178abb0f93cSkardel 		io_closeclock(&pp->io);
179f003fb54Skardel 	if (NULL != up)
180abb0f93cSkardel 		free(up);
181abb0f93cSkardel }
182abb0f93cSkardel 
183abb0f93cSkardel 
184abb0f93cSkardel /*
185abb0f93cSkardel  * dumbclock_receive - receive data from the serial interface
186abb0f93cSkardel  */
187abb0f93cSkardel static void
188abb0f93cSkardel dumbclock_receive(
189abb0f93cSkardel 	struct recvbuf *rbufp
190abb0f93cSkardel 	)
191abb0f93cSkardel {
192abb0f93cSkardel 	struct dumbclock_unit *up;
193abb0f93cSkardel 	struct refclockproc *pp;
194abb0f93cSkardel 	struct peer *peer;
195abb0f93cSkardel 
196abb0f93cSkardel 	l_fp	trtmp;		/* arrival timestamp */
197abb0f93cSkardel 	int	hours;		/* hour-of-day */
198abb0f93cSkardel 	int	minutes;	/* minutes-past-the-hour */
199abb0f93cSkardel 	int	seconds;	/* seconds */
200abb0f93cSkardel 	int	temp;		/* int temp */
201abb0f93cSkardel 	int	got_good;	/* got a good time flag */
202abb0f93cSkardel 
203abb0f93cSkardel 	/*
204abb0f93cSkardel 	 * Initialize pointers and read the timecode and timestamp
205abb0f93cSkardel 	 */
2068585484eSchristos 	peer = rbufp->recv_peer;
207abb0f93cSkardel 	pp = peer->procptr;
2088585484eSchristos 	up = pp->unitptr;
209abb0f93cSkardel 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
210abb0f93cSkardel 
211abb0f93cSkardel 	if (temp == 0) {
212abb0f93cSkardel 		if (up->tcswitch == 0) {
213abb0f93cSkardel 			up->tcswitch = 1;
214abb0f93cSkardel 			up->laststamp = trtmp;
215abb0f93cSkardel 		} else
216abb0f93cSkardel 			up->tcswitch = 0;
217abb0f93cSkardel 		return;
218abb0f93cSkardel 	}
219abb0f93cSkardel 	pp->lencode = (u_short)temp;
220abb0f93cSkardel 	pp->lastrec = up->laststamp;
221abb0f93cSkardel 	up->laststamp = trtmp;
222abb0f93cSkardel 	up->tcswitch = 1;
223abb0f93cSkardel 
224abb0f93cSkardel #ifdef DEBUG
225abb0f93cSkardel 	if (debug)
226abb0f93cSkardel 		printf("dumbclock: timecode %d %s\n",
227abb0f93cSkardel 		       pp->lencode, pp->a_lastcode);
228abb0f93cSkardel #endif
229abb0f93cSkardel 
230abb0f93cSkardel 	/*
231abb0f93cSkardel 	 * We get down to business. Check the timecode format...
232abb0f93cSkardel 	 */
233abb0f93cSkardel 	got_good=0;
234abb0f93cSkardel 	if (sscanf(pp->a_lastcode,"%02d:%02d:%02d",
235abb0f93cSkardel 		   &hours,&minutes,&seconds) == 3)
236abb0f93cSkardel 	{
237abb0f93cSkardel 	    struct tm *gmtp;
238abb0f93cSkardel 	    struct tm *lt_p;
239abb0f93cSkardel 	    time_t     asserted_time;	     /* the SPM time based on the composite time+date */
240abb0f93cSkardel 	    struct tm  asserted_tm;	     /* the struct tm of the same */
241abb0f93cSkardel 	    int        adjyear;
242abb0f93cSkardel 	    int        adjmon;
243abb0f93cSkardel 	    time_t     reality_delta;
244abb0f93cSkardel 	    time_t     now;
245abb0f93cSkardel 
246abb0f93cSkardel 
247abb0f93cSkardel 	    /*
248abb0f93cSkardel 	     * Convert to GMT for sites that distribute localtime.  This
249abb0f93cSkardel 	     * means we have to figure out what day it is.  Easier said
250abb0f93cSkardel 	     * than done...
251abb0f93cSkardel 	     */
252abb0f93cSkardel 
253abb0f93cSkardel 	    memset(&asserted_tm, 0, sizeof(asserted_tm));
254abb0f93cSkardel 
255abb0f93cSkardel 	    asserted_tm.tm_year  = up->ymd.tm_year;
256abb0f93cSkardel 	    asserted_tm.tm_mon   = up->ymd.tm_mon;
257abb0f93cSkardel 	    asserted_tm.tm_mday  = up->ymd.tm_mday;
258abb0f93cSkardel 	    asserted_tm.tm_hour  = hours;
259abb0f93cSkardel 	    asserted_tm.tm_min   = minutes;
260abb0f93cSkardel 	    asserted_tm.tm_sec   = seconds;
261abb0f93cSkardel 	    asserted_tm.tm_isdst = -1;
262abb0f93cSkardel 
263abb0f93cSkardel #ifdef GET_LOCALTIME
264abb0f93cSkardel 	    asserted_time = mktime (&asserted_tm);
265abb0f93cSkardel 	    time(&now);
266abb0f93cSkardel #else
267abb0f93cSkardel #include "GMT unsupported for dumbclock!"
268abb0f93cSkardel #endif
269abb0f93cSkardel 	    reality_delta = asserted_time - now;
270abb0f93cSkardel 
271abb0f93cSkardel 	    /*
272abb0f93cSkardel 	     * We assume that if the time is grossly wrong, it's because we got the
273abb0f93cSkardel 	     * year/month/day wrong.
274abb0f93cSkardel 	     */
275abb0f93cSkardel 	    if (reality_delta > INSANE_SECONDS)
276abb0f93cSkardel 	    {
277abb0f93cSkardel 		asserted_time -= SECSPERDAY; /* local clock behind real time */
278abb0f93cSkardel 	    }
279abb0f93cSkardel 	    else if (-reality_delta > INSANE_SECONDS)
280abb0f93cSkardel 	    {
281abb0f93cSkardel 		asserted_time += SECSPERDAY; /* local clock ahead of real time */
282abb0f93cSkardel 	    }
283abb0f93cSkardel 	    lt_p = localtime(&asserted_time);
284abb0f93cSkardel 	    if (lt_p)
285abb0f93cSkardel 	    {
286abb0f93cSkardel 		up->ymd = *lt_p;
287abb0f93cSkardel 	    }
288abb0f93cSkardel 	    else
289abb0f93cSkardel 	    {
290abb0f93cSkardel 		refclock_report (peer, CEVNT_FAULT);
291abb0f93cSkardel 		return;
292abb0f93cSkardel 	    }
293abb0f93cSkardel 
294abb0f93cSkardel 	    if ((gmtp = gmtime (&asserted_time)) == NULL)
295abb0f93cSkardel 	    {
296abb0f93cSkardel 		refclock_report (peer, CEVNT_FAULT);
297abb0f93cSkardel 		return;
298abb0f93cSkardel 	    }
299abb0f93cSkardel 	    adjyear = gmtp->tm_year+1900;
300abb0f93cSkardel 	    adjmon  = gmtp->tm_mon+1;
301abb0f93cSkardel 	    pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
302abb0f93cSkardel 	    pp->hour   = gmtp->tm_hour;
303abb0f93cSkardel 	    pp->minute = gmtp->tm_min;
304abb0f93cSkardel 	    pp->second = gmtp->tm_sec;
305abb0f93cSkardel #ifdef DEBUG
306abb0f93cSkardel 	    if (debug)
307abb0f93cSkardel 		printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
308abb0f93cSkardel 			adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
309abb0f93cSkardel 			pp->second);
310abb0f93cSkardel #endif
311abb0f93cSkardel 
312abb0f93cSkardel 	    got_good=1;
313abb0f93cSkardel 	}
314abb0f93cSkardel 
315abb0f93cSkardel 	if (!got_good)
316abb0f93cSkardel 	{
317abb0f93cSkardel 	    if (up->linect > 0)
318abb0f93cSkardel 	    	up->linect--;
319abb0f93cSkardel 	    else
320abb0f93cSkardel 	    	refclock_report(peer, CEVNT_BADREPLY);
321abb0f93cSkardel 	    return;
322abb0f93cSkardel 	}
323abb0f93cSkardel 
324abb0f93cSkardel 	/*
325abb0f93cSkardel 	 * Process the new sample in the median filter and determine the
326abb0f93cSkardel 	 * timecode timestamp.
327abb0f93cSkardel 	 */
328abb0f93cSkardel 	if (!refclock_process(pp)) {
329abb0f93cSkardel 		refclock_report(peer, CEVNT_BADTIME);
330abb0f93cSkardel 		return;
331abb0f93cSkardel 	}
332abb0f93cSkardel 	pp->lastref = pp->lastrec;
333abb0f93cSkardel 	refclock_receive(peer);
334abb0f93cSkardel 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
335abb0f93cSkardel 	up->lasthour = (u_char)pp->hour;
336abb0f93cSkardel }
337abb0f93cSkardel 
338abb0f93cSkardel #if 0
339abb0f93cSkardel /*
340abb0f93cSkardel  * dumbclock_poll - called by the transmit procedure
341abb0f93cSkardel  */
342abb0f93cSkardel static void
343abb0f93cSkardel dumbclock_poll(
344abb0f93cSkardel 	int unit,
345abb0f93cSkardel 	struct peer *peer
346abb0f93cSkardel 	)
347abb0f93cSkardel {
348abb0f93cSkardel 	register struct dumbclock_unit *up;
349abb0f93cSkardel 	struct refclockproc *pp;
350abb0f93cSkardel 	char pollchar;
351abb0f93cSkardel 
352abb0f93cSkardel 	/*
353abb0f93cSkardel 	 * Time to poll the clock. The Chrono-log clock is supposed to
354abb0f93cSkardel 	 * respond to a 'T' by returning a timecode in the format(s)
355abb0f93cSkardel 	 * specified above.  Ours does (can?) not, but this seems to be
356abb0f93cSkardel 	 * an installation-specific problem.  This code is dyked out,
357abb0f93cSkardel 	 * but may be re-enabled if anyone ever finds a Chrono-log that
358abb0f93cSkardel 	 * actually listens to this command.
359abb0f93cSkardel 	 */
360abb0f93cSkardel #if 0
361abb0f93cSkardel 	pp = peer->procptr;
3628585484eSchristos 	up = pp->unitptr;
363abb0f93cSkardel 	if (peer->reach == 0)
364abb0f93cSkardel 		refclock_report(peer, CEVNT_TIMEOUT);
365abb0f93cSkardel 	if (up->linect > 0)
366abb0f93cSkardel 		pollchar = 'R';
367abb0f93cSkardel 	else
368abb0f93cSkardel 		pollchar = 'T';
369*eabc0478Schristos 	if (refclock_fdwrite(peer, pp->io.fd, &pollchar, 1) != 1)
370abb0f93cSkardel 		refclock_report(peer, CEVNT_FAULT);
371abb0f93cSkardel 	else
372abb0f93cSkardel 		pp->polls++;
373abb0f93cSkardel #endif
374abb0f93cSkardel }
375abb0f93cSkardel #endif
376abb0f93cSkardel 
377abb0f93cSkardel #else
378*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT
379abb0f93cSkardel #endif	/* defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK) */
380