/* refclock_bancomm.c - clock driver for the Datum/Bancomm bc635VME * Time and Frequency Processor. It requires the BANCOMM bc635VME/ * bc350VXI Time and Frequency Processor Module Driver for SunOS4.x * and SunOS5.x UNIX Systems. It has been tested on a UltraSparc * IIi-cEngine running Solaris 2.6. * * Author(s): Ganesh Ramasivan & Gary Cliff, Computing Devices Canada, * Ottawa, Canada * * Date: July 1999 * * Note(s): The refclock type has been defined as 16. * * This program has been modelled after the Bancomm driver * originally written by R. Schmidt of Time Service, U.S. * Naval Observatory for a HP-UX machine. Since the original * authors no longer plan to maintain this code, all * references to the HP-UX vme2 driver subsystem bave been * removed. Functions vme_report_event(), vme_receive(), * vme_control() and vme_buginfo() have been deleted because * they are no longer being used. * * 04/28/2005 Rob Neal * Modified to add support for Symmetricom bc637PCI-U Time & * Frequency Processor. * 2/21/2007 Ali Ghorashi * Modified to add support for Symmetricom bc637PCI-U Time & * Frequency Processor on Solaris. * Tested on Solaris 10 with a bc635 card. * * Card bus type (VME/VXI or PCI) and environment are specified via the * "mode" keyword on the server command in ntp.conf. * server 127.127.16.u prefer mode M * where u is the id (usually 0) of the entry in /dev (/dev/stfp0) * * and M is one of the following modes: * 1 : FreeBSD PCI 635/637. * 2 : Linux or Windows PCI 635/637. * 3 : Solaris PCI 635/637 * not specified, or other number: * : Assumed to be VME/VXI legacy Bancomm card on Solaris. * Linux and Windows platforms require Symmetricoms' proprietary driver * for the TFP card. * Solaris requires Symmetricom's driver and its header file (freely distributed) to * be installed and running. */ #ifdef HAVE_CONFIG_H #include #endif #if defined(REFCLOCK) && defined(CLOCK_BANC) #include "ntpd.h" #include "ntp_io.h" #include "ntp_refclock.h" #include "ntp_unixtime.h" #include "ntp_stdlib.h" #include #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif struct btfp_time /* Structure for reading 5 time words */ /* in one ioctl(2) operation. */ { unsigned short btfp_time[5]; /* Time words 0,1,2,3, and 4. (16bit)*/ }; /* SunOS5 ioctl commands definitions.*/ #define BTFPIOC ( 'b'<< 8 ) #define IOCIO( l, n ) ( BTFPIOC | n ) #define IOCIOR( l, n, s ) ( BTFPIOC | n ) #define IOCIORN( l, n, s ) ( BTFPIOC | n ) #define IOCIOWN( l, n, s ) ( BTFPIOC | n ) /***** Simple ioctl commands *****/ #define RUNLOCK IOCIOR(b, 19, int ) /* Release Capture Lockout */ #define RCR0 IOCIOR(b, 22, int ) /* Read control register zero.*/ #define WCR0 IOCIOWN(b, 23, int) /* Write control register zero*/ /***** Compound ioctl commands *****/ /* Read all 5 time words in one call. */ #if defined(__FreeBSD__) # define READTIME _IOR('u', 5, struct btfp_time ) #else # define READTIME IOCIORN(b, 32, sizeof( struct btfp_time )) #endif /* Solaris specific section */ struct stfp_tm { int32_t tm_sec; int32_t tm_min; int32_t tm_hour; int32_t tm_mday; int32_t tm_mon; int32_t tm_year; int32_t tm_wday; int32_t tm_yday; int32_t tm_isdst; }; struct stfp_time { struct stfp_tm tm; int32_t usec; /* usec 0 - 999999 */ int32_t hnsec; /* hnsec 0 - 9 (hundreds of nsecs) */ int32_t status; }; #define SELTIMEFORMAT 2 # define TIME_DECIMAL 0 # define TIME_BINARY 1 #if defined(__sun__) #undef READTIME #define READTIME 9 #endif /** __sun___ **/ /* end solaris specific section */ struct vmedate { /* structure returned by get_vmetime.c */ unsigned short year; unsigned short day; unsigned short hr; unsigned short mn; unsigned short sec; long frac; unsigned short status; }; typedef void *SYMMT_PCI_HANDLE; /* * VME interface parameters. */ #define VMEPRECISION (-21) /* precision assumed (1 us) */ #define USNOREFID "BTFP" /* or whatever */ #define VMEREFID "BTFP" /* reference id */ #define VMEDESCRIPTION "Bancomm bc635 TFP" /* who we are */ #define VMEHSREFID 0x7f7f1000 /* 127.127.16.00 refid hi strata */ /* clock type 16 is used here */ #define GMT 0 /* hour offset from Greenwich */ /* * Imported from ntp_timer module */ extern u_long current_time; /* current time(s) */ /* * VME unit control structure. * Changes made to vmeunit structure. Most members are now available in the * new refclockproc structure in ntp_refclock.h - 07/99 - Ganesh Ramasivan */ struct vmeunit { struct vmedate vmedata; /* data returned from vme read */ u_long lasttime; /* last time clock heard from */ }; /* * Function prototypes */ static int vme_start (int, struct peer *); static void vme_shutdown (int, struct peer *); static void vme_receive (struct recvbuf *); static void vme_poll (int unit, struct peer *); struct vmedate *get_datumtime(struct vmedate *); void tvme_fill(struct vmedate *, uint32_t btm[2]); void stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp); static const char *get_devicename(int n); /* [Bug 3558] and [Bug 1674] perlinger@ntp.org says: * * bcReadBinTime() is defined to use two DWORD pointers on Windows and * Linux in the BANCOMM SDK. DWORD is of course Windows-specific * (*shudder*), and it is defined as 'unsigned long' under * Linux/Unix. (*sigh*) * * This creates quite some headache. The size of 'unsigned long' is * platform/compiler/memory-model dependent (LP32 vs LP64 vs LLP64), * while the card itself always creates 32bit time stamps. What a * bummer. And DWORD has tendency to contain 64bit on Win64 (which is * why we have a DWORD32 defined on Win64) so it can be used as * substitute for 'UINT_PTR' in Windows API headers. I won't even try * to comment on that, because anything I have to say will not be civil. * * We work around this by possibly using a wrapper function that makes * the necessary conversions/casts. It might be a bit tricky to * maintain the conditional logic below, but any lingering disease needs * constant care to avoid a breakout. */ #if defined(__linux__) typedef unsigned long bcBinTimeT; # if SIZEOF_LONG == 4 # define safeReadBinTime bcReadBinTime # endif #elif defined(SYS_WINNT) typedef DWORD bcBinTimeT; # if !defined(_WIN64) || _WIN64 == 0 # define safeReadBinTime bcReadBinTime # endif #else typedef uint32_t bcBinTimeT; # define safeReadBinTime bcReadBinTime #endif /* * Define the bc*() functions as weak so we can compile/link without them. * Only clients with the card will have the proprietary vendor device driver * and interface library needed for use on Linux/Windows platforms. */ extern uint32_t __attribute__ ((weak)) bcReadBinTime(SYMMT_PCI_HANDLE, bcBinTimeT*, bcBinTimeT*, uint8_t*); extern SYMMT_PCI_HANDLE __attribute__ ((weak)) bcStartPci(void); extern void __attribute__ ((weak)) bcStopPci(SYMMT_PCI_HANDLE); /* This is the conversion wrapper for the long/DWORD/uint32_t clash in * reading binary times. */ #ifndef safeReadBinTime static uint32_t safeReadBinTime( SYMMT_PCI_HANDLE hnd, uint32_t *pt1, uint32_t *pt2, uint8_t *p3 ) { bcBinTimeT t1, t2; uint32_t rc; rc = bcReadBinTime(hnd, &t1, &t2, p3); if (rc != 0) { *pt1 = (uint32_t)t1; *pt2 = (uint32_t)t2; } return rc; } #endif /* !defined(safeReadBinTime) */ /* * Transfer vector */ struct refclock refclock_bancomm = { vme_start, /* start up driver */ vme_shutdown, /* shut down driver */ vme_poll, /* transmit poll message */ noentry, /* not used (old vme_control) */ noentry, /* initialize driver */ noentry, /* not used (old vme_buginfo) */ NOFLAGS /* not used */ }; int fd_vme; /* file descriptor for ioctls */ int regvalue; int tfp_type; /* mode selector, indicate platform and driver interface */ SYMMT_PCI_HANDLE stfp_handle; /* This helper function returns the device name based on the platform we * are running on and the device number. * * Uses a static buffer, so the result is valid only to the next call of * this function! */ static const char* get_devicename(int n) { # if defined(__sun__) static const char * const template ="/dev/stfp%d"; # else static const char * const template ="/dev/btfp%d"; # endif static char namebuf[20]; snprintf(namebuf, sizeof(namebuf), template, n); namebuf[sizeof(namebuf)-1] = '\0'; /* paranoia rulez! */ return namebuf; } /* * vme_start - open the VME device and initialize data for processing */ static int vme_start( int unit, struct peer *peer ) { register struct vmeunit *vme; struct refclockproc *pp; int dummy; char vmedev[20]; tfp_type = (int)(peer->ttl); switch (tfp_type) { case 1: case 3: break; case 2: stfp_handle = bcStartPci(); /* init the card in lin/win */ break; default: break; } /* * Open VME device */ #ifdef DEBUG printf("Opening DATUM DEVICE %s\n",get_devicename(peer->refclkunit)); #endif if ( (fd_vme = open(get_devicename(peer->refclkunit), O_RDWR)) < 0) { msyslog(LOG_ERR, "vme_start: failed open of %s: %m", vmedev); return (0); } else { switch (tfp_type) { case 1: break; case 2: break; case 3:break; default: /* Release capture lockout in case it was set before. */ if( ioctl( fd_vme, RUNLOCK, &dummy ) ) msyslog(LOG_ERR, "vme_start: RUNLOCK failed %m"); regvalue = 0; /* More esoteric stuff to do... */ if( ioctl( fd_vme, WCR0, ®value ) ) msyslog(LOG_ERR, "vme_start: WCR0 failed %m"); break; } } /* * Allocate unit structure */ vme = emalloc_zero(sizeof(struct vmeunit)); /* * Set up the structures */ pp = peer->procptr; pp->unitptr = vme; pp->timestarted = current_time; pp->io.clock_recv = vme_receive; pp->io.srcclock = peer; pp->io.datalen = 0; pp->io.fd = fd_vme; /* shouldn't there be an io_addclock() call? */ /* * All done. Initialize a few random peer variables, then * return success. Note that root delay and root dispersion are * always zero for this clock. */ peer->precision = VMEPRECISION; memcpy(&pp->refid, USNOREFID,4); return (1); } /* * vme_shutdown - shut down a VME clock */ static void vme_shutdown( int unit, struct peer *peer ) { register struct vmeunit *vme; struct refclockproc *pp; /* * Tell the I/O module to turn us off. We're history. */ pp = peer->procptr; vme = pp->unitptr; io_closeclock(&pp->io); pp->unitptr = NULL; if (NULL != vme) free(vme); if (tfp_type == 2) bcStopPci(stfp_handle); } /* * vme_receive - receive data from the VME device. * * Note: This interface would be interrupt-driven. We don't use that * now, but include a dummy routine for possible future adventures. */ static void vme_receive( struct recvbuf *rbufp ) { } /* * vme_poll - called by the transmit procedure */ static void vme_poll( int unit, struct peer *peer ) { struct vmedate *tptr; struct vmeunit *vme; struct refclockproc *pp; time_t tloc; struct tm *tadr; pp = peer->procptr; vme = pp->unitptr; /* Here is the structure */ tptr = &vme->vmedata; if ((tptr = get_datumtime(tptr)) == NULL ) { refclock_report(peer, CEVNT_BADREPLY); return; } get_systime(&pp->lastrec); pp->polls++; vme->lasttime = current_time; /* * Get VME time and convert to timestamp format. * The year must come from the system clock. */ time(&tloc); tadr = gmtime(&tloc); tptr->year = (unsigned short)(tadr->tm_year + 1900); snprintf(pp->a_lastcode, sizeof(pp->a_lastcode), "%3.3d %2.2d:%2.2d:%2.2d.%.6ld %1d", tptr->day, tptr->hr, tptr->mn, tptr->sec, tptr->frac, tptr->status); pp->lencode = (u_short) strlen(pp->a_lastcode); pp->day = tptr->day; pp->hour = tptr->hr; pp->minute = tptr->mn; pp->second = tptr->sec; pp->nsec = tptr->frac; #ifdef DEBUG if (debug) printf("pp: %3d %02d:%02d:%02d.%06ld %1x\n", pp->day, pp->hour, pp->minute, pp->second, pp->nsec, tptr->status); #endif if (tptr->status ) { /* Status 0 is locked to ref., 1 is not */ refclock_report(peer, CEVNT_BADREPLY); return; } /* * Now, compute the reference time value. Use the heavy * machinery for the seconds and the millisecond field for the * fraction when present. If an error in conversion to internal * format is found, the program declares bad data and exits. * Note that this code does not yet know how to do the years and * relies on the clock-calendar chip for sanity. */ if (!refclock_process(pp)) { refclock_report(peer, CEVNT_BADTIME); return; } pp->lastref = pp->lastrec; refclock_receive(peer); record_clock_stats(&peer->srcadr, pp->a_lastcode); } struct vmedate * get_datumtime(struct vmedate *time_vme) { char cbuf[7]; struct btfp_time vts; uint32_t btm[2]; uint8_t dmy; struct stfp_time stfpm; if (time_vme == NULL) time_vme = emalloc(sizeof(*time_vme)); switch (tfp_type) { case 1: /* BSD, PCI, 2 32bit time words */ if (ioctl(fd_vme, READTIME, &btm)) { msyslog(LOG_ERR, "get_bc63x error: %m"); return(NULL); } tvme_fill(time_vme, btm); break; case 2: /* Linux/Windows, PCI, 2 32bit time words */ if (safeReadBinTime(stfp_handle, &btm[1], &btm[0], &dmy) == 0) { msyslog(LOG_ERR, "get_datumtime error: %m"); return(NULL); } tvme_fill(time_vme, btm); break; case 3: /** solaris **/ memset(&stfpm,0,sizeof(stfpm)); /* we need the time in decimal format */ /* Here we rudely assume that we are the only user of the driver. * Other programs will have to set their own time format before reading * the time. */ if(ioctl (fd_vme, SELTIMEFORMAT, TIME_DECIMAL)){ msyslog(LOG_ERR, "Could not set time format"); return (NULL); } /* read the time */ if (ioctl(fd_vme, READTIME, &stfpm)) { msyslog(LOG_ERR, "ioctl error: %m"); return(NULL); } stfp_time2tvme(time_vme, &stfpm); break; default: /* legacy bancomm card */ if (ioctl(fd_vme, READTIME, &vts)) { msyslog(LOG_ERR, "get_datumtime error: %m"); return(NULL); } /* Get day */ snprintf(cbuf, sizeof(cbuf), "%3.3x", ((vts.btfp_time[ 0 ] & 0x000f) << 8) + ((vts.btfp_time[ 1 ] & 0xff00) >> 8)); time_vme->day = (unsigned short)atoi(cbuf); /* Get hour */ snprintf(cbuf, sizeof(cbuf), "%2.2x", vts.btfp_time[ 1 ] & 0x00ff); time_vme->hr = (unsigned short)atoi(cbuf); /* Get minutes */ snprintf(cbuf, sizeof(cbuf), "%2.2x", (vts.btfp_time[ 2 ] & 0xff00) >> 8); time_vme->mn = (unsigned short)atoi(cbuf); /* Get seconds */ snprintf(cbuf, sizeof(cbuf), "%2.2x", vts.btfp_time[ 2 ] & 0x00ff); time_vme->sec = (unsigned short)atoi(cbuf); /* Get microseconds. Yes, we ignore the 0.1 microsecond digit so we can use the TVTOTSF function later on...*/ snprintf(cbuf, sizeof(cbuf), "%4.4x%2.2x", vts.btfp_time[ 3 ], vts.btfp_time[ 4 ] >> 8); time_vme->frac = (u_long) atoi(cbuf); /* Get status bit */ time_vme->status = (vts.btfp_time[0] & 0x0010) >> 4; break; } if (time_vme->status) return ((void *)NULL); else return (time_vme); } /* Assign values to time_vme struct. Mostly for readability */ void tvme_fill(struct vmedate *time_vme, uint32_t btm[2]) { struct tm maj; time_t dmaj; uint32_t dmin; dmaj = btm[1]; /* syntax sugar & expansion */ dmin = btm[0]; /* just syntax sugar */ gmtime_r(&dmaj, &maj); time_vme->day = maj.tm_yday+1; time_vme->hr = maj.tm_hour; time_vme->mn = maj.tm_min; time_vme->sec = maj.tm_sec; time_vme->frac = (dmin & 0x000fffff) * 1000; time_vme->frac += ((dmin & 0x00f00000) >> 20) * 100; time_vme->status = (dmin & 0x01000000) >> 24; return; } /* Assign values to time_vme struct. Mostly for readability */ void stfp_time2tvme(struct vmedate *time_vme, struct stfp_time *stfp) { time_vme->day = stfp->tm.tm_yday+1; time_vme->hr = stfp->tm.tm_hour; time_vme->mn = stfp->tm.tm_min; time_vme->sec = stfp->tm.tm_sec; time_vme->frac = stfp->usec*1000; time_vme->frac += stfp->hnsec * 100; time_vme->status = stfp->status; return; } #else NONEMPTY_TRANSLATION_UNIT #endif /* REFCLOCK */