1/*	$NetBSD: refclock_gpsvme.c,v 1.5 2020/05/25 20:47:25 christos Exp $	*/
2
3/* refclock_psc.c:  clock driver for Brandywine PCI-SyncClock32/HP-UX 11.X */
4
5#ifdef	HAVE_CONFIG_H
6#include	<config.h>
7#endif	/* HAVE_CONFIG_H	*/
8
9#if defined(REFCLOCK) && defined(CLOCK_GPSVME)
10
11#include	"ntpd.h"
12#include	"ntp_io.h"
13#include	"ntp_refclock.h"
14#include	"ntp_unixtime.h"
15#include	"ntp_stdlib.h"
16
17#ifdef	__hpux
18#include	<sys/rtprio.h>	/* may already be included above	*/
19#include	<sys/lock.h>	/* NEEDED for PROCLOCK			*/
20#endif	/* __hpux	*/
21
22#ifdef	__linux__
23#include	<sys/ioctl.h>	/* for _IOR, ioctl			*/
24#endif	/* __linux__	*/
25
26enum {				/* constants	*/
27    BUFSIZE			=	32,
28    PSC_SYNC_OK			=	0x40,	/* Sync status bit	*/
29    DP_LEAPSEC_DAY10DAY1 	= 	0x82,	/* DP RAM address	*/
30    DP_LEAPSEC_DAY1000DAY100	=	0x83,
31    DELAY			=	1,
32    NUNIT			=	2	/* max UNITS		*/
33};
34
35/*	clock card registers	*/
36struct psc_regs {
37    uint32_t		low_time;	/* card base + 0x00	*/
38    uint32_t		high_time;	/* card base + 0x04	*/
39    uint32_t		ext_low_time;	/* card base + 0x08	*/
40    uint32_t		ext_high_time;	/* card base + 0x0C	*/
41    uint8_t		device_status;	/* card base + 0x10	*/
42    uint8_t		device_control;	/* card base + 0x11	*/
43    uint8_t		reserved0;	/* card base + 0x12	*/
44    uint8_t		ext_100ns;	/* card base + 0x13	*/
45    uint8_t		match_usec;	/* card base + 0x14	*/
46    uint8_t		match_msec;	/* card base + 0x15	*/
47    uint8_t		reserved1;	/* card base + 0x16	*/
48    uint8_t		reserved2;	/* card base + 0x17	*/
49    uint8_t		reserved3;	/* card base + 0x18	*/
50    uint8_t		reserved4;	/* card base + 0x19	*/
51    uint8_t		dp_ram_addr;	/* card base + 0x1A	*/
52    uint8_t		reserved5;	/* card base + 0x1B	*/
53    uint8_t		reserved6;	/* card base + 0x1C	*/
54    uint8_t		reserved7;	/* card base + 0x1D	*/
55    uint8_t		dp_ram_data;	/* card base + 0x1E	*/
56    uint8_t		reserved8;	/* card base + 0x1F	*/
57} *volatile regp[NUNIT];
58
59#define	PSC_REGS	_IOR('K', 0, long)     	/* ioctl argument	*/
60
61/* Macros to swap byte order and convert BCD to binary	*/
62#define SWAP(val) ( ((val) >> 24) | (((val) & 0x00ff0000) >> 8) | \
63(((val) & 0x0000ff00) << 8) | (((val) & 0x000000ff) << 24) )
64#define BCD2INT2(val)  ( ((val) >> 4 & 0x0f)*10 + ((val) & 0x0f) )
65#define BCD2INT3(val)  ( ((val) >> 8 & 0x0f)*100 + ((val) >> 4 & 0x0f)*10 + \
66((val) & 0x0f) )
67
68/* PSC interface definitions */
69#define PRECISION	(-20)	/* precision assumed (1 us)	*/
70#define REFID		"USNO"	/* reference ID	*/
71#define DESCRIPTION	"Brandywine PCI-SyncClock32"
72#define DEVICE		"/dev/refclock%1d"	/* device file	*/
73
74/* clock unit control structure */
75struct psc_unit {
76    short	unit;		/* NTP refclock unit number	*/
77    short	last_hour;	/* last hour (monitor leap sec)	*/
78    int		msg_flag[2];	/* count error messages		*/
79};
80int	fd[NUNIT];		/* file descriptor	*/
81
82/* Local function prototypes */
83static int		psc_start(int, struct peer *);
84static void		psc_shutdown(int, struct peer *);
85static void		psc_poll(int, struct peer *);
86static void		check_leap_sec(struct refclockproc *, int);
87
88/* Transfer vector	*/
89struct refclock	refclock_gpsvme = {
90    psc_start, psc_shutdown, psc_poll, noentry, noentry, noentry, NOFLAGS
91};
92
93/* psc_start:  open device and initialize data for processing */
94static int
95psc_start(
96    int		unit,
97    struct peer	*peer
98    )
99{
100    char			buf[BUFSIZE];
101    struct refclockproc		*pp;
102    struct psc_unit		*up = emalloc(sizeof *up);
103
104    if (unit < 0 || unit > 1) {		/* support units 0 and 1	*/
105	msyslog(LOG_ERR, "psc_start: bad unit: %d", unit);
106	return 0;
107    }
108
109    memset(up, '\0', sizeof *up);
110
111    snprintf(buf, sizeof(buf), DEVICE, unit);	/* dev file name	*/
112    fd[unit] = open(buf, O_RDONLY);	/* open device file	*/
113    if (fd[unit] < 0) {
114	msyslog(LOG_ERR, "psc_start: unit: %d, open failed.  %m", unit);
115	return 0;
116    }
117
118    /* get the address of the mapped regs	*/
119    if (ioctl(fd[unit], PSC_REGS, &regp[unit]) < 0) {
120	msyslog(LOG_ERR, "psc_start: unit: %d, ioctl failed.  %m", unit);
121	return 0;
122    }
123
124    /* initialize peer variables	*/
125    pp = peer->procptr;
126    pp->io.clock_recv = noentry;
127    pp->io.srcclock = peer;
128    pp->io.datalen = 0;
129    pp->io.fd = -1;
130    pp->unitptr = up;
131    get_systime(&pp->lastrec);
132    memcpy(&pp->refid, REFID, 4);
133    peer->precision = PRECISION;
134    pp->clockdesc = DESCRIPTION;
135    up->unit = unit;
136#ifdef	__hpux
137    rtprio(0,120); 		/* set real time priority	*/
138    plock(PROCLOCK); 		/* lock process in memory	*/
139#endif	/* __hpux	*/
140    return 1;
141}
142
143/* psc_shutdown:  shut down the clock */
144static void
145psc_shutdown(
146    int		unit,
147    struct peer	*peer
148    )
149{
150    if (NULL != peer->procptr->unitptr)
151	free(peer->procptr->unitptr);
152    if (fd[unit] > 0)
153	close(fd[unit]);
154}
155
156/* psc_poll:  read, decode, and record device time */
157static void
158psc_poll(
159    int		unit,
160    struct peer	*peer
161    )
162{
163    struct refclockproc	*pp = peer->procptr;
164    struct psc_unit		*up;
165    unsigned			tlo, thi;
166    unsigned char		status;
167
168    up = (struct psc_unit *) pp->unitptr;
169    tlo = regp[unit]->low_time;		/* latch and read first 4 bytes	*/
170    thi = regp[unit]->high_time;	/* read 4 higher order bytes	*/
171    status = regp[unit]->device_status;	/* read device status byte	*/
172
173    if (!(status & PSC_SYNC_OK)) {
174	refclock_report(peer, CEVNT_BADTIME);
175	if (!up->msg_flag[unit]) {	/* write once to system log	*/
176	    msyslog(LOG_WARNING,
177		"SYNCHRONIZATION LOST on unit %1d, status %02x\n",
178		unit, status);
179	    up->msg_flag[unit] = 1;
180	}
181	return;
182    }
183
184    get_systime(&pp->lastrec);
185    pp->polls++;
186
187    tlo = SWAP(tlo);			/* little to big endian swap on	*/
188    thi = SWAP(thi);			/* copy of data			*/
189    /* convert the BCD time to broken down time used by refclockproc	*/
190    pp->day	= BCD2INT3((thi & 0x0FFF0000) >> 16);
191    pp->hour	= BCD2INT2((thi & 0x0000FF00) >> 8);
192    pp->minute	= BCD2INT2(thi & 0x000000FF);
193    pp->second	= BCD2INT2(tlo >> 24);
194    /* ntp_process() in ntp_refclock.c appears to use usec as fraction of
195       second in microseconds if usec is nonzero. */
196    pp->nsec	= 1000000*BCD2INT3((tlo & 0x00FFF000) >> 12) +
197	BCD2INT3(tlo & 0x00000FFF);
198
199    snprintf(pp->a_lastcode, sizeof(pp->a_lastcode),
200	     "%3.3d %2.2d:%2.2d:%2.2d.%09ld %02x %08x %08x", pp->day,
201	     pp->hour, pp->minute, pp->second, pp->nsec, status, thi,
202	     tlo);
203    pp->lencode = strlen(pp->a_lastcode);
204
205    /* compute the timecode timestamp	*/
206    if (!refclock_process(pp)) {
207	refclock_report(peer, CEVNT_BADTIME);
208	return;
209    }
210    /* simulate the NTP receive and packet procedures	*/
211    refclock_receive(peer);
212    /* write clock statistics to file	*/
213    record_clock_stats(&peer->srcadr, pp->a_lastcode);
214
215    /* With the first timecode beginning the day, check for a GPS
216       leap second notification.      */
217    if (pp->hour < up->last_hour) {
218	check_leap_sec(pp, unit);
219	up->msg_flag[0] = up->msg_flag[1] = 0;	/* reset flags	*/
220    }
221    up->last_hour = pp->hour;
222}
223
224/* check_leap_sec:  read the Dual Port RAM leap second day registers.  The
225   onboard GPS receiver should write the hundreds digit of day of year in
226   DP_LeapSec_Day1000Day100 and the tens and ones digits in
227   DP_LeapSec_Day10Day1.  If these values are nonzero and today, we have
228   a leap second pending, so we set the pp->leap flag to LEAP_ADDSECOND.
229   If the BCD data are zero or a date other than today, set pp->leap to
230   LEAP_NOWARNING.  */
231static void
232check_leap_sec(struct refclockproc *pp, int unit)
233{
234    unsigned char	dhi, dlo;
235    int			leap_day;
236
237    regp[unit]->dp_ram_addr = DP_LEAPSEC_DAY10DAY1;
238    usleep(DELAY);
239    dlo = regp[unit]->dp_ram_data;
240    regp[unit]->dp_ram_addr = DP_LEAPSEC_DAY1000DAY100;
241    usleep(DELAY);
242    dhi = regp[unit]->dp_ram_data;
243    leap_day = BCD2INT2(dlo) + 100*(dhi & 0x0F);
244
245    pp->leap = LEAP_NOWARNING;			/* default	*/
246    if (leap_day && leap_day == pp->day) {
247	pp->leap = LEAP_ADDSECOND;		/* leap second today	*/
248	msyslog(LOG_ERR, "LEAP_ADDSECOND flag set, day %d (%x %x).",
249	    leap_day, dhi, dlo);
250    }
251}
252
253#else
254int	refclock_gpsvme_bs;
255#endif	/* REFCLOCK	*/
256