xref: /freebsd/contrib/ntp/ntpd/refclock_dumbclock.c (revision daf1cffce2e07931f27c6c6998652e90df6ba87e)
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 <stdio.h>
18 #include <ctype.h>
19 #include <sys/time.h>
20 #include <time.h>
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 /*
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		P((int, struct peer *));
74 static	void	dumbclock_shutdown	P((int, struct peer *));
75 static	void	dumbclock_receive	P((struct recvbuf *));
76 #if 0
77 static	void	dumbclock_poll		P((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 	(void)sprintf(device, DEVICE, unit);
115 #ifdef DEBUG
116 	if (debug)
117 		printf ("starting Dumbclock with device %s\n",device);
118 #endif
119 	if (!(fd = refclock_open(device, SPEED232, 0)))
120 		return (0);
121 
122 	/*
123 	 * Allocate and initialize unit structure
124 	 */
125 	if (!(up = (struct dumbclock_unit *)
126 	      emalloc(sizeof(struct dumbclock_unit)))) {
127 		(void) close(fd);
128 		return (0);
129 	}
130 	memset((char *)up, 0, sizeof(struct dumbclock_unit));
131 	pp = peer->procptr;
132 	pp->unitptr = (caddr_t)up;
133 	pp->io.clock_recv = dumbclock_receive;
134 	pp->io.srcclock = (caddr_t)peer;
135 	pp->io.datalen = 0;
136 	pp->io.fd = fd;
137 	if (!io_addclock(&pp->io)) {
138 		(void) close(fd);
139 		free(up);
140 		return (0);
141 	}
142 
143 
144 	time(&now);
145 #ifdef GET_LOCALTIME
146 	tm_time_p = localtime(&now);
147 #else
148 	tm_time_p = gmtime(&now);
149 #endif
150 	if (tm_time_p)
151 	{
152 	    up->ymd = *tm_time_p;
153 	}
154 	else
155 	{
156 	    return 0;
157 	}
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  */
172 static void
173 dumbclock_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 = (struct dumbclock_unit *)pp->unitptr;
183 	io_closeclock(&pp->io);
184 	free(up);
185 }
186 
187 
188 /*
189  * dumbclock_receive - receive data from the serial interface
190  */
191 static void
192 dumbclock_receive(
193 	struct recvbuf *rbufp
194 	)
195 {
196 	struct dumbclock_unit *up;
197 	struct refclockproc *pp;
198 	struct peer *peer;
199 
200 	l_fp	     trtmp;	/* arrival timestamp */
201 	int          hours;	/* hour-of-day */
202 	int	     minutes;	/* minutes-past-the-hour */
203 	int          seconds;	/* seconds */
204 	int	     temp;	/* int temp */
205 	int          got_good;	/* got a good time flag */
206 
207 	/*
208 	 * Initialize pointers and read the timecode and timestamp
209 	 */
210 	peer = (struct peer *)rbufp->recv_srcclock;
211 	pp = peer->procptr;
212 	up = (struct dumbclock_unit *)pp->unitptr;
213 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
214 
215 	if (temp == 0) {
216 		if (up->tcswitch == 0) {
217 			up->tcswitch = 1;
218 			up->laststamp = trtmp;
219 		} else
220 		    up->tcswitch = 0;
221 		return;
222 	}
223 	pp->lencode = temp;
224 	pp->lastrec = up->laststamp;
225 	up->laststamp = trtmp;
226 	up->tcswitch = 1;
227 
228 #ifdef DEBUG
229 	if (debug)
230 		printf("dumbclock: timecode %d %s\n",
231 		       pp->lencode, pp->a_lastcode);
232 #endif
233 
234 	/*
235 	 * We get down to business. Check the timecode format...
236 	 */
237 	pp->msec = 0;
238 	got_good=0;
239 	if (sscanf(pp->a_lastcode,"%02d:%02d:%02d",
240 		   &hours,&minutes,&seconds) == 3)
241 	{
242 	    struct tm *gmtp;
243 	    struct tm *lt_p;
244 	    time_t     asserted_time;	     /* the SPM time based on the composite time+date */
245 	    struct tm  asserted_tm;	     /* the struct tm of the same */
246 	    int        adjyear;
247 	    int        adjmon;
248 	    int        reality_delta;
249 	    time_t     now;
250 
251 
252 	    /*
253 	     * Convert to GMT for sites that distribute localtime.  This
254              * means we have to figure out what day it is.  Easier said
255 	     * than done...
256 	     */
257 
258 	    asserted_tm.tm_year  = up->ymd.tm_year;
259 	    asserted_tm.tm_mon   = up->ymd.tm_mon;
260 	    asserted_tm.tm_mday  = up->ymd.tm_mday;
261 	    asserted_tm.tm_hour  = hours;
262 	    asserted_tm.tm_min   = minutes;
263 	    asserted_tm.tm_sec   = seconds;
264 	    asserted_tm.tm_isdst = -1;
265 
266 #ifdef GET_LOCALTIME
267 	    asserted_time = mktime (&asserted_tm);
268 	    time(&now);
269 #else
270 #include "GMT unsupported for dumbclock!"
271 #endif
272 	    reality_delta = asserted_time - now;
273 
274 	    /*
275 	     * We assume that if the time is grossly wrong, it's because we got the
276 	     * year/month/day wrong.
277 	     */
278 	    if (reality_delta > INSANE_SECONDS)
279 	    {
280 		asserted_time -= SECSPERDAY; /* local clock behind real time */
281 	    }
282 	    else if (-reality_delta > INSANE_SECONDS)
283 	    {
284 		asserted_time += SECSPERDAY; /* local clock ahead of real time */
285 	    }
286 	    lt_p = localtime(&asserted_time);
287 	    if (lt_p)
288 	    {
289 		up->ymd = *lt_p;
290 	    }
291 	    else
292 	    {
293 		refclock_report (peer, CEVNT_FAULT);
294 		return;
295 	    }
296 
297 	    if ((gmtp = gmtime (&asserted_time)) == NULL)
298 	    {
299 		refclock_report (peer, CEVNT_FAULT);
300 		return;
301 	    }
302 	    adjyear = gmtp->tm_year+1900;
303 	    adjmon  = gmtp->tm_mon+1;
304 	    pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
305 	    pp->hour   = gmtp->tm_hour;
306 	    pp->minute = gmtp->tm_min;
307 	    pp->second = gmtp->tm_sec;
308 #ifdef DEBUG
309 	    if (debug)
310 		printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
311 			adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
312 			pp->second);
313 #endif
314 
315 	    got_good=1;
316 	}
317 
318 	if (!got_good)
319 	{
320 	    if (up->linect > 0)
321 	    	up->linect--;
322 	    else
323 	    	refclock_report(peer, CEVNT_BADREPLY);
324 	    return;
325 	}
326 
327 	/*
328 	 * Process the new sample in the median filter and determine the
329 	 * timecode timestamp.
330 	 */
331 	if (!refclock_process(pp)) {
332 		refclock_report(peer, CEVNT_BADTIME);
333 		return;
334 	}
335 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
336 	refclock_receive(peer);
337 	up->lasthour = pp->hour;
338 }
339 
340 #if 0
341 /*
342  * dumbclock_poll - called by the transmit procedure
343  */
344 static void
345 dumbclock_poll(
346 	int unit,
347 	struct peer *peer
348 	)
349 {
350 	register struct dumbclock_unit *up;
351 	struct refclockproc *pp;
352 	char pollchar;
353 
354 	/*
355 	 * Time to poll the clock. The Chrono-log clock is supposed to
356 	 * respond to a 'T' by returning a timecode in the format(s)
357 	 * specified above.  Ours does (can?) not, but this seems to be
358 	 * an installation-specific problem.  This code is dyked out,
359 	 * but may be re-enabled if anyone ever finds a Chrono-log that
360 	 * actually listens to this command.
361 	 */
362 #if 0
363 	pp = peer->procptr;
364 	up = (struct dumbclock_unit *)pp->unitptr;
365 	if (peer->reach == 0)
366 		refclock_report(peer, CEVNT_TIMEOUT);
367 	if (up->linect > 0)
368 		pollchar = 'R';
369 	else
370 		pollchar = 'T';
371 	if (write(pp->io.fd, &pollchar, 1) != 1)
372 		refclock_report(peer, CEVNT_FAULT);
373 	else
374 		pp->polls++;
375 #endif
376 }
377 #endif
378 
379 #else
380 int refclock_dumbclock_bs;
381 #endif /* REFCLOCK */
382