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