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
64struct btfp_time                /* Structure for reading 5 time words   */
65                                /* in one ioctl(2) operation.           */
66{
67	unsigned short btfp_time[5];  /* Time words 0,1,2,3, and 4. (16bit)*/
68};
69/* SunOS5 ioctl commands definitions.*/
70#define BTFPIOC            ( 'b'<< 8 )
71#define IOCIO( l, n )      ( BTFPIOC | n )
72#define IOCIOR( l, n, s )  ( BTFPIOC | n )
73#define IOCIORN( l, n, s ) ( BTFPIOC | n )
74#define IOCIOWN( l, n, s ) ( BTFPIOC | n )
75
76/***** Simple ioctl commands *****/
77#define RUNLOCK     	IOCIOR(b, 19, int )  /* Release Capture Lockout */
78#define RCR0      	IOCIOR(b, 22, int )  /* Read control register zero.*/
79#define	WCR0		IOCIOWN(b, 23, int)	     /* Write control register zero*/
80/***** Compound ioctl commands *****/
81
82/* Read all 5 time words in one call.   */
83#define READTIME	IOCIORN(b, 32, sizeof( struct btfp_time ))
84
85#if defined(__FreeBSD__)
86#undef  READTIME
87#define READTIME	_IOR('u', 5, struct btfp_time )
88#endif
89
90/* Solaris specific section */
91struct	stfp_tm {
92	int32_t tm_sec;
93	int32_t tm_min;
94	int32_t tm_hour;
95	int32_t tm_mday;
96	int32_t tm_mon;
97	int32_t tm_year;
98	int32_t tm_wday;
99	int32_t tm_yday;
100	int32_t tm_isdst;
101};
102
103struct stfp_time {
104	struct stfp_tm	tm;
105	int32_t 	usec;			/* usec 0 - 999999 */
106	int32_t 	hnsec;			/* hnsec 0 - 9 (hundreds of nsecs) */
107	int32_t 	status;
108};
109
110#define SELTIMEFORMAT	2
111#	define TIME_DECIMAL 0
112#	define TIME_BINARY	1
113
114#if defined(__sun__)
115#undef	READTIME
116#define READTIME		9
117#endif /** __sun___ **/
118/* end solaris specific section */
119
120struct vmedate {			   /* structure returned by get_vmetime.c */
121	unsigned short year;
122	unsigned short day;
123	unsigned short hr;
124	unsigned short mn;
125	unsigned short sec;
126	long frac;
127	unsigned short status;
128};
129
130typedef void *SYMMT_PCI_HANDLE;
131
132/*
133 * VME interface parameters.
134 */
135#define VMEPRECISION    (-21)   /* precision assumed (1 us) */
136#define USNOREFID       "BTFP"  /* or whatever */
137#define VMEREFID        "BTFP"  /* reference id */
138#define VMEDESCRIPTION  "Bancomm bc635 TFP" /* who we are */
139#define VMEHSREFID      0x7f7f1000 /* 127.127.16.00 refid hi strata */
140/* clock type 16 is used here  */
141#define GMT           	0       /* hour offset from Greenwich */
142
143/*
144 * Imported from ntp_timer module
145 */
146extern u_long current_time;     /* current time(s) */
147
148/*
149 * VME unit control structure.
150 * Changes made to vmeunit structure. Most members are now available in the
151 * new refclockproc structure in ntp_refclock.h - 07/99 - Ganesh Ramasivan
152 */
153struct vmeunit {
154	struct vmedate vmedata; /* data returned from vme read */
155	u_long lasttime;        /* last time clock heard from */
156};
157
158/*
159 * Function prototypes
160 */
161static  int     vme_start       (int, struct peer *);
162static  void    vme_shutdown    (int, struct peer *);
163static  void    vme_receive     (struct recvbuf *);
164static  void    vme_poll        (int unit, struct peer *);
165struct vmedate *get_datumtime(struct vmedate *);
166void	tvme_fill(struct vmedate *, uint32_t btm[2]);
167void	stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp);
168inline const char *DEVICE_NAME(int n);
169
170
171/*
172 * Define the bc*() functions as weak so we can compile/link without them.
173 * Only clients with the card will have the proprietary vendor device driver
174 * and interface library needed for use on Linux/Windows platforms.
175 */
176extern uint32_t __attribute__ ((weak)) bcReadBinTime(SYMMT_PCI_HANDLE, uint32_t *, uint32_t*, uint8_t*);
177extern SYMMT_PCI_HANDLE __attribute__ ((weak)) bcStartPci(void);
178extern void __attribute__ ((weak)) bcStopPci(SYMMT_PCI_HANDLE);
179
180/*
181 * Transfer vector
182 */
183struct  refclock refclock_bancomm = {
184	vme_start, 		/* start up driver */
185	vme_shutdown,		/* shut down driver */
186	vme_poll,		/* transmit poll message */
187	noentry,		/* not used (old vme_control) */
188	noentry,		/* initialize driver */
189	noentry,		/* not used (old vme_buginfo) */
190	NOFLAGS			/* not used */
191};
192
193int fd_vme;  /* file descriptor for ioctls */
194int regvalue;
195int tfp_type;	/* mode selector, indicate platform and driver interface */
196SYMMT_PCI_HANDLE stfp_handle;
197
198/**
199 * this macro returns the device name based on
200 * the platform we are running on and the device number
201 */
202#if defined(__sun__)
203inline const char *DEVICE_NAME(int n) {static char s[20]={0}; snprintf(s,19,"/dev/stfp%d",n);return s;}
204#else
205inline const char* DEVICE_NAME(int n) {static char s[20]={0}; snprintf(s,19,"/dev/btfp%d",n);return s;}
206#endif /**__sun__**/
207
208/*
209 * vme_start - open the VME device and initialize data for processing
210 */
211static int
212vme_start(
213	int unit,
214	struct peer *peer
215	)
216{
217	register struct vmeunit *vme;
218	struct refclockproc *pp;
219	int dummy;
220	char vmedev[20];
221
222	tfp_type = (int)(peer->ttl);
223	switch (tfp_type) {
224		case 1:
225		case 3:
226			break;
227		case 2:
228			stfp_handle = bcStartPci(); 	/* init the card in lin/win */
229			break;
230		default:
231			break;
232	}
233	/*
234	 * Open VME device
235	 */
236#ifdef DEBUG
237
238	printf("Opening DATUM DEVICE %s\n",DEVICE_NAME(peer->refclkunit));
239#endif
240	if ( (fd_vme = open(DEVICE_NAME(peer->refclkunit), O_RDWR)) < 0) {
241		msyslog(LOG_ERR, "vme_start: failed open of %s: %m", vmedev);
242		return (0);
243	}
244	else  {
245		switch (tfp_type) {
246		  	case 1:	break;
247			case 2: break;
248			case 3:break;
249			default:
250				/* Release capture lockout in case it was set before. */
251				if( ioctl( fd_vme, RUNLOCK, &dummy ) )
252		    		msyslog(LOG_ERR, "vme_start: RUNLOCK failed %m");
253
254				regvalue = 0; /* More esoteric stuff to do... */
255				if( ioctl( fd_vme, WCR0, &regvalue ) )
256		    		msyslog(LOG_ERR, "vme_start: WCR0 failed %m");
257				break;
258		}
259	}
260
261	/*
262	 * Allocate unit structure
263	 */
264	vme = emalloc_zero(sizeof(struct vmeunit));
265
266
267	/*
268	 * Set up the structures
269	 */
270	pp = peer->procptr;
271	pp->unitptr = vme;
272	pp->timestarted = current_time;
273
274	pp->io.clock_recv = vme_receive;
275	pp->io.srcclock = peer;
276	pp->io.datalen = 0;
277	pp->io.fd = fd_vme;
278	/* shouldn't there be an io_addclock() call? */
279
280	/*
281	 * All done.  Initialize a few random peer variables, then
282 	 * return success. Note that root delay and root dispersion are
283	 * always zero for this clock.
284	 */
285	peer->precision = VMEPRECISION;
286	memcpy(&pp->refid, USNOREFID,4);
287	return (1);
288}
289
290
291/*
292 * vme_shutdown - shut down a VME clock
293 */
294static void
295vme_shutdown(
296	int unit,
297	struct peer *peer
298	)
299{
300	register struct vmeunit *vme;
301	struct refclockproc *pp;
302
303	/*
304	 * Tell the I/O module to turn us off.  We're history.
305	 */
306	pp = peer->procptr;
307	vme = pp->unitptr;
308	io_closeclock(&pp->io);
309	pp->unitptr = NULL;
310	if (NULL != vme)
311		free(vme);
312	if (tfp_type == 2)
313		bcStopPci(stfp_handle);
314}
315
316
317/*
318 * vme_receive - receive data from the VME device.
319 *
320 * Note: This interface would be interrupt-driven. We don't use that
321 * now, but include a dummy routine for possible future adventures.
322 */
323static void
324vme_receive(
325	struct recvbuf *rbufp
326	)
327{
328}
329
330
331/*
332 * vme_poll - called by the transmit procedure
333 */
334static void
335vme_poll(
336	int unit,
337	struct peer *peer
338	)
339{
340	struct vmedate *tptr;
341	struct vmeunit *vme;
342	struct refclockproc *pp;
343	time_t tloc;
344	struct tm *tadr;
345
346	pp = peer->procptr;
347	vme = pp->unitptr;        /* Here is the structure */
348
349	tptr = &vme->vmedata;
350	if ((tptr = get_datumtime(tptr)) == NULL ) {
351		refclock_report(peer, CEVNT_BADREPLY);
352		return;
353	}
354
355	get_systime(&pp->lastrec);
356	pp->polls++;
357	vme->lasttime = current_time;
358
359	/*
360	 * Get VME time and convert to timestamp format.
361	 * The year must come from the system clock.
362	 */
363
364	  time(&tloc);
365	  tadr = gmtime(&tloc);
366	  tptr->year = (unsigned short)(tadr->tm_year + 1900);
367
368	snprintf(pp->a_lastcode,
369		 sizeof(pp->a_lastcode),
370		 "%3.3d %2.2d:%2.2d:%2.2d.%.6ld %1d",
371		 tptr->day,
372		 tptr->hr,
373		 tptr->mn,
374		 tptr->sec,
375		 tptr->frac,
376		 tptr->status);
377
378	pp->lencode = (u_short) strlen(pp->a_lastcode);
379
380	pp->day =  tptr->day;
381	pp->hour =   tptr->hr;
382	pp->minute =  tptr->mn;
383	pp->second =  tptr->sec;
384	pp->nsec =   tptr->frac;
385
386#ifdef DEBUG
387	if (debug)
388	    printf("pp: %3d %02d:%02d:%02d.%06ld %1x\n",
389		   pp->day, pp->hour, pp->minute, pp->second,
390		   pp->nsec, tptr->status);
391#endif
392	if (tptr->status ) {       /*  Status 0 is locked to ref., 1 is not */
393		refclock_report(peer, CEVNT_BADREPLY);
394		return;
395	}
396
397	/*
398	 * Now, compute the reference time value. Use the heavy
399	 * machinery for the seconds and the millisecond field for the
400	 * fraction when present. If an error in conversion to internal
401	 * format is found, the program declares bad data and exits.
402	 * Note that this code does not yet know how to do the years and
403	 * relies on the clock-calendar chip for sanity.
404	 */
405	if (!refclock_process(pp)) {
406		refclock_report(peer, CEVNT_BADTIME);
407		return;
408	}
409	pp->lastref = pp->lastrec;
410	refclock_receive(peer);
411	record_clock_stats(&peer->srcadr, pp->a_lastcode);
412}
413
414struct vmedate *
415get_datumtime(struct vmedate *time_vme)
416{
417	char cbuf[7];
418	struct btfp_time vts;
419	uint32_t btm[2];
420	uint8_t dmy;
421	struct stfp_time stfpm;
422
423	if (time_vme == NULL)
424  		time_vme = emalloc(sizeof(*time_vme));
425
426	switch (tfp_type) {
427		case 1:				/* BSD, PCI, 2 32bit time words */
428			if (ioctl(fd_vme, READTIME, &btm)) {
429	    		msyslog(LOG_ERR, "get_bc63x error: %m");
430				return(NULL);
431			}
432			tvme_fill(time_vme, btm);
433			break;
434
435		case 2:				/* Linux/Windows, PCI, 2 32bit time words */
436			if (bcReadBinTime(stfp_handle, &btm[1], &btm[0], &dmy) == 0) {
437	    		msyslog(LOG_ERR, "get_datumtime error: %m");
438				return(NULL);
439			}
440			tvme_fill(time_vme, btm);
441			break;
442
443		case 3: /** solaris **/
444			memset(&stfpm,0,sizeof(stfpm));
445
446			/* we need the time in decimal format */
447			/* Here we rudely assume that we are the only user of the driver.
448			 * Other programs will have to set their own time format before reading
449			 * the time.
450			 */
451			if(ioctl (fd_vme, SELTIMEFORMAT, TIME_DECIMAL)){
452					msyslog(LOG_ERR, "Could not set time format");
453					return (NULL);
454			}
455			/* read the time */
456			if (ioctl(fd_vme, READTIME, &stfpm)) {
457				msyslog(LOG_ERR, "ioctl error: %m");
458				return(NULL);
459			}
460			stfp_time2tvme(time_vme,  &stfpm);
461			break;
462
463		default:			/* legacy bancomm card */
464
465			if (ioctl(fd_vme, READTIME, &vts)) {
466				msyslog(LOG_ERR,
467					"get_datumtime error: %m");
468				return(NULL);
469			}
470			/* Get day */
471			snprintf(cbuf, sizeof(cbuf), "%3.3x",
472				 ((vts.btfp_time[ 0 ] & 0x000f) << 8) +
473				  ((vts.btfp_time[ 1 ] & 0xff00) >> 8));
474			time_vme->day = (unsigned short)atoi(cbuf);
475
476			/* Get hour */
477			snprintf(cbuf, sizeof(cbuf), "%2.2x",
478				 vts.btfp_time[ 1 ] & 0x00ff);
479			time_vme->hr = (unsigned short)atoi(cbuf);
480
481			/* Get minutes */
482			snprintf(cbuf, sizeof(cbuf), "%2.2x",
483				 (vts.btfp_time[ 2 ] & 0xff00) >> 8);
484			time_vme->mn = (unsigned short)atoi(cbuf);
485
486			/* Get seconds */
487			snprintf(cbuf, sizeof(cbuf), "%2.2x",
488				 vts.btfp_time[ 2 ] & 0x00ff);
489			time_vme->sec = (unsigned short)atoi(cbuf);
490
491			/* Get microseconds.  Yes, we ignore the 0.1 microsecond digit so
492				 we can use the TVTOTSF function  later on...*/
493
494			snprintf(cbuf, sizeof(cbuf), "%4.4x%2.2x",
495				 vts.btfp_time[ 3 ],
496				 vts.btfp_time[ 4 ] >> 8);
497			time_vme->frac = (u_long) atoi(cbuf);
498
499			/* Get status bit */
500			time_vme->status = (vts.btfp_time[0] & 0x0010) >> 4;
501
502			break;
503	}
504
505	if (time_vme->status)
506		return ((void *)NULL);
507	else
508	    return (time_vme);
509}
510/* Assign values to time_vme struct. Mostly for readability */
511void
512tvme_fill(struct vmedate *time_vme, uint32_t btm[2])
513{
514	struct tm maj;
515	uint32_t dmaj, dmin;
516
517	dmaj = btm[1];			/* syntax sugar */
518	dmin = btm[0];
519
520	gmtime_r(&dmaj, &maj);
521	time_vme->day  = maj.tm_yday+1;
522	time_vme->hr   = maj.tm_hour;
523	time_vme->mn   = maj.tm_min;
524	time_vme->sec  = maj.tm_sec;
525	time_vme->frac = (dmin & 0x000fffff) * 1000;
526	time_vme->frac += ((dmin & 0x00f00000) >> 20) * 100;
527	time_vme->status = (dmin & 0x01000000) >> 24;
528	return;
529}
530
531
532/* Assign values to time_vme struct. Mostly for readability */
533void
534stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp)
535{
536
537	time_vme->day  = stfp->tm.tm_yday+1;
538	time_vme->hr   = stfp->tm.tm_hour;
539	time_vme->mn   = stfp->tm.tm_min;
540	time_vme->sec  = stfp->tm.tm_sec;
541	time_vme->frac = stfp->usec*1000;
542	time_vme->frac += stfp->hnsec * 100;
543	time_vme->status = stfp->status;
544	return;
545}
546#else
547int refclock_bancomm_bs;
548#endif /* REFCLOCK */
549