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