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