xref: /freebsd/contrib/ntp/ntpd/refclock_bancomm.c (revision e9ac41698b2f322d55ccf9da50a3596edb2c1800)
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 
67 struct 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 */
93 struct	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 
105 struct 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 
122 struct 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 
132 typedef 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  */
148 extern 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  */
155 struct 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  */
163 static  int     vme_start       (int, struct peer *);
164 static  void    vme_shutdown    (int, struct peer *);
165 static  void    vme_receive     (struct recvbuf *);
166 static  void    vme_poll        (int unit, struct peer *);
167 struct vmedate *get_datumtime(struct vmedate *);
168 void	tvme_fill(struct vmedate *, uint32_t btm[2]);
169 void	stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp);
170 static 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  */
212 extern uint32_t __attribute__ ((weak)) bcReadBinTime(SYMMT_PCI_HANDLE, bcBinTimeT*, bcBinTimeT*, uint8_t*);
213 extern SYMMT_PCI_HANDLE __attribute__ ((weak)) bcStartPci(void);
214 extern 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
220 static uint32_t
221 safeReadBinTime(
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  */
243 struct  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 
253 int fd_vme;  /* file descriptor for ioctls */
254 int regvalue;
255 int tfp_type;	/* mode selector, indicate platform and driver interface */
256 SYMMT_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  */
264 static const char*
265 get_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  */
283 static int
284 vme_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  */
366 static void
367 vme_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  */
395 static void
396 vme_receive(
397 	struct recvbuf *rbufp
398 	)
399 {
400 }
401 
402 
403 /*
404  * vme_poll - called by the transmit procedure
405  */
406 static void
407 vme_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 
486 struct vmedate *
487 get_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 */
583 void
584 tvme_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 */
606 void
607 stfp_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
620 NONEMPTY_TRANSLATION_UNIT
621 #endif /* REFCLOCK */
622