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