/* Shared Memory driver for PPS signals. * ===================================== * * 20 jul 2003 pport hack v0.0 gnu@wraith.sf.ca.us * v0.1 prune dead code * 21 jul 2003 v0.2 "might be a bug" fix with PPCLRIRQ * v0.3 revert fix--causes 40ms offset. * 22 jul 2003 v0.4 flap pin 14 a la freebsd * v0.5 fix poll argument * v0.6 debug, syslog instrumentation * 23 jul 2003 v0.7 getopt handling, merge parallel and serial versions * v0.8 optionize flapping of pin 14 * v0.9 multiple unit support * 30 aug 2004 v0.95SITE (RALeach) * reduced TOLERANCE considerably; * averaging of a number of timestamps to fit ntpd polling * (a crude hack); * -D(ebug) option now duplicable; * serial code tidy up. * Conditional compile of serial and parallel sections. * See DOSERIAL and DOPARALLEL. * Wed Mar 8 20:10:35 GMT 2006 (RALeach) * added precision calculation down to -19 * * Usage: * modprobe parport_pc io=0x378 irq=7 * modprobe ppdev * shm -d/dev/device -s (serial) or -p (parallel) -u (unit) * -c -l (line) -f -D * * -c sets second boundary on hi->lo transition for serial * -l line defines serial line being sampled: DCD CTS DSR * -f sets pin 14 flapping on parallel i/f * * -D debug level, may be repeated: * No. of -D * 0 normal operation: daemonize; no reporting * 1 + no daemonization; progress reports to stderr * 2 + out-of-range reports * 3 + no updating SHM * * (wire PPS to parallel pin 10, ground to 18, echo out to 14) * * * SHM driver to allow PPS time sources to work without a PPS kernel. * The PPS pin must be connected to the declared signal pin on a serial port. * This is Linux-specific code. * * * By: David J. Schwartz * * Version 1.03 * * Put into the public domain. Please keep the acknowledgement above * intact. Portions taken from the NTP source (the time_shm structure * and attach_shm code) are covered by the NTP copyright/license * * Compile with '-O2 -Os -static -march=[your_arch]' to minimize * memory footprint and maximize accuracy. * * Notes: * 1) You should have a line like the following in your /etc/ntpd.conf * server 127.127.28.0 minpoll 4 maxpoll 4 prefer * 2) Make sure no 'restrict' statement in your conf file prevents * you from trusting the clock. If in doubt, add: * restrict 127.127.28.0 * 3) You must configure NTPD with the SHM clock driver * * TODO: * 1) Average multiple PPS readings together, filtering out those that * appear to be 'late'. DONE * 2) Have two states, synchronized and unsynchronized. Start in the * unsynchronized state. If synchronized, go unsynchronized if most * readings exceed the threshold. If unsynchronized, go synchronized * if 'sysinfo' indicates a reasonable stratum and root distance. * */ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Accept a timestamp if it's within this many microseconds of the * system's second boundary, offset by the ASSUME_DELAY. * Do not exceed 250000 * * Setting this low helps avoid false lock, but requires a more accurate * starting time synchronization. Setting this high makes capture easier * but risks false lock if the clock starts too far off. */ #define TOLERANCE 50000 /* Assume it takes this long for us to get scheduled and get the time * (in microseconds). This is the offset to be applied to the range test. * The actual time offset is separately applied by the ntpd time1 fudge. */ #define ASSUME_DELAY 0 /* Accuracy is assumed to be 2^PRECISION seconds. -11 is approximately 490uS * This is just an initial estimate, it will be adjusted. */ #define PRECISION (-11) /* Number of stamps to collect before updating ntpd. minpoll s/b rather * greater. NSTAGE of 14 chimes with a poll interval of 16 s. (minpoll 4) */ #define NSTAGE 14 /* Include wanted code */ #undef DOSERIAL #undef DOPARALLEL #define DONIC int nodive = 0; /* -D option number */ struct timeval tv; struct timezone tz; struct time_shm { int shm_mode; int stamp_count; time_t clock_sec; int clock_usec; time_t receive_sec; int receive_usec; int leap_indicator; int precision; int number_of_samples; int is_valid; int place_holder[10]; }; struct time_shm *sh; struct time_shm *attach_shm(int unit) { int shm_id=0; shm_id=shmget(0x4e545030+unit, sizeof(struct time_shm), IPC_CREAT|0700); if(shm_id==-1) { if (nodive) { fprintf(stderr,"shmget failed.\n"); perror("shmget"); exit(1); } exit(1); } sh=(struct time_shm *) shmat(shm_id, 0, 0); if(sh==(void *) -1 || sh==0) { if (nodive) { fprintf(stderr,"shmat failed.\n"); perror("shmat"); exit(1); } exit(1); } if (nodive) { fprintf(stderr,"shmat ok\n"); } return sh; } /* This is our current estimate of precision. Our microseconds estimated * error is 1000000*2^(precision_est/32). */ int precision_est=(PRECISION*32); int update_offset(int offset) { static int last_reading=0; static int average_offset=0; int target_precision; /* How far is this reading from the last one? */ int diff=(last_reading>offset) ? (last_reading-offset) : (offset-last_reading); /* On average, how much are these readings varying? * In units of 16 microseconds, exponential average */ average_offset=(average_offset*15)/16 + diff; last_reading=offset; /* Adjust our precision estimate based upon how well our readings are * agreeing with each other. This will give low values when our * system clock's rate is off or due to system jitter, but that's * actually reasonable. * The magic constants are based on: * average_offset/16/1000000=2^(target_precision/32) * Or, to put it another way, our estimated precision is equal to our * average offset of sequential readings after units are converted * average_offset is in units of 16ths of a microsecond * target_precision is in units of 32nds of a power of 2 */ if(average_offset<30) target_precision=-19*32; else if(average_offset<61) target_precision=-18*32; else if(average_offset<122) target_precision=-17*32; else if(average_offset<244) target_precision=-16*32; else if(average_offset<488) target_precision=-15*32; else if(average_offset<976) target_precision=-14*32; else if(average_offset<1953) target_precision=-13*32; else if(average_offset<3906) target_precision=-12*32; else if(average_offset<7812) target_precision=-11*32; else if(average_offset<15625) target_precision=-10*32; else if(average_offset<31250) target_precision=-9*32; else target_precision=-8*32; if(precision_esttarget_precision) precision_est--; /* gain precision slow */ return average_offset; } /* * Comparison routine for quicksort */ static int TimeCompare(const void *a, const void *b) { int timea,timeb; timea = *(int *) a; timeb = *(int *) b; if (timeatimeb) return +1; else return 0; } void PutStamp(time_t cloc_sec, int cloc_usec, time_t sys_sec, int sys_usec) { /* Number of stamps to use; ~ 60% is good, according to DM. */ #define NMIDDLE (((NSTAGE*6)+5)/10) int count, sum, diff, i, j, offset; static int n = 0, err_usec = 0; static int diffs[NSTAGE]; /* Store up the diff */ diff = sys_usec - cloc_usec; if (diff > 500000) diff -= 1000000; diffs[n++] = diff; if (n < NSTAGE) return; /* Remove outliers. * * Reject the furthest from the median of the samples until * approximately 60 percent of the samples remain. */ qsort(diffs, (size_t) NSTAGE, sizeof(int), TimeCompare); i = 0; j = NSTAGE; while ((j - i) > NMIDDLE) { offset = diffs[(j + i) / 2]; if (diffs[j - 1] - offset < offset - diffs[i]) i++; /* reject low end */ else j--; /* reject high end */ } if (nodive) fprintf(stderr, "range:%d-%d ", i, j-1); /* Calculate the mean of the central portion. */ count = 0; sum = 0; for (n = i; n < j; n++) { sum += diffs[n]; count++; } err_usec = sum/count; if (err_usec < 0) { sys_sec = cloc_sec - 1; sys_usec = err_usec + 1000000; } else { sys_sec = cloc_sec; sys_usec = err_usec; } n = 0; if (nodive < 3 ) { sh->shm_mode=1; sh->leap_indicator=0; sh->is_valid=0; sh->stamp_count++; __asm__ __volatile__("": : :"memory"); sh->precision=precision_est/32; sh->clock_sec=cloc_sec; sh->clock_usec=cloc_usec; sh->receive_sec=sys_sec; sh->receive_usec=sys_usec; __asm__ __volatile__("": : :"memory"); sh->stamp_count++; sh->is_valid=1; } if (nodive) { fprintf(stderr, "sys:%d/%d ref:%d/%d prec: %d\n", (int) sys_sec, (int) sys_usec, (int) cloc_sec, (int) cloc_usec, precision_est/32); } } void frobtrans(void) { int fy; if(tv.tv_usec > (1000000 - TOLERANCE)) { fy = update_offset(tv.tv_usec-1000000); PutStamp(tv.tv_sec+1, 0, tv.tv_sec, tv.tv_usec); } else if(tv.tv_usec < (TOLERANCE)) { fy = update_offset(tv.tv_usec); PutStamp(tv.tv_sec, 0, tv.tv_sec, tv.tv_usec); } else { if (nodive > 1) { fprintf(stderr, "out of range %ld\n", tv.tv_usec); } } } extern int errno; int main(argc, argv) int argc; char *argv[]; { int i, f, f1, f2, f3, fx, whatitis; int sp_fd = 0, pp_fd = 0, n_fd=0, zoo_poll = 0, hitolow = 0, flap = 0; int deviceis_serial = 0, deviceis_parallel = 0, deviceis_nic=0, bogon = 0; int unit = 0; unsigned int pp_nfds = 1; struct pollfd pp_pollfd[2]; struct ppdev_frob_struct frobit; struct sched_param sp; struct rlimit rl; char *prog; char *pps_dev[256]; //KKP: THESE TWO WERE ARRAYS OF POINTERS TO CHARS!! char *pps_sig[256]; /* name of sig line: CD RTS DSR */ int sig = TIOCM_CD; prog = argv[0]; if (argc == 1) { bogon = 1; } while((whatitis = getopt(argc, argv,"d:Dfspncu:l:h")) != EOF) { switch(whatitis) { case 'd': (void) memcpy((char *)pps_dev, (char *)&optarg, sizeof(optarg)); break; case 'D': nodive++; break; case 'f': flap = 1; break; case 'n': deviceis_parallel = 0; deviceis_serial = 0; deviceis_nic=1; break; case 's': deviceis_serial = 1; deviceis_parallel = 0; deviceis_nic=0; break; case 'p': deviceis_parallel = 1; deviceis_serial = 0; deviceis_nic=0; break; case 'c': hitolow = 1; break; case 'u': unit = atoi(optarg); break; case 'l': (void) memcpy((char *)pps_sig, (char *)&optarg, sizeof(optarg)); break; case 'h': default: bogon = 1; break; } } if (bogon==1) { printf("usage: %s -d/DEV/DEVICE -s or -p or -n (serial or parallel or netpps) -lSIGNAME -c -D -u unit \n",prog); printf("usage for netpps: %s -dIP.ADR.OF.INTERFACE -n -u UNITNO \n",prog); exit(1); } #ifdef DOSERIAL if (memcmp(*pps_sig, "DCD", 3) == 0) sig = TIOCM_CD; else if (memcmp(*pps_sig, "CTS", 3) == 0) sig = TIOCM_CTS; else if (memcmp(*pps_sig, "DSR", 3) == 0) sig = TIOCM_DSR; else { printf("%s -lSIGNAME must be one of DCD CTS DSR: %s\n", prog, *pps_sig); exit(1); } #endif if(nodive) printf("%s device %s nodive %d flap %d serial %d parallel %d hitolow %d unit %d signal %s\n", prog, *pps_dev, nodive, flap, deviceis_serial, deviceis_parallel, hitolow, unit, *pps_sig); if (nodive==0) { if(fork()!=0) return(0); setsid(); setpgrp(); setsid(); getrlimit(RLIMIT_NOFILE, &rl); for (i = 0; i <= rl.rlim_max; i++) (void) close(i); } mlockall(MCL_FUTURE); errno = 0; setpriority(PRIO_PROCESS, getpid(), -20); if (errno) { if (nodive) { fprintf(stderr,"%s: setpriority failed.\n", prog); } } memset(&sp, 0, sizeof(sp)); sp.sched_priority=sched_get_priority_max(SCHED_FIFO); if(sched_setscheduler(0, SCHED_FIFO, &sp)!=0) { if (nodive) { fprintf(stderr,"%s: unable to set RR scheduling.\n", prog); } } #ifdef DOPARALLEL if (deviceis_parallel) { pp_fd = open (*pps_dev,O_RDWR); if (pp_fd<0) { if (nodive) { fprintf(stderr, "%s: r/w open on %s failed.\n", prog, *pps_dev); perror("open"); } exit(1); } if (ioctl (pp_fd, PPEXCL) == -1) { if (nodive) { fprintf(stderr, "%s: open exclusive on %s failed.\n", prog, *pps_dev); perror ("ioctl_ppexcl"); } close (pp_fd); exit(1); } if (ioctl (pp_fd, PPCLAIM) == -1) { if (nodive) { fprintf (stderr,"%s: open claim on %s failed.\n", prog, *pps_dev); perror ("ioctl_ppclaim"); } close (pp_fd); exit(1); } fx = IEEE1284_MODE_BYTE; if (ioctl(pp_fd, PPSETMODE, &fx) == -1) { if (nodive) { fprintf (stderr,"%s: ppsetmode on %s failed.\n",prog, *pps_dev); perror ("ioctl_ppsetmode"); } f1 = ioctl(pp_fd, PPRELEASE); close (pp_fd); exit(1); } } #endif #ifdef DOSERIAL if (deviceis_serial) { sp_fd = open(*pps_dev, O_RDWR); if (sp_fd<0) { if (nodive) { fprintf(stderr,"%s: open failed on %s.\n", prog, *pps_dev); perror("open"); } exit(1); } } #endif #ifdef DONIC if (deviceis_nic) { struct sockaddr_in addr; int nbytes,addrlen; struct ip_mreq mreq; unsigned yes=1; if ((n_fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) { perror("socket"); exit(1); } if (setsockopt(n_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) { perror("Reusing ADDR failed"); exit(1); } /* set up destination address */ memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(47392); /* bind to receive address */ if (bind(n_fd,(struct sockaddr *) &addr,sizeof(addr)) < 0) { perror("bind"); exit(1); } /* use setsockopt() to request that the kernel join a multicast group */ mreq.imr_multiaddr.s_addr=inet_addr("239.255.171.50"); printf("multicast subscribing on %s\n",*pps_dev); mreq.imr_interface.s_addr=inet_addr(*pps_dev);//htonl(INADDR_ANY); // mreq.imr_interface.s_addr=inet_addr("10.222.222.222");//htonl(INADDR_ANY); if (setsockopt(n_fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) { perror("setsockopt"); exit(1); } } #endif if(nodive < 3)attach_shm(unit); openlog(prog,0,LOG_LOCAL1); #ifdef DOSERIAL if (deviceis_serial) { while (1) { while (ioctl(sp_fd, TIOCMIWAIT, sig) != 0) ; gettimeofday(&tv, &tz); if (ioctl(sp_fd, TIOCMGET, &f) != 0) continue; if (hitolow) { if((f & sig) != 0) frobtrans(); } else { if((f & sig) == 0) frobtrans(); } } } #endif #ifdef DOPARALLEL if (deviceis_parallel) { f3 = 0; pp_pollfd[0].fd = pp_fd; pp_pollfd[0].events = POLLIN; while(1) { zoo_poll = poll(&pp_pollfd[0], pp_nfds, 1010); gettimeofday(&tv, &tz); /* grab timestamp */ /* a zero is a timeout. keep going... */ if(zoo_poll==0) continue; if(zoo_poll<0) { if (nodive) { fprintf(stderr,"zoo_poll pport failed.\n"); perror("zoo_poll"); } syslog(LOG_ERR,"zoo_poll pport failed."); exit(1); } /* this stays here or you get a 40ms offset. yikes? */ f2 = ioctl(pp_fd,PPCLRIRQ, &f3); if (f2<0) { if (nodive) { fprintf(stderr,"parallel ppclrirq failure.\n"); perror("ppclrirq"); } syslog(LOG_ERR,"parallel ppclrirq failure"); /* if we don't stop, we'll lock machine in a hard loop. */ exit(1); } if (flap) { frobit.mask = PARPORT_CONTROL_AUTOFD; frobit.val = ~ PARPORT_CONTROL_AUTOFD; if (ioctl (pp_fd, PPFCONTROL, &frobit) == -1) { if (nodive) { fprintf(stderr, "echo frob set failed.\n"); perror("ioctl_frob_set"); } } } if(pp_pollfd[0].revents && POLLIN) { frobtrans(); } if (flap) { frobit.mask = PARPORT_CONTROL_AUTOFD; frobit.val = PARPORT_CONTROL_AUTOFD; if (ioctl (pp_fd, PPFCONTROL, &frobit) == -1) { if (nodive) { fprintf(stderr, "echo frob clear failed.\n"); perror("ioctl_frob_clear"); } } } } } #endif #ifdef DONIC if (deviceis_nic) { unsigned char msgbuf[256]; unsigned nbytes,addrlen,stat,ddx; struct sockaddr_in addr; while (1) { // printf( "run \n"); tv.tv_sec=0; tv.tv_usec=0; if ((nbytes=recvfrom(n_fd,msgbuf,256,0, (struct sockaddr *) &addr,&addrlen)) < 0) { perror("recvfrom"); exit(1); } stat=ioctl(n_fd,SIOCGSTAMP,&tv); if (stat) gettimeofday(&tv, &tz); ddx=0; for (i=0; i<8; ++i) if (msgbuf[i*5]&128) ddx|=1<