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