xref: /netbsd-src/external/bsd/ntp/dist/ntpd/refclock_pcf.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
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