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