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, ®value ) ) 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