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