1 /* $NetBSD: refclock_bancomm.c,v 1.5 2020/05/25 20:47:25 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@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
safeReadBinTime(SYMMT_PCI_HANDLE hnd,uint32_t * pt1,uint32_t * pt2,uint8_t * p3)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*
get_devicename(int n)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
vme_start(int unit,struct peer * peer)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, ®value ) )
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
vme_shutdown(int unit,struct peer * peer)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
vme_receive(struct recvbuf * rbufp)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
vme_poll(int unit,struct peer * peer)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 *
get_datumtime(struct vmedate * time_vme)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
tvme_fill(struct vmedate * time_vme,uint32_t btm[2])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
stfp_time2tvme(struct vmedate * time_vme,struct stfp_time * stfp)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 int refclock_bancomm_bs;
623 #endif /* REFCLOCK */
624