refclock_bancomm.c revision 344884
1/* refclock_bancomm.c - clock driver for the  Datum/Bancomm bc635VME
2 * Time and Frequency Processor. It requires the BANCOMM bc635VME/
3 * bc350VXI Time and Frequency Processor Module Driver for SunOS4.x
4 * and SunOS5.x UNIX Systems. It has been tested on a UltraSparc
5 * IIi-cEngine running Solaris 2.6.
6 *
7 * Author(s): 	Ganesh Ramasivan & Gary Cliff, Computing Devices Canada,
8 *		Ottawa, Canada
9 *
10 * Date: 	July 1999
11 *
12 * Note(s):	The refclock type has been defined as 16.
13 *
14 *		This program has been modelled after the Bancomm driver
15 *		originally written by R. Schmidt of Time Service, U.S.
16 *		Naval Observatory for a HP-UX machine. Since the original
17 *		authors no longer plan to maintain this code, all
18 *		references to the HP-UX vme2 driver subsystem bave been
19 *		removed. Functions vme_report_event(), vme_receive(),
20 *		vme_control() and vme_buginfo() have been deleted because
21 *		they are no longer being used.
22 *
23 *	04/28/2005 Rob Neal
24 *		Modified to add support for Symmetricom bc637PCI-U Time &
25 *		Frequency Processor.
26 *	2/21/2007 Ali Ghorashi
27 *	        Modified to add support for Symmetricom bc637PCI-U Time &
28 *		Frequency Processor on Solaris.
29 *		Tested on Solaris 10 with a bc635 card.
30 *
31 *		Card bus type (VME/VXI or PCI) and environment are specified via the
32 *		"mode" keyword on the server command in ntp.conf.
33 *		server 127.127.16.u prefer mode M
34 *		where u is the id (usually 0) of the entry in /dev (/dev/stfp0)
35 *
36 *		and M is one of the following modes:
37 *		1		: FreeBSD PCI 635/637.
38 *		2		: Linux or Windows PCI 635/637.
39 *		3		: Solaris PCI 635/637
40 *		not specified, or other number:
41 *				: Assumed to be VME/VXI legacy Bancomm card on Solaris.
42 *		Linux and Windows platforms require Symmetricoms' proprietary driver
43 *		for the TFP card.
44 *		Solaris requires Symmetricom's driver and its header file (freely distributed) to
45 *		be installed and running.
46 */
47
48#ifdef HAVE_CONFIG_H
49#include <config.h>
50#endif
51
52#if defined(REFCLOCK) && defined(CLOCK_BANC)
53
54#include "ntpd.h"
55#include "ntp_io.h"
56#include "ntp_refclock.h"
57#include "ntp_unixtime.h"
58#include "ntp_stdlib.h"
59
60#include <stdio.h>
61#include <syslog.h>
62#include <ctype.h>
63#ifdef HAVE_SYS_IOCTL_H
64# include <sys/ioctl.h>
65#endif
66
67struct btfp_time                /* Structure for reading 5 time words   */
68                                /* in one ioctl(2) operation.           */
69{
70	unsigned short btfp_time[5];  /* Time words 0,1,2,3, and 4. (16bit)*/
71};
72/* SunOS5 ioctl commands definitions.*/
73#define BTFPIOC            ( 'b'<< 8 )
74#define IOCIO( l, n )      ( BTFPIOC | n )
75#define IOCIOR( l, n, s )  ( BTFPIOC | n )
76#define IOCIORN( l, n, s ) ( BTFPIOC | n )
77#define IOCIOWN( l, n, s ) ( BTFPIOC | n )
78
79/***** Simple ioctl commands *****/
80#define RUNLOCK		IOCIOR(b, 19, int )  /* Release Capture Lockout */
81#define RCR0		IOCIOR(b, 22, int )  /* Read control register zero.*/
82#define	WCR0		IOCIOWN(b, 23, int)  /* Write control register zero*/
83/***** Compound ioctl commands *****/
84
85/* Read all 5 time words in one call.   */
86#if defined(__FreeBSD__)
87# define READTIME	_IOR('u', 5, struct btfp_time )
88#else
89# define READTIME	IOCIORN(b, 32, sizeof( struct btfp_time ))
90#endif
91
92/* Solaris specific section */
93struct	stfp_tm {
94	int32_t tm_sec;
95	int32_t tm_min;
96	int32_t tm_hour;
97	int32_t tm_mday;
98	int32_t tm_mon;
99	int32_t tm_year;
100	int32_t tm_wday;
101	int32_t tm_yday;
102	int32_t tm_isdst;
103};
104
105struct stfp_time {
106	struct stfp_tm	tm;
107	int32_t 	usec;			/* usec 0 - 999999 */
108	int32_t 	hnsec;			/* hnsec 0 - 9 (hundreds of nsecs) */
109	int32_t 	status;
110};
111
112#define SELTIMEFORMAT	2
113#	define TIME_DECIMAL 0
114#	define TIME_BINARY	1
115
116#if defined(__sun__)
117#undef	READTIME
118#define READTIME		9
119#endif /** __sun___ **/
120/* end solaris specific section */
121
122struct vmedate {			   /* structure returned by get_vmetime.c */
123	unsigned short year;
124	unsigned short day;
125	unsigned short hr;
126	unsigned short mn;
127	unsigned short sec;
128	long frac;
129	unsigned short status;
130};
131
132typedef void *SYMMT_PCI_HANDLE;
133
134/*
135 * VME interface parameters.
136 */
137#define VMEPRECISION    (-21)   /* precision assumed (1 us) */
138#define USNOREFID       "BTFP"  /* or whatever */
139#define VMEREFID        "BTFP"  /* reference id */
140#define VMEDESCRIPTION  "Bancomm bc635 TFP" /* who we are */
141#define VMEHSREFID      0x7f7f1000 /* 127.127.16.00 refid hi strata */
142/* clock type 16 is used here  */
143#define GMT           	0       /* hour offset from Greenwich */
144
145/*
146 * Imported from ntp_timer module
147 */
148extern u_long current_time;     /* current time(s) */
149
150/*
151 * VME unit control structure.
152 * Changes made to vmeunit structure. Most members are now available in the
153 * new refclockproc structure in ntp_refclock.h - 07/99 - Ganesh Ramasivan
154 */
155struct vmeunit {
156	struct vmedate vmedata; /* data returned from vme read */
157	u_long lasttime;        /* last time clock heard from */
158};
159
160/*
161 * Function prototypes
162 */
163static  int     vme_start       (int, struct peer *);
164static  void    vme_shutdown    (int, struct peer *);
165static  void    vme_receive     (struct recvbuf *);
166static  void    vme_poll        (int unit, struct peer *);
167struct vmedate *get_datumtime(struct vmedate *);
168void	tvme_fill(struct vmedate *, uint32_t btm[2]);
169void	stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp);
170static const char *get_devicename(int n);
171
172/* [Bug 3558] and [Bug 1674]  perlinger@ntp.org says:
173 *
174 * bcReadBinTime() is defined to use two DWORD pointers on Windows and
175 * Linux in the BANCOMM SDK.  DWORD is of course Windows-specific
176 * (*shudder*), and it is defined as 'unsigned long' under
177 * Linux/Unix. (*sigh*)
178 *
179 * This creates quite some headache.  The size of 'unsigned long' is
180 * platform/compiler/memory-model dependent (LP32 vs LP64 vs LLP64),
181 * while the card itself always creates 32bit time stamps.  What a
182 * bummer.  And DWORD has tendency to contain 64bit on Win64 (which is
183 * why we have a DWORD32 defined on Win64) so it can be used as
184 * substitute for 'UINT_PTR' in Windows API headers.  I won't even try
185 * to comment on that, because anything I have to say will not be civil.
186 *
187 * We work around this by possibly using a wrapper function that makes
188 * the necessary conversions/casts.  It might be a bit tricky to
189 * maintain the conditional logic below, but any lingering disease needs
190 * constant care to avoid a breakout.
191 */
192#if defined(__linux__)
193  typedef unsigned long bcBinTimeT;
194# if SIZEOF_LONG == 4
195#   define safeReadBinTime bcReadBinTime
196# endif
197#elif defined(SYS_WINNT)
198  typedef DWORD bcBinTimeT;
199# if !defined(_WIN64) || _WIN64 == 0
200#   define safeReadBinTime bcReadBinTime
201# endif
202#else
203  typedef uint32_t bcBinTimeT;
204# define safeReadBinTime bcReadBinTime
205#endif
206
207/*
208 * Define the bc*() functions as weak so we can compile/link without them.
209 * Only clients with the card will have the proprietary vendor device driver
210 * and interface library needed for use on Linux/Windows platforms.
211 */
212extern uint32_t __attribute__ ((weak)) bcReadBinTime(SYMMT_PCI_HANDLE, bcBinTimeT*, bcBinTimeT*, uint8_t*);
213extern SYMMT_PCI_HANDLE __attribute__ ((weak)) bcStartPci(void);
214extern void __attribute__ ((weak)) bcStopPci(SYMMT_PCI_HANDLE);
215
216/* This is the conversion wrapper for the long/DWORD/uint32_t clash in
217 * reading binary times.
218 */
219#ifndef safeReadBinTime
220static uint32_t
221safeReadBinTime(
222	SYMMT_PCI_HANDLE hnd,
223	uint32_t        *pt1,
224	uint32_t        *pt2,
225	uint8_t         *p3
226	)
227{
228	bcBinTimeT t1, t2;
229	uint32_t   rc;
230
231	rc = bcReadBinTime(hnd, &t1, &t2, p3);
232	if (rc != 0) {
233		*pt1 = (uint32_t)t1;
234		*pt2 = (uint32_t)t2;
235	}
236	return rc;
237}
238#endif /* !defined(safeReadBinTime) */
239
240/*
241 * Transfer vector
242 */
243struct  refclock refclock_bancomm = {
244	vme_start, 		/* start up driver */
245	vme_shutdown,		/* shut down driver */
246	vme_poll,		/* transmit poll message */
247	noentry,		/* not used (old vme_control) */
248	noentry,		/* initialize driver */
249	noentry,		/* not used (old vme_buginfo) */
250	NOFLAGS			/* not used */
251};
252
253int fd_vme;  /* file descriptor for ioctls */
254int regvalue;
255int tfp_type;	/* mode selector, indicate platform and driver interface */
256SYMMT_PCI_HANDLE stfp_handle;
257
258/* This helper function returns the device name based on the platform we
259 * are running on and the device number.
260 *
261 * Uses a static buffer, so the result is valid only to the next call of
262 * this function!
263 */
264static const char*
265get_devicename(int n)
266{
267
268#   if defined(__sun__)
269	static const char * const template ="/dev/stfp%d";
270#   else
271	static const char * const template ="/dev/btfp%d";
272#   endif
273	static char namebuf[20];
274
275	snprintf(namebuf, sizeof(namebuf), template, n);
276	namebuf[sizeof(namebuf)-1] = '\0'; /* paranoia rulez! */
277	return namebuf;
278}
279
280/*
281 * vme_start - open the VME device and initialize data for processing
282 */
283static int
284vme_start(
285	int unit,
286	struct peer *peer
287	)
288{
289	register struct vmeunit *vme;
290	struct refclockproc *pp;
291	int dummy;
292	char vmedev[20];
293
294	tfp_type = (int)(peer->ttl);
295	switch (tfp_type) {
296		case 1:
297		case 3:
298			break;
299		case 2:
300			stfp_handle = bcStartPci(); 	/* init the card in lin/win */
301			break;
302		default:
303			break;
304	}
305	/*
306	 * Open VME device
307	 */
308#ifdef DEBUG
309
310	printf("Opening DATUM DEVICE %s\n",get_devicename(peer->refclkunit));
311#endif
312	if ( (fd_vme = open(get_devicename(peer->refclkunit), O_RDWR)) < 0) {
313		msyslog(LOG_ERR, "vme_start: failed open of %s: %m", vmedev);
314		return (0);
315	}
316	else  {
317		switch (tfp_type) {
318		  	case 1:	break;
319			case 2: break;
320			case 3:break;
321			default:
322				/* Release capture lockout in case it was set before. */
323				if( ioctl( fd_vme, RUNLOCK, &dummy ) )
324		    		msyslog(LOG_ERR, "vme_start: RUNLOCK failed %m");
325
326				regvalue = 0; /* More esoteric stuff to do... */
327				if( ioctl( fd_vme, WCR0, &regvalue ) )
328		    		msyslog(LOG_ERR, "vme_start: WCR0 failed %m");
329				break;
330		}
331	}
332
333	/*
334	 * Allocate unit structure
335	 */
336	vme = emalloc_zero(sizeof(struct vmeunit));
337
338
339	/*
340	 * Set up the structures
341	 */
342	pp = peer->procptr;
343	pp->unitptr = vme;
344	pp->timestarted = current_time;
345
346	pp->io.clock_recv = vme_receive;
347	pp->io.srcclock = peer;
348	pp->io.datalen = 0;
349	pp->io.fd = fd_vme;
350	/* shouldn't there be an io_addclock() call? */
351
352	/*
353	 * All done.  Initialize a few random peer variables, then
354 	 * return success. Note that root delay and root dispersion are
355	 * always zero for this clock.
356	 */
357	peer->precision = VMEPRECISION;
358	memcpy(&pp->refid, USNOREFID,4);
359	return (1);
360}
361
362
363/*
364 * vme_shutdown - shut down a VME clock
365 */
366static void
367vme_shutdown(
368	int unit,
369	struct peer *peer
370	)
371{
372	register struct vmeunit *vme;
373	struct refclockproc *pp;
374
375	/*
376	 * Tell the I/O module to turn us off.  We're history.
377	 */
378	pp = peer->procptr;
379	vme = pp->unitptr;
380	io_closeclock(&pp->io);
381	pp->unitptr = NULL;
382	if (NULL != vme)
383		free(vme);
384	if (tfp_type == 2)
385		bcStopPci(stfp_handle);
386}
387
388
389/*
390 * vme_receive - receive data from the VME device.
391 *
392 * Note: This interface would be interrupt-driven. We don't use that
393 * now, but include a dummy routine for possible future adventures.
394 */
395static void
396vme_receive(
397	struct recvbuf *rbufp
398	)
399{
400}
401
402
403/*
404 * vme_poll - called by the transmit procedure
405 */
406static void
407vme_poll(
408	int unit,
409	struct peer *peer
410	)
411{
412	struct vmedate *tptr;
413	struct vmeunit *vme;
414	struct refclockproc *pp;
415	time_t tloc;
416	struct tm *tadr;
417
418	pp = peer->procptr;
419	vme = pp->unitptr;        /* Here is the structure */
420
421	tptr = &vme->vmedata;
422	if ((tptr = get_datumtime(tptr)) == NULL ) {
423		refclock_report(peer, CEVNT_BADREPLY);
424		return;
425	}
426
427	get_systime(&pp->lastrec);
428	pp->polls++;
429	vme->lasttime = current_time;
430
431	/*
432	 * Get VME time and convert to timestamp format.
433	 * The year must come from the system clock.
434	 */
435
436	  time(&tloc);
437	  tadr = gmtime(&tloc);
438	  tptr->year = (unsigned short)(tadr->tm_year + 1900);
439
440	snprintf(pp->a_lastcode,
441		 sizeof(pp->a_lastcode),
442		 "%3.3d %2.2d:%2.2d:%2.2d.%.6ld %1d",
443		 tptr->day,
444		 tptr->hr,
445		 tptr->mn,
446		 tptr->sec,
447		 tptr->frac,
448		 tptr->status);
449
450	pp->lencode = (u_short) strlen(pp->a_lastcode);
451
452	pp->day =  tptr->day;
453	pp->hour =   tptr->hr;
454	pp->minute =  tptr->mn;
455	pp->second =  tptr->sec;
456	pp->nsec =   tptr->frac;
457
458#ifdef DEBUG
459	if (debug)
460	    printf("pp: %3d %02d:%02d:%02d.%06ld %1x\n",
461		   pp->day, pp->hour, pp->minute, pp->second,
462		   pp->nsec, tptr->status);
463#endif
464	if (tptr->status ) {       /*  Status 0 is locked to ref., 1 is not */
465		refclock_report(peer, CEVNT_BADREPLY);
466		return;
467	}
468
469	/*
470	 * Now, compute the reference time value. Use the heavy
471	 * machinery for the seconds and the millisecond field for the
472	 * fraction when present. If an error in conversion to internal
473	 * format is found, the program declares bad data and exits.
474	 * Note that this code does not yet know how to do the years and
475	 * relies on the clock-calendar chip for sanity.
476	 */
477	if (!refclock_process(pp)) {
478		refclock_report(peer, CEVNT_BADTIME);
479		return;
480	}
481	pp->lastref = pp->lastrec;
482	refclock_receive(peer);
483	record_clock_stats(&peer->srcadr, pp->a_lastcode);
484}
485
486struct vmedate *
487get_datumtime(struct vmedate *time_vme)
488{
489	char cbuf[7];
490	struct btfp_time vts;
491	uint32_t btm[2];
492	uint8_t dmy;
493	struct stfp_time stfpm;
494
495	if (time_vme == NULL)
496  		time_vme = emalloc(sizeof(*time_vme));
497
498	switch (tfp_type) {
499		case 1:				/* BSD, PCI, 2 32bit time words */
500			if (ioctl(fd_vme, READTIME, &btm)) {
501	    		msyslog(LOG_ERR, "get_bc63x error: %m");
502				return(NULL);
503			}
504			tvme_fill(time_vme, btm);
505			break;
506
507		case 2:				/* Linux/Windows, PCI, 2 32bit time words */
508			if (safeReadBinTime(stfp_handle, &btm[1], &btm[0], &dmy) == 0) {
509	    		msyslog(LOG_ERR, "get_datumtime error: %m");
510				return(NULL);
511			}
512			tvme_fill(time_vme, btm);
513			break;
514
515		case 3: /** solaris **/
516			memset(&stfpm,0,sizeof(stfpm));
517
518			/* we need the time in decimal format */
519			/* Here we rudely assume that we are the only user of the driver.
520			 * Other programs will have to set their own time format before reading
521			 * the time.
522			 */
523			if(ioctl (fd_vme, SELTIMEFORMAT, TIME_DECIMAL)){
524					msyslog(LOG_ERR, "Could not set time format");
525					return (NULL);
526			}
527			/* read the time */
528			if (ioctl(fd_vme, READTIME, &stfpm)) {
529				msyslog(LOG_ERR, "ioctl error: %m");
530				return(NULL);
531			}
532			stfp_time2tvme(time_vme,  &stfpm);
533			break;
534
535		default:			/* legacy bancomm card */
536
537			if (ioctl(fd_vme, READTIME, &vts)) {
538				msyslog(LOG_ERR,
539					"get_datumtime error: %m");
540				return(NULL);
541			}
542			/* Get day */
543			snprintf(cbuf, sizeof(cbuf), "%3.3x",
544				 ((vts.btfp_time[ 0 ] & 0x000f) << 8) +
545				  ((vts.btfp_time[ 1 ] & 0xff00) >> 8));
546			time_vme->day = (unsigned short)atoi(cbuf);
547
548			/* Get hour */
549			snprintf(cbuf, sizeof(cbuf), "%2.2x",
550				 vts.btfp_time[ 1 ] & 0x00ff);
551			time_vme->hr = (unsigned short)atoi(cbuf);
552
553			/* Get minutes */
554			snprintf(cbuf, sizeof(cbuf), "%2.2x",
555				 (vts.btfp_time[ 2 ] & 0xff00) >> 8);
556			time_vme->mn = (unsigned short)atoi(cbuf);
557
558			/* Get seconds */
559			snprintf(cbuf, sizeof(cbuf), "%2.2x",
560				 vts.btfp_time[ 2 ] & 0x00ff);
561			time_vme->sec = (unsigned short)atoi(cbuf);
562
563			/* Get microseconds.  Yes, we ignore the 0.1 microsecond digit so
564				 we can use the TVTOTSF function  later on...*/
565
566			snprintf(cbuf, sizeof(cbuf), "%4.4x%2.2x",
567				 vts.btfp_time[ 3 ],
568				 vts.btfp_time[ 4 ] >> 8);
569			time_vme->frac = (u_long) atoi(cbuf);
570
571			/* Get status bit */
572			time_vme->status = (vts.btfp_time[0] & 0x0010) >> 4;
573
574			break;
575	}
576
577	if (time_vme->status)
578		return ((void *)NULL);
579	else
580	    return (time_vme);
581}
582/* Assign values to time_vme struct. Mostly for readability */
583void
584tvme_fill(struct vmedate *time_vme, uint32_t btm[2])
585{
586	struct tm maj;
587	time_t   dmaj;
588	uint32_t dmin;
589
590	dmaj = btm[1];			/* syntax sugar & expansion */
591	dmin = btm[0];			/* just syntax sugar */
592
593	gmtime_r(&dmaj, &maj);
594	time_vme->day  = maj.tm_yday+1;
595	time_vme->hr   = maj.tm_hour;
596	time_vme->mn   = maj.tm_min;
597	time_vme->sec  = maj.tm_sec;
598	time_vme->frac = (dmin & 0x000fffff) * 1000;
599	time_vme->frac += ((dmin & 0x00f00000) >> 20) * 100;
600	time_vme->status = (dmin & 0x01000000) >> 24;
601	return;
602}
603
604
605/* Assign values to time_vme struct. Mostly for readability */
606void
607stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp)
608{
609
610	time_vme->day  = stfp->tm.tm_yday+1;
611	time_vme->hr   = stfp->tm.tm_hour;
612	time_vme->mn   = stfp->tm.tm_min;
613	time_vme->sec  = stfp->tm.tm_sec;
614	time_vme->frac = stfp->usec*1000;
615	time_vme->frac += stfp->hnsec * 100;
616	time_vme->status = stfp->status;
617	return;
618}
619#else
620int refclock_bancomm_bs;
621#endif /* REFCLOCK */
622