1*eabc0478Schristos /* $NetBSD: refclock_pcf.c,v 1.10 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_pcf - clock driver for the Conrad parallel port radio clock 5abb0f93cSkardel */ 6abb0f93cSkardel 7abb0f93cSkardel #ifdef HAVE_CONFIG_H 8abb0f93cSkardel # include <config.h> 9abb0f93cSkardel #endif 10abb0f93cSkardel 11abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_PCF) 12abb0f93cSkardel 13abb0f93cSkardel #include "ntpd.h" 14abb0f93cSkardel #include "ntp_io.h" 15abb0f93cSkardel #include "ntp_refclock.h" 16abb0f93cSkardel #include "ntp_calendar.h" 17abb0f93cSkardel #include "ntp_stdlib.h" 18abb0f93cSkardel 19abb0f93cSkardel /* 20abb0f93cSkardel * This driver supports the parallel port radio clock sold by Conrad 21abb0f93cSkardel * Electronic under order numbers 967602 and 642002. 22abb0f93cSkardel * 23abb0f93cSkardel * It requires that the local timezone be CET/CEST and that the pcfclock 24abb0f93cSkardel * device driver be installed. A device driver for Linux is available at 25abb0f93cSkardel * http://home.pages.de/~voegele/pcf.html. Information about a FreeBSD 26abb0f93cSkardel * driver is available at http://schumann.cx/pcfclock/. 27abb0f93cSkardel */ 28abb0f93cSkardel 29abb0f93cSkardel /* 30abb0f93cSkardel * Interface definitions 31abb0f93cSkardel */ 32abb0f93cSkardel #define DEVICE "/dev/pcfclocks/%d" 33abb0f93cSkardel #define OLDDEVICE "/dev/pcfclock%d" 34abb0f93cSkardel #define PRECISION (-1) /* precision assumed (about 0.5 s) */ 35abb0f93cSkardel #define REFID "PCF" 36abb0f93cSkardel #define DESCRIPTION "Conrad parallel port radio clock" 37abb0f93cSkardel 38abb0f93cSkardel #define LENPCF 18 /* timecode length */ 39abb0f93cSkardel 40abb0f93cSkardel /* 41abb0f93cSkardel * Function prototypes 42abb0f93cSkardel */ 43abb0f93cSkardel static int pcf_start (int, struct peer *); 44abb0f93cSkardel static void pcf_shutdown (int, struct peer *); 45abb0f93cSkardel static void pcf_poll (int, struct peer *); 46abb0f93cSkardel 47abb0f93cSkardel /* 48abb0f93cSkardel * Transfer vector 49abb0f93cSkardel */ 50abb0f93cSkardel struct refclock refclock_pcf = { 51abb0f93cSkardel pcf_start, /* start up driver */ 52abb0f93cSkardel pcf_shutdown, /* shut down driver */ 53abb0f93cSkardel pcf_poll, /* transmit poll message */ 54abb0f93cSkardel noentry, /* not used */ 55abb0f93cSkardel noentry, /* initialize driver (not used) */ 56abb0f93cSkardel noentry, /* not used */ 57abb0f93cSkardel NOFLAGS /* not used */ 58abb0f93cSkardel }; 59abb0f93cSkardel 60abb0f93cSkardel 61abb0f93cSkardel /* 62abb0f93cSkardel * pcf_start - open the device and initialize data for processing 63abb0f93cSkardel */ 64abb0f93cSkardel static int 65abb0f93cSkardel pcf_start( 66abb0f93cSkardel int unit, 67abb0f93cSkardel struct peer *peer 68abb0f93cSkardel ) 69abb0f93cSkardel { 70abb0f93cSkardel struct refclockproc *pp; 71abb0f93cSkardel int fd; 72abb0f93cSkardel char device[128]; 73abb0f93cSkardel 74abb0f93cSkardel /* 75abb0f93cSkardel * Open device file for reading. 76abb0f93cSkardel */ 773123f114Skardel snprintf(device, sizeof(device), DEVICE, unit); 78abb0f93cSkardel fd = open(device, O_RDONLY); 79abb0f93cSkardel if (fd == -1) { 803123f114Skardel snprintf(device, sizeof(device), OLDDEVICE, unit); 81abb0f93cSkardel fd = open(device, O_RDONLY); 82abb0f93cSkardel } 83abb0f93cSkardel #ifdef DEBUG 84abb0f93cSkardel if (debug) 85abb0f93cSkardel printf ("starting PCF with device %s\n",device); 86abb0f93cSkardel #endif 87abb0f93cSkardel if (fd == -1) { 88abb0f93cSkardel return (0); 89abb0f93cSkardel } 90abb0f93cSkardel 91abb0f93cSkardel pp = peer->procptr; 92abb0f93cSkardel pp->io.clock_recv = noentry; 932950cc38Schristos pp->io.srcclock = peer; 94abb0f93cSkardel pp->io.datalen = 0; 95abb0f93cSkardel pp->io.fd = fd; 96abb0f93cSkardel 97abb0f93cSkardel /* 98abb0f93cSkardel * Initialize miscellaneous variables 99abb0f93cSkardel */ 100abb0f93cSkardel peer->precision = PRECISION; 101abb0f93cSkardel pp->clockdesc = DESCRIPTION; 102abb0f93cSkardel /* one transmission takes 172.5 milliseconds since the radio clock 103abb0f93cSkardel transmits 69 bits with a period of 2.5 milliseconds per bit */ 104abb0f93cSkardel pp->fudgetime1 = 0.1725; 105abb0f93cSkardel memcpy((char *)&pp->refid, REFID, 4); 106abb0f93cSkardel 107abb0f93cSkardel return (1); 108abb0f93cSkardel } 109abb0f93cSkardel 110abb0f93cSkardel 111abb0f93cSkardel /* 112abb0f93cSkardel * pcf_shutdown - shut down the clock 113abb0f93cSkardel */ 114abb0f93cSkardel static void 115abb0f93cSkardel pcf_shutdown( 116abb0f93cSkardel int unit, 117abb0f93cSkardel struct peer *peer 118abb0f93cSkardel ) 119abb0f93cSkardel { 120abb0f93cSkardel struct refclockproc *pp; 121abb0f93cSkardel 122abb0f93cSkardel pp = peer->procptr; 1232950cc38Schristos if (NULL != pp) 1242950cc38Schristos close(pp->io.fd); 125abb0f93cSkardel } 126abb0f93cSkardel 127abb0f93cSkardel 128abb0f93cSkardel /* 129abb0f93cSkardel * pcf_poll - called by the transmit procedure 130abb0f93cSkardel */ 131abb0f93cSkardel static void 132abb0f93cSkardel pcf_poll( 133abb0f93cSkardel int unit, 134abb0f93cSkardel struct peer *peer 135abb0f93cSkardel ) 136abb0f93cSkardel { 137abb0f93cSkardel struct refclockproc *pp; 138abb0f93cSkardel char buf[LENPCF]; 139abb0f93cSkardel struct tm tm, *tp; 140abb0f93cSkardel time_t t; 141abb0f93cSkardel 142abb0f93cSkardel pp = peer->procptr; 143abb0f93cSkardel 144abb0f93cSkardel buf[0] = 0; 145e19314b7Schristos if (read(pp->io.fd, buf, sizeof(buf)) < (ssize_t)sizeof(buf) || buf[0] != 9) { 146abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 147abb0f93cSkardel return; 148abb0f93cSkardel } 149abb0f93cSkardel 1502950cc38Schristos ZERO(tm); 151abb0f93cSkardel 152abb0f93cSkardel tm.tm_mday = buf[11] * 10 + buf[10]; 153abb0f93cSkardel tm.tm_mon = buf[13] * 10 + buf[12] - 1; 154abb0f93cSkardel tm.tm_year = buf[15] * 10 + buf[14]; 155abb0f93cSkardel tm.tm_hour = buf[7] * 10 + buf[6]; 156abb0f93cSkardel tm.tm_min = buf[5] * 10 + buf[4]; 157abb0f93cSkardel tm.tm_sec = buf[3] * 10 + buf[2]; 158abb0f93cSkardel tm.tm_isdst = (buf[8] & 1) ? 1 : (buf[8] & 2) ? 0 : -1; 159abb0f93cSkardel 160abb0f93cSkardel /* 161abb0f93cSkardel * Y2K convert the 2-digit year 162abb0f93cSkardel */ 163abb0f93cSkardel if (tm.tm_year < 99) 164abb0f93cSkardel tm.tm_year += 100; 165abb0f93cSkardel 166abb0f93cSkardel t = mktime(&tm); 167abb0f93cSkardel if (t == (time_t) -1) { 168abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 169abb0f93cSkardel return; 170abb0f93cSkardel } 171abb0f93cSkardel 172abb0f93cSkardel #if defined(__GLIBC__) && defined(_BSD_SOURCE) 173abb0f93cSkardel if ((tm.tm_isdst > 0 && tm.tm_gmtoff != 7200) 174abb0f93cSkardel || (tm.tm_isdst == 0 && tm.tm_gmtoff != 3600) 175abb0f93cSkardel || tm.tm_isdst < 0) { 176abb0f93cSkardel #ifdef DEBUG 177abb0f93cSkardel if (debug) 178abb0f93cSkardel printf ("local time zone not set to CET/CEST\n"); 179abb0f93cSkardel #endif 180abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 181abb0f93cSkardel return; 182abb0f93cSkardel } 183abb0f93cSkardel #endif 184abb0f93cSkardel 185abb0f93cSkardel pp->lencode = strftime(pp->a_lastcode, BMAX, "%Y %m %d %H %M %S", &tm); 186abb0f93cSkardel 187abb0f93cSkardel #if defined(_REENTRANT) || defined(_THREAD_SAFE) 188abb0f93cSkardel tp = gmtime_r(&t, &tm); 189abb0f93cSkardel #else 190abb0f93cSkardel tp = gmtime(&t); 191abb0f93cSkardel #endif 192abb0f93cSkardel if (!tp) { 193abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 194abb0f93cSkardel return; 195abb0f93cSkardel } 196abb0f93cSkardel 197abb0f93cSkardel get_systime(&pp->lastrec); 198abb0f93cSkardel pp->polls++; 199abb0f93cSkardel pp->year = tp->tm_year + 1900; 200abb0f93cSkardel pp->day = tp->tm_yday + 1; 201abb0f93cSkardel pp->hour = tp->tm_hour; 202abb0f93cSkardel pp->minute = tp->tm_min; 203abb0f93cSkardel pp->second = tp->tm_sec; 204abb0f93cSkardel pp->nsec = buf[16] * 31250000; 205abb0f93cSkardel if (buf[17] & 1) 206abb0f93cSkardel pp->nsec += 500000000; 207abb0f93cSkardel 208abb0f93cSkardel #ifdef DEBUG 209abb0f93cSkardel if (debug) 210abb0f93cSkardel printf ("pcf%d: time is %04d/%02d/%02d %02d:%02d:%02d UTC\n", 211abb0f93cSkardel unit, pp->year, tp->tm_mon + 1, tp->tm_mday, pp->hour, 212abb0f93cSkardel pp->minute, pp->second); 213abb0f93cSkardel #endif 214abb0f93cSkardel 215abb0f93cSkardel if (!refclock_process(pp)) { 216abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 217abb0f93cSkardel return; 218abb0f93cSkardel } 219abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 220abb0f93cSkardel if ((buf[1] & 1) && !(pp->sloppyclockflag & CLK_FLAG2)) 221abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 222abb0f93cSkardel else 223abb0f93cSkardel pp->leap = LEAP_NOWARNING; 224abb0f93cSkardel pp->lastref = pp->lastrec; 225abb0f93cSkardel refclock_receive(peer); 226abb0f93cSkardel } 227abb0f93cSkardel #else 228*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 229abb0f93cSkardel #endif /* REFCLOCK */ 230