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