/* * refclock_leitch.c - clock driver for Leitch 5300 Clock System Driver * Ralph Siemsen Nov 16, 2001 * based heavily on refclock_nmea.c * * Leitch only provides two digits of year information, so this driver * currently assumes that the year is between 2000 and 2099. */ #ifdef HAVE_CONFIG_H #include #endif #if defined(REFCLOCK) && defined(CLOCK_LEITCH) #include "ntpd.h" #include "ntp_io.h" #include "ntp_unixtime.h" #include "ntp_refclock.h" #include "ntp_stdlib.h" #include #include #ifdef HAVE_PPSAPI # ifdef HAVE_TIMEPPS_H # include # else # ifdef HAVE_SYS_TIMEPPS_H # include # endif # endif #endif /* HAVE_PPSAPI */ /* * This driver supports the Leitch 5300 Clock System Driver * It is a total rewrite of the existing driver. This one is * based heavily on refclock_nmea.c. * * COMMANDS: * DATE: D * TIME: T * STATUS: S * LOOP: L * * FORMAT: * DATE: YYMMDD * TIME: /HHMMSS /HHMMSS /HHMMSS / * second bondaried on the stop bit of the * second boundaries at '/' above. * STATUS: G (good), D (diag fail), T (time not provided) or * P (last phone update failed) */ /* * Definitions */ # define DEVICE "/dev/leitch%d" /* name of radio device */ #define SPEED232 B300 /* uart speed (300 bps) */ #define PRECISION (-9) /* precision assumed (about 2 ms) */ #define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ #define REFID "ATOM" /* reference id */ #define DESCRIPTION "Leitch 5300 Clock System Driver" /* who we are */ /* * Tables to compute the ddd of year form icky dd/mm timecode. Viva la * leap. */ static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /* * Unit control structure */ struct leitchunit { short state; /* serial state machine counter */ l_fp tstamp; /* timestamp of last poll */ u_short year, yearday; /* date from last leitch poll */ u_short hour, minute, second; /* time form last leitch poll */ #ifdef HAVE_PPSAPI struct timespec ts; /* last timestamp */ pps_params_t pps_params; /* pps parameters */ pps_info_t pps_info; /* last pps data */ pps_handle_t handle; /* pps handlebars */ #endif /* HAVE_PPSAPI */ }; /* * Possible states for leitchunit->state */ #define LEITCH_STATE_IDLE 0 #define LEITCH_STATE_DATE 1 #define LEITCH_STATE_TIME1 2 #define LEITCH_STATE_TIME2 3 #define LEITCH_STATE_TIME3 4 /* * Function prototypes */ static int leitch_start P((int, struct peer *)); static void leitch_shutdown P((int, struct peer *)); #ifdef HAVE_PPSAPI static void leitch_control P((int, struct refclockstat *, struct refclockstat *, struct peer *)); static int leitch_ppsapi P((struct peer *, int, int)); static int leitch_pps P((struct leitchunit *, l_fp *)); #endif /* HAVE_PPSAPI */ static void leitch_receive P((struct recvbuf *)); static int leitch_get_date P((char *, struct peer *)); static int leitch_get_time P((char *, struct peer *)); static void leitch_process P((struct peer *, char *, int)); static void leitch_poll P((int, struct peer *)); static void leitch_send P((int, const char *, struct peer *)); /* * Transfer vector */ struct refclock refclock_leitch = { leitch_start, /* start up driver */ leitch_shutdown, /* shut down driver */ leitch_poll, /* transmit poll message */ #ifdef HAVE_PPSAPI leitch_control, /* fudge control */ #else noentry, /* fudge control */ #endif /* HAVE_PPSAPI */ noentry, /* initialize driver */ noentry, /* buginfo */ NOFLAGS /* not used */ }; /* * leitch_start - open the Leitch devices and initialize data for processing */ static int leitch_start( int unit, struct peer *peer ) { register struct leitchunit *up; struct refclockproc *pp; int fd; char device[20]; /* * Open serial port. Use CLK line discipline, if available. */ (void)sprintf(device, DEVICE, unit); if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) return (0); /* * Allocate and initialize unit structure */ if (!(up = (struct leitchunit *) emalloc(sizeof(struct leitchunit)))) { (void) close(fd); return (0); } memset((char *)up, 0, sizeof(struct leitchunit)); pp = peer->procptr; pp->io.clock_recv = leitch_receive; pp->io.srcclock = (caddr_t)peer; pp->io.datalen = 0; pp->io.fd = fd; if (!io_addclock(&pp->io)) { (void) close(fd); free(up); return (0); } pp->unitptr = (caddr_t)up; /* * Initialize miscellaneous variables */ peer->precision = PRECISION; pp->clockdesc = DESCRIPTION; memcpy((char *)&pp->refid, REFID, 4); up->state = LEITCH_STATE_IDLE; #ifdef HAVE_PPSAPI /* * Start the PPSAPI interface if it is there. Default to use * the assert edge and do not enable the kernel hardpps. */ if (time_pps_create(fd, &up->handle) < 0) { up->handle = 0; msyslog(LOG_ERR, "refclock_leitch: time_pps_create failed: %m"); return (1); } return(leitch_ppsapi(peer, 0, 0)); #else return (1); #endif /* HAVE_PPSAPI */ } /* * leitch_shutdown - shut down the Leitch clock */ static void leitch_shutdown( int unit, struct peer *peer ) { register struct leitchunit *up; struct refclockproc *pp; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; #ifdef HAVE_PPSAPI if (up->handle != 0) time_pps_destroy(up->handle); #endif /* HAVE_PPSAPI */ io_closeclock(&pp->io); free(up); } #ifdef HAVE_PPSAPI /* * leitch_control - fudge control */ static void leitch_control( int unit, /* unit (not used */ struct refclockstat *in, /* input parameters (not uded) */ struct refclockstat *out, /* output parameters (not used) */ struct peer *peer /* peer structure pointer */ ) { struct refclockproc *pp; pp = peer->procptr; leitch_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2, pp->sloppyclockflag & CLK_FLAG3); } /* * Initialize PPSAPI */ int leitch_ppsapi( struct peer *peer, /* peer structure pointer */ int enb_clear, /* clear enable */ int enb_hardpps /* hardpps enable */ ) { struct refclockproc *pp; struct leitchunit *up; int capability; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; if (time_pps_getcap(up->handle, &capability) < 0) { msyslog(LOG_ERR, "refclock_leitch: time_pps_getcap failed: %m"); return (0); } memset(&up->pps_params, 0, sizeof(pps_params_t)); if (enb_clear) up->pps_params.mode = capability & PPS_CAPTURECLEAR; else up->pps_params.mode = capability & PPS_CAPTUREASSERT; if (!up->pps_params.mode) { msyslog(LOG_ERR, "refclock_leitch: invalid capture edge %d", !enb_clear); return (0); } up->pps_params.mode |= PPS_TSFMT_TSPEC; if (time_pps_setparams(up->handle, &up->pps_params) < 0) { msyslog(LOG_ERR, "refclock_leitch: time_pps_setparams failed: %m"); return (0); } if (enb_hardpps) { if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS, up->pps_params.mode & ~PPS_TSFMT_TSPEC, PPS_TSFMT_TSPEC) < 0) { msyslog(LOG_ERR, "refclock_leitch: time_pps_kcbind failed: %m"); return (0); } pps_enable = 1; } peer->precision = PPS_PRECISION; #if DEBUG if (debug) { time_pps_getparams(up->handle, &up->pps_params); printf( "refclock_ppsapi: capability 0x%x version %d mode 0x%x kern %d\n", capability, up->pps_params.api_version, up->pps_params.mode, enb_hardpps); } #endif return (1); } /* * Get PPSAPI timestamps. * * Return 0 on failure and 1 on success. */ static int leitch_pps( struct leitchunit *up, l_fp *tsptr ) { pps_info_t pps_info; struct timespec timeout, ts; double dtemp; l_fp tstmp; /* * Convert the timespec nanoseconds field to ntp l_fp units. */ if (up->handle == 0) return (0); timeout.tv_sec = 0; timeout.tv_nsec = 0; memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t)); if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info, &timeout) < 0) return (0); if (up->pps_params.mode & PPS_CAPTUREASSERT) { if (pps_info.assert_sequence == up->pps_info.assert_sequence) return (0); ts = up->pps_info.assert_timestamp; } else if (up->pps_params.mode & PPS_CAPTURECLEAR) { if (pps_info.clear_sequence == up->pps_info.clear_sequence) return (0); ts = up->pps_info.clear_timestamp; } else { return (0); } if ((up->ts.tv_sec == ts.tv_sec) && (up->ts.tv_nsec == ts.tv_nsec)) return (0); up->ts = ts; tstmp.l_ui = ts.tv_sec + JAN_1970; dtemp = ts.tv_nsec * FRAC / 1e9; tstmp.l_uf = (u_int32)dtemp; *tsptr = tstmp; return (1); } #endif /* HAVE_PPSAPI */ /* * leitch_receive - receive data from the serial interface */ static void leitch_receive( struct recvbuf *rbufp ) { register struct leitchunit *up; struct refclockproc *pp; struct peer *peer; /* Use these variables to hold data until we decide its worth keeping */ char rd_lastcode[BMAX]; l_fp rd_tmp, rd_tmp1; u_short rd_lencode; double diff; /* * Initialize pointers and read the timecode and timestamp */ peer = (struct peer *)rbufp->recv_srcclock; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp); #ifdef HAVE_PPSAPI /* * If the PPSAPI is working, rather use its timestamps. * assume that the PPS occurs on the second so blow any msec */ if (leitch_pps(up, &rd_tmp1) == 1) { pp->msec = 0; } diff = (double)rd_tmp.l_ui + (double)rd_tmp.l_uf/0x100000000 - ((double)rd_tmp1.l_ui + (double)rd_tmp1.l_uf/0x100000000); printf("RFS: PPSAPI timestamp: %08x:%08x\n", rd_tmp1.l_ui, rd_tmp1.l_uf); printf("RFS: system timestamp: %08x:%08x\n", rd_tmp.l_ui, rd_tmp.l_uf); printf("RFS: difference: %08x:%08x (%3.15f ms)\n", rd_tmp.l_ui - rd_tmp1.l_ui, rd_tmp.l_uf - rd_tmp1.l_uf, diff * 1000); /* * RFS: big hack */ if (diff > 0.95) { rd_tmp1.l_ui++; diff = (double)rd_tmp.l_ui + (double)rd_tmp.l_uf/0x100000000 - ((double)rd_tmp1.l_ui + (double)rd_tmp1.l_uf/0x100000000); printf("RFS: adjust to %3.15f ms\n", diff * 1000); } rd_tmp = rd_tmp1; #endif /* HAVE_PPSAPI */ /* * There is a case that a gives back a "blank" line */ if (rd_lencode == 0) return; #ifdef DEBUG if (debug) printf("leitch: leitchread %d %s\n", rd_lencode, rd_lastcode); #endif /* * We check the timecode format and decode its contents. */ switch(up->state) { case LEITCH_STATE_IDLE: /* Unexpected, discard */ return; case LEITCH_STATE_DATE: /* Validate the date and store it into up->yearday */ if (!leitch_get_date(rd_lastcode, peer)) { refclock_report(peer, CEVNT_BADREPLY); return; } /* Advance to next state, reading the time */ leitch_send(pp->io.fd,"T\r", peer); up->state = LEITCH_STATE_TIME1; break; case LEITCH_STATE_TIME1: case LEITCH_STATE_TIME2: case LEITCH_STATE_TIME3: /* Validate time and store into up->TIME1 */ if (!leitch_get_time(rd_lastcode, peer)) { refclock_report(peer, CEVNT_BADREPLY); return; } up->tstamp = rd_tmp; /* Advance to next state */ if (up->state++ == LEITCH_STATE_TIME3) { /* Transfer time to pp struct and report to caller */ leitch_process(peer, rd_lastcode, rd_lencode); /* Return state machine to idle */ up->state = LEITCH_STATE_IDLE; } break; default: syslog(LOG_ERR, "leitch_receive: invalid state %d ", up->state); } } /* * Parse the date from the Leitch clock. * Returns 0 on error and -1 if successful. * Date is written to the *yearday parameter * NOTE: year value isn't really used. */ static int leitch_get_date( char *rd_lastcode, struct peer *peer ) { register struct leitchunit *up; struct refclockproc *pp; int year, month, day; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; if (sscanf(rd_lastcode, "%02d%02d%02d", &year, &month, &day) != 3) { refclock_report(peer, CEVNT_BADREPLY); return 0; } if (month < 1 || month > 12 || day < 1) { refclock_report(peer, CEVNT_BADTIME); return 0; } /* * Leitch only provides two digits of year. * Simple-mindedly we'll assume it is after 2000. */ if (year > 99) { refclock_report(peer, CEVNT_BADTIME); return 0; } year = year + 2000; /* * Determine day-of-year. * Only works up until 2099. */ if (year % 4) { int i; if (day > day1tab[month - 1]) { refclock_report(peer, CEVNT_BADTIME); return 0; } for (i = 0; i < month - 1; i++) day += day1tab[i]; } else { int i; if (day > day2tab[month - 1]) { refclock_report(peer, CEVNT_BADTIME); return 0; } for (i = 0; i < month - 1; i++) day += day2tab[i]; } /* Store the results into leitchunit struct */ up->year = year; up->yearday = day; return 1; } /* * Parse time from the leitch clock. * Returns 0 on error and 1 on success. */ static int leitch_get_time( char *rd_lastcode, struct peer *peer ) { register struct leitchunit *up; struct refclockproc *pp; int hour, minute, second; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; if (sscanf(rd_lastcode, "%02d%02d%02d", &hour, &minute, &second) != 3) { refclock_report(peer, CEVNT_BADREPLY); return 0; } if (hour > 23 || minute > 59 || second > 59) { refclock_report(peer, CEVNT_BADTIME); return 0; } up->hour = hour; up->minute = minute; up->second = second; return 1; } /* * Called when a complete time/date sequence has been read from leitch. * At this time, the last reported date/time and the timestamp are all * contained in fields of the leitchunit structure. */ static void leitch_process( struct peer *peer, char *rd_lastcode, int rd_lencode ) { register struct leitchunit *up; struct refclockproc *pp; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; #ifdef DEBUG if (debug) printf("leitch_process: called for %d/%03d %02d:%02d:%02d\n", up->year, up->yearday, up->hour, up->minute, up->second); #endif /* Copy last serial stream to pp, for logging purposes */ pp->lencode = rd_lencode; strcpy(pp->a_lastcode,rd_lastcode); /* Set reception time stamp */ pp->lastrec = up->tstamp; /* Copy time out of leitchunit, assume 0 milliseconds */ pp->msec = 0; pp->second = up->second; pp->minute = up->minute; pp->hour = up->hour; pp->day = up->yearday; pp->year = up->year; /* Leap second notification is enabled by fudge time2 */ pp->leap = LEAP_NOWARNING; if (pp->fudgetime2 > 0) pp->leap = LEAP_ADDSECOND; if (pp->fudgetime2 < 0) pp->leap = LEAP_DELSECOND; /* * Process the new sample in the median filter and determine the * reference clock offset and dispersion. We use lastrec as both * the reference time and receive time, in order to avoid being * cute, like setting the reference time later than the receive * time, which may cause a paranoid protocol module to chuck out * the data. */ if (!refclock_process(pp)) { refclock_report(peer, CEVNT_BADTIME); return; } refclock_receive(peer); /* If we get here - what we got from the clock is OK, so say so */ refclock_report(peer, CEVNT_NOMINAL); record_clock_stats(&peer->srcadr, pp->a_lastcode); } /* * leitch_poll - called by the transmit procedure */ static void leitch_poll( int unit, struct peer *peer ) { register struct leitchunit *up; struct refclockproc *pp; pp = peer->procptr; up = (struct leitchunit *)pp->unitptr; if (up->state != LEITCH_STATE_IDLE) { refclock_report(peer, CEVNT_TIMEOUT); up->state = LEITCH_STATE_IDLE; } else { leitch_send(pp->io.fd,"D\r", peer); up->state = LEITCH_STATE_DATE; } } /* * leitch_send(fd,cmd, peer) send command to Leitch. */ static void leitch_send( int fd, const char *cmd, struct peer *peer ) { if (write(fd, cmd, strlen(cmd)) == -1) { refclock_report(peer, CEVNT_FAULT); } } #else int refclock_leitch_bs; #endif /* REFCLOCK */