Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / ntp / ntpd / refclock_dumbclock.c
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 */