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
safeReadBinTime(SYMMT_PCI_HANDLE hnd,uint32_t * pt1,uint32_t * pt2,uint8_t * p3)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*
get_devicename(int n)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
vme_start(int unit,struct peer * peer)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
vme_shutdown(int unit,struct peer * peer)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
vme_receive(struct recvbuf * rbufp)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
vme_poll(int unit,struct peer * peer)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 *
get_datumtime(struct vmedate * time_vme)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
tvme_fill(struct vmedate * time_vme,uint32_t btm[2])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
stfp_time2tvme(struct vmedate * time_vme,struct stfp_time * stfp)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