1 /*        $NetBSD: refclock_pcf.c,v 1.10 2024/08/18 20:47:18 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  */
43 static    int       pcf_start                     (int, struct peer *);
44 static    void      pcf_shutdown                  (int, struct peer *);
45 static    void      pcf_poll            (int, struct peer *);
46 
47 /*
48  * Transfer vector
49  */
50 struct  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  */
64 static int
pcf_start(int unit,struct peer * peer)65 pcf_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  */
114 static void
pcf_shutdown(int unit,struct peer * peer)115 pcf_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  */
131 static void
pcf_poll(int unit,struct peer * peer)132 pcf_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
228 NONEMPTY_TRANSLATION_UNIT
229 #endif /* REFCLOCK */
230