xref: /freebsd/contrib/ntp/ntpd/refclock_dumbclock.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
1 /*
2  * refclock_dumbclock - clock driver for a unknown time distribution system
3  * that only provides hh:mm:ss (in local time, yet!).
4  */
5 
6 /*
7  * Must interpolate back to local time.  Very annoying.
8  */
9 #define GET_LOCALTIME
10 
11 #ifdef HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14 
15 #if defined(SYS_WINNT)
16 #undef close
17 #define close closesocket
18 #endif
19 
20 #if defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK)
21 
22 #include "ntpd.h"
23 #include "ntp_io.h"
24 #include "ntp_refclock.h"
25 #include "ntp_calendar.h"
26 #include "ntp_stdlib.h"
27 
28 #include <stdio.h>
29 #include <ctype.h>
30 
31 /*
32  * This driver supports a generic dumb clock that only outputs hh:mm:ss,
33  * in local time, no less.
34  *
35  * Input format:
36  *
37  *      hh:mm:ss   <cr>
38  *
39  * hh:mm:ss -- what you'd expect, with a 24 hour clock.  (Heck, that's the only
40  * way it could get stupider.)  We take time on the <cr>.
41  *
42  * The original source of this module was the WWVB module.
43  */
44 
45 /*
46  * Interface definitions
47  */
48 #define	DEVICE		"/dev/dumbclock%d" /* device name and unit */
49 #define	SPEED232	B9600	/* uart speed (9600 baud) */
50 #define	PRECISION	(-13)	/* precision assumed (about 100 us) */
51 #define	REFID		"dumbclock"	/* reference ID */
52 #define	DESCRIPTION	"Dumb clock" /* WRU */
53 
54 
55 /*
56  * Insanity check.  Since the time is local, we need to make sure that during midnight
57  * transitions, we can convert back to Unix time.  If the conversion results in some number
58  * worse than this number of seconds away, assume the next day and retry.
59  */
60 #define INSANE_SECONDS 3600
61 
62 /*
63  * Dumb clock control structure
64  */
65 struct dumbclock_unit {
66 	u_char	  tcswitch;		     /* timecode switch */
67 	l_fp	  laststamp;		     /* last receive timestamp */
68 	u_char	  lasthour;		     /* last hour (for monitor) */
69 	u_char	  linect;		     /* count ignored lines (for monitor */
70         struct tm ymd;			     /* struct tm for y/m/d only */
71 };
72 
73 /*
74  * Function prototypes
75  */
76 static	int	dumbclock_start		P((int, struct peer *));
77 static	void	dumbclock_shutdown	P((int, struct peer *));
78 static	void	dumbclock_receive	P((struct recvbuf *));
79 #if 0
80 static	void	dumbclock_poll		P((int, struct peer *));
81 #endif
82 
83 /*
84  * Transfer vector
85  */
86 struct	refclock refclock_dumbclock = {
87 	dumbclock_start,		     /* start up driver */
88 	dumbclock_shutdown,		     /* shut down driver */
89 	noentry,			     /* poll the driver -- a nice fabrication */
90 	noentry,			     /* not used */
91 	noentry,			     /* not used */
92 	noentry,			     /* not used */
93 	NOFLAGS				     /* not used */
94 };
95 
96 
97 /*
98  * dumbclock_start - open the devices and initialize data for processing
99  */
100 static int
101 dumbclock_start(
102 	int unit,
103 	struct peer *peer
104 	)
105 {
106 	register struct dumbclock_unit *up;
107 	struct refclockproc *pp;
108 	int fd;
109 	char device[20];
110 	struct tm *tm_time_p;
111 	time_t     now;
112 
113 	/*
114 	 * Open serial port. Don't bother with CLK line discipline, since
115 	 * it's not available.
116 	 */
117 	(void)sprintf(device, DEVICE, unit);
118 #ifdef DEBUG
119 	if (debug)
120 		printf ("starting Dumbclock with device %s\n",device);
121 #endif
122 	fd = refclock_open(device, SPEED232, 0);
123 	if (fd < 0)
124 		return (0);
125 
126 	/*
127 	 * Allocate and initialize unit structure
128 	 */
129 	up = (struct dumbclock_unit *)emalloc(sizeof(struct dumbclock_unit));
130 	if (up == NULL) {
131 		(void) close(fd);
132 		return (0);
133 	}
134 	memset((char *)up, 0, sizeof(struct dumbclock_unit));
135 	pp = peer->procptr;
136 	pp->unitptr = (caddr_t)up;
137 	pp->io.clock_recv = dumbclock_receive;
138 	pp->io.srcclock = (caddr_t)peer;
139 	pp->io.datalen = 0;
140 	pp->io.fd = fd;
141 	if (!io_addclock(&pp->io)) {
142 		(void) close(fd);
143 		free(up);
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 	{
156 	    up->ymd = *tm_time_p;
157 	}
158 	else
159 	{
160 	    return 0;
161 	}
162 
163 	/*
164 	 * Initialize miscellaneous variables
165 	 */
166 	peer->precision = PRECISION;
167 	pp->clockdesc = DESCRIPTION;
168 	memcpy((char *)&pp->refid, REFID, 4);
169 	return (1);
170 }
171 
172 
173 /*
174  * dumbclock_shutdown - shut down the clock
175  */
176 static void
177 dumbclock_shutdown(
178 	int unit,
179 	struct peer *peer
180 	)
181 {
182 	register struct dumbclock_unit *up;
183 	struct refclockproc *pp;
184 
185 	pp = peer->procptr;
186 	up = (struct dumbclock_unit *)pp->unitptr;
187 	io_closeclock(&pp->io);
188 	free(up);
189 }
190 
191 
192 /*
193  * dumbclock_receive - receive data from the serial interface
194  */
195 static void
196 dumbclock_receive(
197 	struct recvbuf *rbufp
198 	)
199 {
200 	struct dumbclock_unit *up;
201 	struct refclockproc *pp;
202 	struct peer *peer;
203 
204 	l_fp	     trtmp;	/* arrival timestamp */
205 	int          hours;	/* hour-of-day */
206 	int	     minutes;	/* minutes-past-the-hour */
207 	int          seconds;	/* seconds */
208 	int	     temp;	/* int temp */
209 	int          got_good;	/* got a good time flag */
210 
211 	/*
212 	 * Initialize pointers and read the timecode and timestamp
213 	 */
214 	peer = (struct peer *)rbufp->recv_srcclock;
215 	pp = peer->procptr;
216 	up = (struct dumbclock_unit *)pp->unitptr;
217 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
218 
219 	if (temp == 0) {
220 		if (up->tcswitch == 0) {
221 			up->tcswitch = 1;
222 			up->laststamp = trtmp;
223 		} else
224 		    up->tcswitch = 0;
225 		return;
226 	}
227 	pp->lencode = (u_short)temp;
228 	pp->lastrec = up->laststamp;
229 	up->laststamp = trtmp;
230 	up->tcswitch = 1;
231 
232 #ifdef DEBUG
233 	if (debug)
234 		printf("dumbclock: timecode %d %s\n",
235 		       pp->lencode, pp->a_lastcode);
236 #endif
237 
238 	/*
239 	 * We get down to business. Check the timecode format...
240 	 */
241 	got_good=0;
242 	if (sscanf(pp->a_lastcode,"%02d:%02d:%02d",
243 		   &hours,&minutes,&seconds) == 3)
244 	{
245 	    struct tm *gmtp;
246 	    struct tm *lt_p;
247 	    time_t     asserted_time;	     /* the SPM time based on the composite time+date */
248 	    struct tm  asserted_tm;	     /* the struct tm of the same */
249 	    int        adjyear;
250 	    int        adjmon;
251 	    int        reality_delta;
252 	    time_t     now;
253 
254 
255 	    /*
256 	     * Convert to GMT for sites that distribute localtime.  This
257              * means we have to figure out what day it is.  Easier said
258 	     * than done...
259 	     */
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  */
348 static void
349 dumbclock_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 = (struct dumbclock_unit *)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
384 int refclock_dumbclock_bs;
385 #endif /* REFCLOCK */
386