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