diff options
Diffstat (limited to 'contrib/ntp/ntpd/refclock_parse.c')
-rw-r--r-- | contrib/ntp/ntpd/refclock_parse.c | 6219 |
1 files changed, 6219 insertions, 0 deletions
diff --git a/contrib/ntp/ntpd/refclock_parse.c b/contrib/ntp/ntpd/refclock_parse.c new file mode 100644 index 000000000000..aa9d2f2c68bf --- /dev/null +++ b/contrib/ntp/ntpd/refclock_parse.c @@ -0,0 +1,6219 @@ +/* + * /src/NTP/REPOSITORY/ntp4-dev/ntpd/refclock_parse.c,v 4.81 2009/05/01 10:15:29 kardel RELEASE_20090105_A + * + * refclock_parse.c,v 4.81 2009/05/01 10:15:29 kardel RELEASE_20090105_A + * + * generic reference clock driver for several DCF/GPS/MSF/... receivers + * + * PPS notes: + * On systems that support PPSAPI (RFC2783) PPSAPI is the + * preferred interface. + * + * Optionally make use of a STREAMS module for input processing where + * available and configured. This STREAMS module reduces the time + * stamp latency for serial and PPS events. + * Currently the STREAMS module is only available for Suns running + * SunOS 4.x and SunOS5.x. + * + * Copyright (c) 1995-2015 by Frank Kardel <kardel <AT> ntp.org> + * Copyright (c) 1989-1994 by Frank Kardel, Friedrich-Alexander Universitaet Erlangen-Nuernberg, Germany + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "ntp_types.h" + +#if defined(REFCLOCK) && defined(CLOCK_PARSE) + +/* + * This driver currently provides the support for + * - Meinberg receiver DCF77 PZF535 (TCXO version) (DCF) + * - Meinberg receiver DCF77 PZF535 (OCXO version) (DCF) + * - Meinberg receiver DCF77 PZF509 (DCF) + * - Meinberg receiver DCF77 AM receivers (e.g. C51) (DCF) + * - IGEL CLOCK (DCF) + * - ELV DCF7000 (DCF) + * - Schmid clock (DCF) + * - Conrad DCF77 receiver module (DCF) + * - FAU DCF77 NTP receiver (TimeBrick) (DCF) + * - WHARTON 400A Series clock (DCF) + * + * - Meinberg GPS receivers (GPS) + * - Trimble (TSIP and TAIP protocol) (GPS) + * + * - RCC8000 MSF Receiver (MSF) + * - VARITEXT clock (MSF) + */ + +/* + * Meinberg receivers are usually connected via a + * 9600/7E1 or 19200/8N1 serial line. + * + * The Meinberg GPS receivers also have a special NTP time stamp + * format. The firmware release is Uni-Erlangen. + * + * Meinberg generic receiver setup: + * output time code every second + * Baud rate 9600 7E2S + * + * Meinberg GPS receiver setup: + * output time code every second + * Baudrate 19200 8N1 + * + * This software supports the standard data formats used + * in Meinberg receivers. + * + * Special software versions are only sensible for the + * oldest GPS receiver, GPS16x. For newer receiver types + * the output string format can be configured at the device, + * and the device name is generally GPSxxx instead of GPS16x. + * + * Meinberg can be reached via: http://www.meinberg.de/ + */ + +#include "ntpd.h" +#include "ntp_refclock.h" +#include "timevalops.h" /* includes <sys/time.h> */ +#include "ntp_control.h" +#include "ntp_string.h" + +#include <stdio.h> +#include <ctype.h> +#ifndef TM_IN_SYS_TIME +# include <time.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#if !defined(STREAM) && !defined(HAVE_SYSV_TTYS) && !defined(HAVE_BSD_TTYS) && !defined(HAVE_TERMIOS) +# include "Bletch: Define one of {STREAM,HAVE_SYSV_TTYS,HAVE_TERMIOS}" +#endif + +#ifdef STREAM +# include <sys/stream.h> +# include <sys/stropts.h> +#endif + +#ifdef HAVE_TERMIOS +# include <termios.h> +# define TTY_GETATTR(_FD_, _ARG_) tcgetattr((_FD_), (_ARG_)) +# define TTY_SETATTR(_FD_, _ARG_) tcsetattr((_FD_), TCSANOW, (_ARG_)) +# undef HAVE_SYSV_TTYS +#endif + +#ifdef HAVE_SYSV_TTYS +# define TTY_GETATTR(_FD_, _ARG_) ioctl((_FD_), TCGETA, (_ARG_)) +# define TTY_SETATTR(_FD_, _ARG_) ioctl((_FD_), TCSETAW, (_ARG_)) +#endif + +#ifdef HAVE_BSD_TTYS +/* #error CURRENTLY NO BSD TTY SUPPORT */ +# include "Bletch: BSD TTY not currently supported" +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +#ifdef HAVE_PPSAPI +# include "ppsapi_timepps.h" +# include "refclock_atom.h" +#endif + +#ifdef PPS +# ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +# endif +# ifdef HAVE_TIO_SERIAL_STUFF +# include <linux/serial.h> +# endif +#endif + +# define BUFFER_SIZE(_BUF, _PTR) ((int)((_BUF) + sizeof(_BUF) - (_PTR))) +# define BUFFER_SIZES(_BUF, _PTR, _SZ) ((int)((_BUF) + (_SZ) - (_PTR))) + +/* + * document type of PPS interfacing - copy of ifdef mechanism in local_input() + */ +#undef PPS_METHOD + +#ifdef HAVE_PPSAPI +#define PPS_METHOD "PPS API" +#else +#ifdef TIOCDCDTIMESTAMP +#define PPS_METHOD "TIOCDCDTIMESTAMP" +#else /* TIOCDCDTIMESTAMP */ +#if defined(HAVE_STRUCT_PPSCLOCKEV) && (defined(HAVE_CIOGETEV) || defined(HAVE_TIOCGPPSEV)) +#ifdef HAVE_CIOGETEV +#define PPS_METHOD "CIOGETEV" +#endif +#ifdef HAVE_TIOCGPPSEV +#define PPS_METHOD "TIOCGPPSEV" +#endif +#endif +#endif /* TIOCDCDTIMESTAMP */ +#endif /* HAVE_PPSAPI */ + +/* + * COND_DEF can be conditionally defined as DEF or 0. If defined as DEF + * then some more parse-specific variables are flagged to be printed with + * "ntpq -c cv <assid>". This can be lengthy, so by default COND_DEF + * should be defined as 0. + */ +#if 0 +# define COND_DEF DEF // enable this for testing +#else +# define COND_DEF 0 // enable this by default +#endif + +#include "ntp_io.h" +#include "ntp_stdlib.h" + +#include "parse.h" +#include "mbg_gps166.h" +#include "trimble.h" +#include "binio.h" +#include "ascii.h" +#include "ieee754io.h" +#include "recvbuff.h" + +static char rcsid[] = "refclock_parse.c,v 4.81 2009/05/01 10:15:29 kardel RELEASE_20090105_A+POWERUPTRUST"; + +/**=========================================================================== + ** external interface to ntp mechanism + **/ + +static int parse_start (int, struct peer *); +static void parse_shutdown (int, struct peer *); +static void parse_poll (int, struct peer *); +static void parse_control (int, const struct refclockstat *, struct refclockstat *, struct peer *); + +struct refclock refclock_parse = { + parse_start, + parse_shutdown, + parse_poll, + parse_control, + noentry, + noentry, + NOFLAGS +}; + +/* + * Definitions + */ +#define MAXUNITS 4 /* maximum number of "PARSE" units permitted */ +#define PARSEDEVICE "/dev/refclock-%d" /* device to open %d is unit number */ +#define PARSEPPSDEVICE "/dev/refclockpps-%d" /* optional pps device to open %d is unit number */ + +#undef ABS +#define ABS(_X_) (((_X_) < 0) ? -(_X_) : (_X_)) + +#define PARSE_HARDPPS_DISABLE 0 +#define PARSE_HARDPPS_ENABLE 1 + +/**=========================================================================== + ** function vector for dynamically binding io handling mechanism + **/ + +struct parseunit; /* to keep inquiring minds happy */ + +typedef struct bind +{ + const char *bd_description; /* name of type of binding */ + int (*bd_init) (struct parseunit *); /* initialize */ + void (*bd_end) (struct parseunit *); /* end */ + int (*bd_setcs) (struct parseunit *, parsectl_t *); /* set character size */ + int (*bd_disable) (struct parseunit *); /* disable */ + int (*bd_enable) (struct parseunit *); /* enable */ + int (*bd_getfmt) (struct parseunit *, parsectl_t *); /* get format */ + int (*bd_setfmt) (struct parseunit *, parsectl_t *); /* setfmt */ + int (*bd_timecode) (struct parseunit *, parsectl_t *); /* get time code */ + void (*bd_receive) (struct recvbuf *); /* receive operation */ + int (*bd_io_input) (struct recvbuf *); /* input operation */ +} bind_t; + +#define PARSE_END(_X_) (*(_X_)->binding->bd_end)(_X_) +#define PARSE_SETCS(_X_, _CS_) (*(_X_)->binding->bd_setcs)(_X_, _CS_) +#define PARSE_ENABLE(_X_) (*(_X_)->binding->bd_enable)(_X_) +#define PARSE_DISABLE(_X_) (*(_X_)->binding->bd_disable)(_X_) +#define PARSE_GETFMT(_X_, _DCT_) (*(_X_)->binding->bd_getfmt)(_X_, _DCT_) +#define PARSE_SETFMT(_X_, _DCT_) (*(_X_)->binding->bd_setfmt)(_X_, _DCT_) +#define PARSE_GETTIMECODE(_X_, _DCT_) (*(_X_)->binding->bd_timecode)(_X_, _DCT_) + +/* + * special handling flags + */ +#define PARSE_F_PPSONSECOND 0x00000001 /* PPS pulses are on second */ +#define PARSE_F_POWERUPTRUST 0x00000100 /* POWERUP state ist trusted for */ + /* trusttime after SYNC was seen */ +/**=========================================================================== + ** error message regression handling + ** + ** there are quite a few errors that can occur in rapid succession such as + ** noisy input data or no data at all. in order to reduce the amount of + ** syslog messages in such case, we are using a backoff algorithm. We limit + ** the number of error messages of a certain class to 1 per time unit. if a + ** configurable number of messages is displayed that way, we move on to the + ** next time unit / count for that class. a count of messages that have been + ** suppressed is held and displayed whenever a corresponding message is + ** displayed. the time units for a message class will also be displayed. + ** whenever an error condition clears we reset the error message state, + ** thus we would still generate much output on pathological conditions + ** where the system oscillates between OK and NOT OK states. coping + ** with that condition is currently considered too complicated. + **/ + +#define ERR_ALL (unsigned)~0 /* "all" errors */ +#define ERR_BADDATA (unsigned)0 /* unusable input data/conversion errors */ +#define ERR_NODATA (unsigned)1 /* no input data */ +#define ERR_BADIO (unsigned)2 /* read/write/select errors */ +#define ERR_BADSTATUS (unsigned)3 /* unsync states */ +#define ERR_BADEVENT (unsigned)4 /* non nominal events */ +#define ERR_INTERNAL (unsigned)5 /* internal error */ +#define ERR_CNT (unsigned)(ERR_INTERNAL+1) + +#define ERR(_X_) if (list_err(parse, (_X_))) + +struct errorregression +{ + u_long err_count; /* number of repititions per class */ + u_long err_delay; /* minimum delay between messages */ +}; + +static struct errorregression +err_baddata[] = /* error messages for bad input data */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 3, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_nodata[] = /* error messages for missing input data */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 3, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_badstatus[] = /* unsynchronized state messages */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 3, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_badio[] = /* io failures (bad reads, selects, ...) */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 5, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_badevent[] = /* non nominal events */ +{ + { 20, 0 }, /* output first message immediately */ + { 6, 60 }, /* output next five messages in 60 second intervals */ + { 5, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_internal[] = /* really bad things - basically coding/OS errors */ +{ + { 0, 0 }, /* output all messages immediately */ +}; + +static struct errorregression * +err_tbl[] = +{ + err_baddata, + err_nodata, + err_badio, + err_badstatus, + err_badevent, + err_internal +}; + +struct errorinfo +{ + u_long err_started; /* begin time (ntp) of error condition */ + u_long err_last; /* last time (ntp) error occurred */ + u_long err_cnt; /* number of error repititions */ + u_long err_suppressed; /* number of suppressed messages */ + struct errorregression *err_stage; /* current error stage */ +}; + +/**=========================================================================== + ** refclock instance data + **/ + +struct parseunit +{ + /* + * NTP management + */ + struct peer *peer; /* backlink to peer structure - refclock inactive if 0 */ + struct refclockproc *generic; /* backlink to refclockproc structure */ + + /* + * PARSE io + */ + bind_t *binding; /* io handling binding */ + + /* + * parse state + */ + parse_t parseio; /* io handling structure (user level parsing) */ + + /* + * type specific parameters + */ + struct parse_clockinfo *parse_type; /* link to clock description */ + + /* + * clock state handling/reporting + */ + u_char flags; /* flags (leap_control) */ + u_long lastchange; /* time (ntp) when last state change accured */ + u_long statetime[CEVNT_MAX+1]; /* accumulated time of clock states */ + u_long pollneeddata; /* current_time(!=0) for receive sample expected in PPS mode */ + u_short lastformat; /* last format used */ + u_long lastsync; /* time (ntp) when clock was last seen fully synchronized */ + u_long maxunsync; /* max time in seconds a receiver is trusted after loosing synchronisation */ + double ppsphaseadjust; /* phase adjustment of PPS time stamp */ + u_long lastmissed; /* time (ntp) when poll didn't get data (powerup heuristic) */ + u_long ppsserial; /* magic cookie for ppsclock serials (avoids stale ppsclock data) */ + int ppsfd; /* fd to ise for PPS io */ +#ifdef HAVE_PPSAPI + int hardppsstate; /* current hard pps state */ + struct refclock_atom atom; /* PPSAPI structure */ +#endif + parsetime_t timedata; /* last (parse module) data */ + void *localdata; /* optional local, receiver-specific data */ + unsigned long localstate; /* private local state */ + struct errorinfo errors[ERR_CNT]; /* error state table for suppressing excessive error messages */ + struct ctl_var *kv; /* additional pseudo variables */ + u_long laststatistic; /* time when staticstics where output */ +}; + + +/**=========================================================================== + ** Clockinfo section all parameter for specific clock types + ** includes NTP parameters, TTY parameters and IO handling parameters + **/ + +static void poll_dpoll (struct parseunit *); +static void poll_poll (struct peer *); +static int poll_init (struct parseunit *); + +typedef struct poll_info +{ + u_long rate; /* poll rate - once every "rate" seconds - 0 off */ + const char *string; /* string to send for polling */ + u_long count; /* number of characters in string */ +} poll_info_t; + +#define NO_CL_FLAGS 0 +#define NO_POLL 0 +#define NO_INIT 0 +#define NO_END 0 +#define NO_EVENT 0 +#define NO_LCLDATA 0 +#define NO_MESSAGE 0 +#define NO_PPSDELAY 0 + +#define DCF_ID "DCF" /* generic DCF */ +#define DCF_A_ID "DCFa" /* AM demodulation */ +#define DCF_P_ID "DCFp" /* psuedo random phase shift */ +#define GPS_ID "GPS" /* GPS receiver */ + +#define NOCLOCK_ROOTDELAY 0.0 +#define NOCLOCK_BASEDELAY 0.0 +#define NOCLOCK_DESCRIPTION 0 +#define NOCLOCK_MAXUNSYNC 0 +#define NOCLOCK_CFLAG 0 +#define NOCLOCK_IFLAG 0 +#define NOCLOCK_OFLAG 0 +#define NOCLOCK_LFLAG 0 +#define NOCLOCK_ID "TILT" +#define NOCLOCK_POLL NO_POLL +#define NOCLOCK_INIT NO_INIT +#define NOCLOCK_END NO_END +#define NOCLOCK_DATA NO_LCLDATA +#define NOCLOCK_FORMAT "" +#define NOCLOCK_TYPE CTL_SST_TS_UNSPEC +#define NOCLOCK_SAMPLES 0 +#define NOCLOCK_KEEP 0 + +#define DCF_TYPE CTL_SST_TS_LF +#define GPS_TYPE CTL_SST_TS_UHF + +/* + * receiver specific constants + */ +#define MBG_SPEED (B9600) +#define MBG_CFLAG (CS7|PARENB|CREAD|CLOCAL|HUPCL|CSTOPB) +#define MBG_IFLAG (IGNBRK|IGNPAR|ISTRIP) +#define MBG_OFLAG 0 +#define MBG_LFLAG 0 +#define MBG_FLAGS PARSE_F_PPSONSECOND + +/* + * Meinberg DCF77 receivers + */ +#define DCFUA31_ROOTDELAY 0.0 /* 0 */ +#define DCFUA31_BASEDELAY 0.010 /* 10.7421875ms: 10 ms (+/- 3 ms) */ +#define DCFUA31_DESCRIPTION "Meinberg DCF77 C51 or compatible" +#define DCFUA31_MAXUNSYNC 60*30 /* only trust clock for 1/2 hour */ +#define DCFUA31_SPEED MBG_SPEED +#define DCFUA31_CFLAG MBG_CFLAG +#define DCFUA31_IFLAG MBG_IFLAG +#define DCFUA31_OFLAG MBG_OFLAG +#define DCFUA31_LFLAG MBG_LFLAG +#define DCFUA31_SAMPLES 5 +#define DCFUA31_KEEP 3 +#define DCFUA31_FORMAT "Meinberg Standard" + +/* + * Meinberg DCF PZF535/TCXO (FM/PZF) receiver + */ +#define DCFPZF535_ROOTDELAY 0.0 +#define DCFPZF535_BASEDELAY 0.001968 /* 1.968ms +- 104us (oscilloscope) - relative to start (end of STX) */ +#define DCFPZF535_DESCRIPTION "Meinberg DCF PZF 535/509 / TCXO" +#define DCFPZF535_MAXUNSYNC 60*60*12 /* only trust clock for 12 hours + * @ 5e-8df/f we have accumulated + * at most 2.16 ms (thus we move to + * NTP synchronisation */ +#define DCFPZF535_SPEED MBG_SPEED +#define DCFPZF535_CFLAG MBG_CFLAG +#define DCFPZF535_IFLAG MBG_IFLAG +#define DCFPZF535_OFLAG MBG_OFLAG +#define DCFPZF535_LFLAG MBG_LFLAG +#define DCFPZF535_SAMPLES 5 +#define DCFPZF535_KEEP 3 +#define DCFPZF535_FORMAT "Meinberg Standard" + +/* + * Meinberg DCF PZF535/OCXO receiver + */ +#define DCFPZF535OCXO_ROOTDELAY 0.0 +#define DCFPZF535OCXO_BASEDELAY 0.001968 /* 1.968ms +- 104us (oscilloscope) - relative to start (end of STX) */ +#define DCFPZF535OCXO_DESCRIPTION "Meinberg DCF PZF 535/509 / OCXO" +#define DCFPZF535OCXO_MAXUNSYNC 60*60*96 /* only trust clock for 4 days + * @ 5e-9df/f we have accumulated + * at most an error of 1.73 ms + * (thus we move to NTP synchronisation) */ +#define DCFPZF535OCXO_SPEED MBG_SPEED +#define DCFPZF535OCXO_CFLAG MBG_CFLAG +#define DCFPZF535OCXO_IFLAG MBG_IFLAG +#define DCFPZF535OCXO_OFLAG MBG_OFLAG +#define DCFPZF535OCXO_LFLAG MBG_LFLAG +#define DCFPZF535OCXO_SAMPLES 5 +#define DCFPZF535OCXO_KEEP 3 +#define DCFPZF535OCXO_FORMAT "Meinberg Standard" + +/* + * Meinberg GPS receivers + */ +static void gps16x_message (struct parseunit *, parsetime_t *); +static int gps16x_poll_init (struct parseunit *); + +#define GPS16X_ROOTDELAY 0.0 /* nothing here */ +#define GPS16X_BASEDELAY 0.001968 /* XXX to be fixed ! 1.968ms +- 104us (oscilloscope) - relative to start (end of STX) */ +#define GPS16X_DESCRIPTION "Meinberg GPS receiver" +#define GPS16X_MAXUNSYNC 60*60*96 /* only trust clock for 4 days + * @ 5e-9df/f we have accumulated + * at most an error of 1.73 ms + * (thus we move to NTP synchronisation) */ +#define GPS16X_SPEED B19200 +#define GPS16X_CFLAG (CS8|CREAD|CLOCAL|HUPCL) +#define GPS16X_IFLAG (IGNBRK|IGNPAR) +#define GPS16X_OFLAG MBG_OFLAG +#define GPS16X_LFLAG MBG_LFLAG +#define GPS16X_POLLRATE 6 +#define GPS16X_POLLCMD "" +#define GPS16X_CMDSIZE 0 + +static poll_info_t gps16x_pollinfo = { GPS16X_POLLRATE, GPS16X_POLLCMD, GPS16X_CMDSIZE }; + +#define GPS16X_INIT gps16x_poll_init +#define GPS16X_POLL 0 +#define GPS16X_END 0 +#define GPS16X_DATA ((void *)(&gps16x_pollinfo)) +#define GPS16X_MESSAGE gps16x_message +#define GPS16X_ID GPS_ID +#define GPS16X_FORMAT "Meinberg GPS Extended" +#define GPS16X_SAMPLES 5 +#define GPS16X_KEEP 3 + +/* + * ELV DCF7000 Wallclock-Receiver/Switching Clock (Kit) + * + * This is really not the hottest clock - but before you have nothing ... + */ +#define DCF7000_ROOTDELAY 0.0 /* 0 */ +#define DCF7000_BASEDELAY 0.405 /* slow blow */ +#define DCF7000_DESCRIPTION "ELV DCF7000" +#define DCF7000_MAXUNSYNC (60*5) /* sorry - but it just was not build as a clock */ +#define DCF7000_SPEED (B9600) +#define DCF7000_CFLAG (CS8|CREAD|PARENB|PARODD|CLOCAL|HUPCL) +#define DCF7000_IFLAG (IGNBRK) +#define DCF7000_OFLAG 0 +#define DCF7000_LFLAG 0 +#define DCF7000_SAMPLES 5 +#define DCF7000_KEEP 3 +#define DCF7000_FORMAT "ELV DCF7000" + +/* + * Schmid DCF Receiver Kit + * + * When the WSDCF clock is operating optimally we want the primary clock + * distance to come out at 300 ms. Thus, peer.distance in the WSDCF peer + * structure is set to 290 ms and we compute delays which are at least + * 10 ms long. The following are 290 ms and 10 ms expressed in u_fp format + */ +#define WS_POLLRATE 1 /* every second - watch interdependency with poll routine */ +#define WS_POLLCMD "\163" +#define WS_CMDSIZE 1 + +static poll_info_t wsdcf_pollinfo = { WS_POLLRATE, WS_POLLCMD, WS_CMDSIZE }; + +#define WSDCF_INIT poll_init +#define WSDCF_POLL poll_dpoll +#define WSDCF_END 0 +#define WSDCF_DATA ((void *)(&wsdcf_pollinfo)) +#define WSDCF_ROOTDELAY 0.0 /* 0 */ +#define WSDCF_BASEDELAY 0.010 /* ~ 10ms */ +#define WSDCF_DESCRIPTION "WS/DCF Receiver" +#define WSDCF_FORMAT "Schmid" +#define WSDCF_MAXUNSYNC (60*60) /* assume this beast hold at 1 h better than 2 ms XXX-must verify */ +#define WSDCF_SPEED (B1200) +#define WSDCF_CFLAG (CS8|CREAD|CLOCAL) +#define WSDCF_IFLAG 0 +#define WSDCF_OFLAG 0 +#define WSDCF_LFLAG 0 +#define WSDCF_SAMPLES 5 +#define WSDCF_KEEP 3 + +/* + * RAW DCF77 - input of DCF marks via RS232 - many variants + */ +#define RAWDCF_FLAGS 0 +#define RAWDCF_ROOTDELAY 0.0 /* 0 */ +#define RAWDCF_BASEDELAY 0.258 +#define RAWDCF_FORMAT "RAW DCF77 Timecode" +#define RAWDCF_MAXUNSYNC (0) /* sorry - its a true receiver - no signal - no time */ +#define RAWDCF_SPEED (B50) +#ifdef NO_PARENB_IGNPAR /* Was: defined(SYS_IRIX4) || defined(SYS_IRIX5) */ +/* somehow doesn't grok PARENB & IGNPAR (mj) */ +# define RAWDCF_CFLAG (CS8|CREAD|CLOCAL) +#else +# define RAWDCF_CFLAG (CS8|CREAD|CLOCAL|PARENB) +#endif +#ifdef RAWDCF_NO_IGNPAR /* Was: defined(SYS_LINUX) && defined(CLOCK_RAWDCF) */ +# define RAWDCF_IFLAG 0 +#else +# define RAWDCF_IFLAG (IGNPAR) +#endif +#define RAWDCF_OFLAG 0 +#define RAWDCF_LFLAG 0 +#define RAWDCF_SAMPLES 20 +#define RAWDCF_KEEP 12 +#define RAWDCF_INIT 0 + +/* + * RAW DCF variants + */ +/* + * Conrad receiver + * + * simplest (cheapest) DCF clock - e. g. DCF77 receiver by Conrad + * (~40DM - roughly $30 ) followed by a level converter for RS232 + */ +#define CONRAD_BASEDELAY 0.292 /* Conrad receiver @ 50 Baud on a Sun */ +#define CONRAD_DESCRIPTION "RAW DCF77 CODE (Conrad DCF77 receiver module)" + +/* Gude Analog- und Digitalsystem GmbH 'Expert mouseCLOCK USB v2.0' */ +#define GUDE_EMC_USB_V20_SPEED (B4800) +#define GUDE_EMC_USB_V20_BASEDELAY 0.425 /* USB serial<->USB converter FTDI232R */ +#define GUDE_EMC_USB_V20_DESCRIPTION "RAW DCF77 CODE (Expert mouseCLOCK USB v2.0)" + +/* + * TimeBrick receiver + */ +#define TIMEBRICK_BASEDELAY 0.210 /* TimeBrick @ 50 Baud on a Sun */ +#define TIMEBRICK_DESCRIPTION "RAW DCF77 CODE (TimeBrick)" + +/* + * IGEL:clock receiver + */ +#define IGELCLOCK_BASEDELAY 0.258 /* IGEL:clock receiver */ +#define IGELCLOCK_DESCRIPTION "RAW DCF77 CODE (IGEL:clock)" +#define IGELCLOCK_SPEED (B1200) +#define IGELCLOCK_CFLAG (CS8|CREAD|HUPCL|CLOCAL) + +/* + * RAWDCF receivers that need to be powered from DTR + * (like Expert mouse clock) + */ +static int rawdcf_init_1 (struct parseunit *); +#define RAWDCFDTRSET_DESCRIPTION "RAW DCF77 CODE (DTR SET/RTS CLR)" +#define RAWDCFDTRSET75_DESCRIPTION "RAW DCF77 CODE (DTR SET/RTS CLR @ 75 baud)" +#define RAWDCFDTRSET_INIT rawdcf_init_1 + +/* + * RAWDCF receivers that need to be powered from + * DTR CLR and RTS SET + */ +static int rawdcf_init_2 (struct parseunit *); +#define RAWDCFDTRCLRRTSSET_DESCRIPTION "RAW DCF77 CODE (DTR CLR/RTS SET)" +#define RAWDCFDTRCLRRTSSET75_DESCRIPTION "RAW DCF77 CODE (DTR CLR/RTS SET @ 75 baud)" +#define RAWDCFDTRCLRRTSSET_INIT rawdcf_init_2 + +/* + * Trimble GPS receivers (TAIP and TSIP protocols) + */ +#ifndef TRIM_POLLRATE +#define TRIM_POLLRATE 0 /* only true direct polling */ +#endif + +#define TRIM_TAIPPOLLCMD ">SRM;FR_FLAG=F;EC_FLAG=F<>QTM<" +#define TRIM_TAIPCMDSIZE (sizeof(TRIM_TAIPPOLLCMD)-1) + +static poll_info_t trimbletaip_pollinfo = { TRIM_POLLRATE, TRIM_TAIPPOLLCMD, TRIM_TAIPCMDSIZE }; +static int trimbletaip_init (struct parseunit *); +static void trimbletaip_event (struct parseunit *, int); + +/* query time & UTC correction data */ +static char tsipquery[] = { DLE, 0x21, DLE, ETX, DLE, 0x2F, DLE, ETX }; + +static poll_info_t trimbletsip_pollinfo = { TRIM_POLLRATE, tsipquery, sizeof(tsipquery) }; +static int trimbletsip_init (struct parseunit *); +static void trimbletsip_end (struct parseunit *); +static void trimbletsip_message (struct parseunit *, parsetime_t *); +static void trimbletsip_event (struct parseunit *, int); + +#define TRIMBLETSIP_IDLE_TIME (300) /* 5 minutes silence at most */ +#define TRIMBLE_RESET_HOLDOFF TRIMBLETSIP_IDLE_TIME + +#define TRIMBLETAIP_SPEED (B4800) +#define TRIMBLETAIP_CFLAG (CS8|CREAD|CLOCAL) +#define TRIMBLETAIP_IFLAG (BRKINT|IGNPAR|ISTRIP|ICRNL|IXON) +#define TRIMBLETAIP_OFLAG (OPOST|ONLCR) +#define TRIMBLETAIP_LFLAG (0) + +#define TRIMBLETSIP_SPEED (B9600) +#define TRIMBLETSIP_CFLAG (CS8|CLOCAL|CREAD|PARENB|PARODD) +#define TRIMBLETSIP_IFLAG (IGNBRK) +#define TRIMBLETSIP_OFLAG (0) +#define TRIMBLETSIP_LFLAG (ICANON) + +#define TRIMBLETSIP_SAMPLES 5 +#define TRIMBLETSIP_KEEP 3 +#define TRIMBLETAIP_SAMPLES 5 +#define TRIMBLETAIP_KEEP 3 + +#define TRIMBLETAIP_FLAGS (PARSE_F_PPSONSECOND) +#define TRIMBLETSIP_FLAGS (TRIMBLETAIP_FLAGS) + +#define TRIMBLETAIP_POLL poll_dpoll +#define TRIMBLETSIP_POLL poll_dpoll + +#define TRIMBLETAIP_INIT trimbletaip_init +#define TRIMBLETSIP_INIT trimbletsip_init + +#define TRIMBLETAIP_EVENT trimbletaip_event + +#define TRIMBLETSIP_EVENT trimbletsip_event +#define TRIMBLETSIP_MESSAGE trimbletsip_message + +#define TRIMBLETAIP_END 0 +#define TRIMBLETSIP_END trimbletsip_end + +#define TRIMBLETAIP_DATA ((void *)(&trimbletaip_pollinfo)) +#define TRIMBLETSIP_DATA ((void *)(&trimbletsip_pollinfo)) + +#define TRIMBLETAIP_ID GPS_ID +#define TRIMBLETSIP_ID GPS_ID + +#define TRIMBLETAIP_FORMAT "Trimble TAIP" +#define TRIMBLETSIP_FORMAT "Trimble TSIP" + +#define TRIMBLETAIP_ROOTDELAY 0x0 +#define TRIMBLETSIP_ROOTDELAY 0x0 + +#define TRIMBLETAIP_BASEDELAY 0.0 +#define TRIMBLETSIP_BASEDELAY 0.020 /* GPS time message latency */ + +#define TRIMBLETAIP_DESCRIPTION "Trimble GPS (TAIP) receiver" +#define TRIMBLETSIP_DESCRIPTION "Trimble GPS (TSIP) receiver" + +#define TRIMBLETAIP_MAXUNSYNC 0 +#define TRIMBLETSIP_MAXUNSYNC 0 + +#define TRIMBLETAIP_EOL '<' + +/* + * RadioCode Clocks RCC 800 receiver + */ +#define RCC_POLLRATE 0 /* only true direct polling */ +#define RCC_POLLCMD "\r" +#define RCC_CMDSIZE 1 + +static poll_info_t rcc8000_pollinfo = { RCC_POLLRATE, RCC_POLLCMD, RCC_CMDSIZE }; +#define RCC8000_FLAGS 0 +#define RCC8000_POLL poll_dpoll +#define RCC8000_INIT poll_init +#define RCC8000_END 0 +#define RCC8000_DATA ((void *)(&rcc8000_pollinfo)) +#define RCC8000_ROOTDELAY 0.0 +#define RCC8000_BASEDELAY 0.0 +#define RCC8000_ID "MSF" +#define RCC8000_DESCRIPTION "RCC 8000 MSF Receiver" +#define RCC8000_FORMAT "Radiocode RCC8000" +#define RCC8000_MAXUNSYNC (60*60) /* should be ok for an hour */ +#define RCC8000_SPEED (B2400) +#define RCC8000_CFLAG (CS8|CREAD|CLOCAL) +#define RCC8000_IFLAG (IGNBRK|IGNPAR) +#define RCC8000_OFLAG 0 +#define RCC8000_LFLAG 0 +#define RCC8000_SAMPLES 5 +#define RCC8000_KEEP 3 + +/* + * Hopf Radio clock 6021 Format + * + */ +#define HOPF6021_ROOTDELAY 0.0 +#define HOPF6021_BASEDELAY 0.0 +#define HOPF6021_DESCRIPTION "HOPF 6021" +#define HOPF6021_FORMAT "hopf Funkuhr 6021" +#define HOPF6021_MAXUNSYNC (60*60) /* should be ok for an hour */ +#define HOPF6021_SPEED (B9600) +#define HOPF6021_CFLAG (CS8|CREAD|CLOCAL) +#define HOPF6021_IFLAG (IGNBRK|ISTRIP) +#define HOPF6021_OFLAG 0 +#define HOPF6021_LFLAG 0 +#define HOPF6021_FLAGS 0 +#define HOPF6021_SAMPLES 5 +#define HOPF6021_KEEP 3 + +/* + * Diem's Computime Radio Clock Receiver + */ +#define COMPUTIME_FLAGS 0 +#define COMPUTIME_ROOTDELAY 0.0 +#define COMPUTIME_BASEDELAY 0.0 +#define COMPUTIME_ID DCF_ID +#define COMPUTIME_DESCRIPTION "Diem's Computime receiver" +#define COMPUTIME_FORMAT "Diem's Computime Radio Clock" +#define COMPUTIME_TYPE DCF_TYPE +#define COMPUTIME_MAXUNSYNC (60*60) /* only trust clock for 1 hour */ +#define COMPUTIME_SPEED (B9600) +#define COMPUTIME_CFLAG (CSTOPB|CS7|CREAD|CLOCAL) +#define COMPUTIME_IFLAG (IGNBRK|IGNPAR|ISTRIP) +#define COMPUTIME_OFLAG 0 +#define COMPUTIME_LFLAG 0 +#define COMPUTIME_SAMPLES 5 +#define COMPUTIME_KEEP 3 + +/* + * Varitext Radio Clock Receiver + */ +#define VARITEXT_FLAGS 0 +#define VARITEXT_ROOTDELAY 0.0 +#define VARITEXT_BASEDELAY 0.0 +#define VARITEXT_ID "MSF" +#define VARITEXT_DESCRIPTION "Varitext receiver" +#define VARITEXT_FORMAT "Varitext Radio Clock" +#define VARITEXT_TYPE DCF_TYPE +#define VARITEXT_MAXUNSYNC (60*60) /* only trust clock for 1 hour */ +#define VARITEXT_SPEED (B9600) +#define VARITEXT_CFLAG (CS7|CREAD|CLOCAL|PARENB|PARODD) +#define VARITEXT_IFLAG (IGNPAR|IGNBRK|INPCK) /*|ISTRIP)*/ +#define VARITEXT_OFLAG 0 +#define VARITEXT_LFLAG 0 +#define VARITEXT_SAMPLES 32 +#define VARITEXT_KEEP 20 + +/* + * SEL240x Satellite Sychronized Clock + */ +#define SEL240X_POLLRATE 0 /* only true direct polling */ +#define SEL240X_POLLCMD "BUB8" +#define SEL240X_CMDSIZE 4 + +static poll_info_t sel240x_pollinfo = { SEL240X_POLLRATE, + SEL240X_POLLCMD, + SEL240X_CMDSIZE }; +#define SEL240X_FLAGS (PARSE_F_PPSONSECOND) +#define SEL240X_POLL poll_dpoll +#define SEL240X_INIT poll_init +#define SEL240X_END 0 +#define SEL240X_DATA ((void *)(&sel240x_pollinfo)) +#define SEL240X_ROOTDELAY 0.0 +#define SEL240X_BASEDELAY 0.0 +#define SEL240X_ID GPS_ID +#define SEL240X_DESCRIPTION "SEL240x Satellite Synchronized Clock" +#define SEL240X_FORMAT "SEL B8" +#define SEL240X_MAXUNSYNC 60*60*12 /* only trust clock for 12 hours */ +#define SEL240X_SPEED (B9600) +#define SEL240X_CFLAG (CS8|CREAD|CLOCAL) +#define SEL240X_IFLAG (IGNBRK|IGNPAR) +#define SEL240X_OFLAG (0) +#define SEL240X_LFLAG (0) +#define SEL240X_SAMPLES 5 +#define SEL240X_KEEP 3 + +static struct parse_clockinfo +{ + u_long cl_flags; /* operation flags (PPS interpretation, trust handling) */ + void (*cl_poll) (struct parseunit *); /* active poll routine */ + int (*cl_init) (struct parseunit *); /* active poll init routine */ + void (*cl_event) (struct parseunit *, int); /* special event handling (e.g. reset clock) */ + void (*cl_end) (struct parseunit *); /* active poll end routine */ + void (*cl_message) (struct parseunit *, parsetime_t *); /* process a lower layer message */ + void *cl_data; /* local data area for "poll" mechanism */ + double cl_rootdelay; /* rootdelay */ + double cl_basedelay; /* current offset by which the RS232 + time code is delayed from the actual time */ + const char *cl_id; /* ID code */ + const char *cl_description; /* device name */ + const char *cl_format; /* fixed format */ + u_char cl_type; /* clock type (ntp control) */ + u_long cl_maxunsync; /* time to trust oscillator after losing synch */ + u_long cl_speed; /* terminal input & output baudrate */ + u_long cl_cflag; /* terminal control flags */ + u_long cl_iflag; /* terminal input flags */ + u_long cl_oflag; /* terminal output flags */ + u_long cl_lflag; /* terminal local flags */ + u_long cl_samples; /* samples for median filter */ + u_long cl_keep; /* samples for median filter to keep */ +} parse_clockinfo[] = +{ + { /* mode 0 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + DCFPZF535_ROOTDELAY, + DCFPZF535_BASEDELAY, + DCF_P_ID, + DCFPZF535_DESCRIPTION, + DCFPZF535_FORMAT, + DCF_TYPE, + DCFPZF535_MAXUNSYNC, + DCFPZF535_SPEED, + DCFPZF535_CFLAG, + DCFPZF535_IFLAG, + DCFPZF535_OFLAG, + DCFPZF535_LFLAG, + DCFPZF535_SAMPLES, + DCFPZF535_KEEP + }, + { /* mode 1 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + DCFPZF535OCXO_ROOTDELAY, + DCFPZF535OCXO_BASEDELAY, + DCF_P_ID, + DCFPZF535OCXO_DESCRIPTION, + DCFPZF535OCXO_FORMAT, + DCF_TYPE, + DCFPZF535OCXO_MAXUNSYNC, + DCFPZF535OCXO_SPEED, + DCFPZF535OCXO_CFLAG, + DCFPZF535OCXO_IFLAG, + DCFPZF535OCXO_OFLAG, + DCFPZF535OCXO_LFLAG, + DCFPZF535OCXO_SAMPLES, + DCFPZF535OCXO_KEEP + }, + { /* mode 2 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + DCFUA31_ROOTDELAY, + DCFUA31_BASEDELAY, + DCF_A_ID, + DCFUA31_DESCRIPTION, + DCFUA31_FORMAT, + DCF_TYPE, + DCFUA31_MAXUNSYNC, + DCFUA31_SPEED, + DCFUA31_CFLAG, + DCFUA31_IFLAG, + DCFUA31_OFLAG, + DCFUA31_LFLAG, + DCFUA31_SAMPLES, + DCFUA31_KEEP + }, + { /* mode 3 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + DCF7000_ROOTDELAY, + DCF7000_BASEDELAY, + DCF_A_ID, + DCF7000_DESCRIPTION, + DCF7000_FORMAT, + DCF_TYPE, + DCF7000_MAXUNSYNC, + DCF7000_SPEED, + DCF7000_CFLAG, + DCF7000_IFLAG, + DCF7000_OFLAG, + DCF7000_LFLAG, + DCF7000_SAMPLES, + DCF7000_KEEP + }, + { /* mode 4 */ + NO_CL_FLAGS, + WSDCF_POLL, + WSDCF_INIT, + NO_EVENT, + WSDCF_END, + NO_MESSAGE, + WSDCF_DATA, + WSDCF_ROOTDELAY, + WSDCF_BASEDELAY, + DCF_A_ID, + WSDCF_DESCRIPTION, + WSDCF_FORMAT, + DCF_TYPE, + WSDCF_MAXUNSYNC, + WSDCF_SPEED, + WSDCF_CFLAG, + WSDCF_IFLAG, + WSDCF_OFLAG, + WSDCF_LFLAG, + WSDCF_SAMPLES, + WSDCF_KEEP + }, + { /* mode 5 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCF_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + CONRAD_BASEDELAY, + DCF_A_ID, + CONRAD_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 6 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCF_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + TIMEBRICK_BASEDELAY, + DCF_A_ID, + TIMEBRICK_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 7 */ + MBG_FLAGS, + GPS16X_POLL, + GPS16X_INIT, + NO_EVENT, + GPS16X_END, + GPS16X_MESSAGE, + GPS16X_DATA, + GPS16X_ROOTDELAY, + GPS16X_BASEDELAY, + GPS16X_ID, + GPS16X_DESCRIPTION, + GPS16X_FORMAT, + GPS_TYPE, + GPS16X_MAXUNSYNC, + GPS16X_SPEED, + GPS16X_CFLAG, + GPS16X_IFLAG, + GPS16X_OFLAG, + GPS16X_LFLAG, + GPS16X_SAMPLES, + GPS16X_KEEP + }, + { /* mode 8 */ + RAWDCF_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + IGELCLOCK_BASEDELAY, + DCF_A_ID, + IGELCLOCK_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + IGELCLOCK_SPEED, + IGELCLOCK_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 9 */ + TRIMBLETAIP_FLAGS, +#if TRIM_POLLRATE /* DHD940515: Allow user config */ + NO_POLL, +#else + TRIMBLETAIP_POLL, +#endif + TRIMBLETAIP_INIT, + TRIMBLETAIP_EVENT, + TRIMBLETAIP_END, + NO_MESSAGE, + TRIMBLETAIP_DATA, + TRIMBLETAIP_ROOTDELAY, + TRIMBLETAIP_BASEDELAY, + TRIMBLETAIP_ID, + TRIMBLETAIP_DESCRIPTION, + TRIMBLETAIP_FORMAT, + GPS_TYPE, + TRIMBLETAIP_MAXUNSYNC, + TRIMBLETAIP_SPEED, + TRIMBLETAIP_CFLAG, + TRIMBLETAIP_IFLAG, + TRIMBLETAIP_OFLAG, + TRIMBLETAIP_LFLAG, + TRIMBLETAIP_SAMPLES, + TRIMBLETAIP_KEEP + }, + { /* mode 10 */ + TRIMBLETSIP_FLAGS, +#if TRIM_POLLRATE /* DHD940515: Allow user config */ + NO_POLL, +#else + TRIMBLETSIP_POLL, +#endif + TRIMBLETSIP_INIT, + TRIMBLETSIP_EVENT, + TRIMBLETSIP_END, + TRIMBLETSIP_MESSAGE, + TRIMBLETSIP_DATA, + TRIMBLETSIP_ROOTDELAY, + TRIMBLETSIP_BASEDELAY, + TRIMBLETSIP_ID, + TRIMBLETSIP_DESCRIPTION, + TRIMBLETSIP_FORMAT, + GPS_TYPE, + TRIMBLETSIP_MAXUNSYNC, + TRIMBLETSIP_SPEED, + TRIMBLETSIP_CFLAG, + TRIMBLETSIP_IFLAG, + TRIMBLETSIP_OFLAG, + TRIMBLETSIP_LFLAG, + TRIMBLETSIP_SAMPLES, + TRIMBLETSIP_KEEP + }, + { /* mode 11 */ + NO_CL_FLAGS, + RCC8000_POLL, + RCC8000_INIT, + NO_EVENT, + RCC8000_END, + NO_MESSAGE, + RCC8000_DATA, + RCC8000_ROOTDELAY, + RCC8000_BASEDELAY, + RCC8000_ID, + RCC8000_DESCRIPTION, + RCC8000_FORMAT, + DCF_TYPE, + RCC8000_MAXUNSYNC, + RCC8000_SPEED, + RCC8000_CFLAG, + RCC8000_IFLAG, + RCC8000_OFLAG, + RCC8000_LFLAG, + RCC8000_SAMPLES, + RCC8000_KEEP + }, + { /* mode 12 */ + HOPF6021_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + HOPF6021_ROOTDELAY, + HOPF6021_BASEDELAY, + DCF_ID, + HOPF6021_DESCRIPTION, + HOPF6021_FORMAT, + DCF_TYPE, + HOPF6021_MAXUNSYNC, + HOPF6021_SPEED, + HOPF6021_CFLAG, + HOPF6021_IFLAG, + HOPF6021_OFLAG, + HOPF6021_LFLAG, + HOPF6021_SAMPLES, + HOPF6021_KEEP + }, + { /* mode 13 */ + COMPUTIME_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + COMPUTIME_ROOTDELAY, + COMPUTIME_BASEDELAY, + COMPUTIME_ID, + COMPUTIME_DESCRIPTION, + COMPUTIME_FORMAT, + COMPUTIME_TYPE, + COMPUTIME_MAXUNSYNC, + COMPUTIME_SPEED, + COMPUTIME_CFLAG, + COMPUTIME_IFLAG, + COMPUTIME_OFLAG, + COMPUTIME_LFLAG, + COMPUTIME_SAMPLES, + COMPUTIME_KEEP + }, + { /* mode 14 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCFDTRSET_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + RAWDCF_BASEDELAY, + DCF_A_ID, + RAWDCFDTRSET_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 15 */ + 0, /* operation flags (io modes) */ + NO_POLL, /* active poll routine */ + NO_INIT, /* active poll init routine */ + NO_EVENT, /* special event handling (e.g. reset clock) */ + NO_END, /* active poll end routine */ + NO_MESSAGE, /* process a lower layer message */ + NO_LCLDATA, /* local data area for "poll" mechanism */ + 0, /* rootdelay */ + 11.0 /* bits */ / 9600, /* current offset by which the RS232 + time code is delayed from the actual time */ + DCF_ID, /* ID code */ + "WHARTON 400A Series clock", /* device name */ + "WHARTON 400A Series clock Output Format 1", /* fixed format */ + /* Must match a format-name in a libparse/clk_xxx.c file */ + DCF_TYPE, /* clock type (ntp control) */ + (1*60*60), /* time to trust oscillator after losing synch */ + B9600, /* terminal input & output baudrate */ + (CS8|CREAD|PARENB|CLOCAL|HUPCL),/* terminal control flags */ + 0, /* terminal input flags */ + 0, /* terminal output flags */ + 0, /* terminal local flags */ + 5, /* samples for median filter */ + 3, /* samples for median filter to keep */ + }, + { /* mode 16 - RAWDCF RTS set, DTR clr */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCFDTRCLRRTSSET_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + RAWDCF_BASEDELAY, + DCF_A_ID, + RAWDCFDTRCLRRTSSET_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 17 */ + VARITEXT_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + VARITEXT_ROOTDELAY, + VARITEXT_BASEDELAY, + VARITEXT_ID, + VARITEXT_DESCRIPTION, + VARITEXT_FORMAT, + VARITEXT_TYPE, + VARITEXT_MAXUNSYNC, + VARITEXT_SPEED, + VARITEXT_CFLAG, + VARITEXT_IFLAG, + VARITEXT_OFLAG, + VARITEXT_LFLAG, + VARITEXT_SAMPLES, + VARITEXT_KEEP + }, + { /* mode 18 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + GPS16X_END, + GPS16X_MESSAGE, + GPS16X_DATA, + GPS16X_ROOTDELAY, + GPS16X_BASEDELAY, + GPS16X_ID, + GPS16X_DESCRIPTION, + GPS16X_FORMAT, + GPS_TYPE, + GPS16X_MAXUNSYNC, + GPS16X_SPEED, + GPS16X_CFLAG, + GPS16X_IFLAG, + GPS16X_OFLAG, + GPS16X_LFLAG, + GPS16X_SAMPLES, + GPS16X_KEEP + }, + { /* mode 19 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCF_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + GUDE_EMC_USB_V20_BASEDELAY, + DCF_A_ID, + GUDE_EMC_USB_V20_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + GUDE_EMC_USB_V20_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 20, like mode 14 but driven by 75 baud */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCFDTRSET_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + RAWDCF_BASEDELAY, + DCF_A_ID, + RAWDCFDTRSET75_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + B75, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 21, like mode 16 but driven by 75 baud + - RAWDCF RTS set, DTR clr */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCFDTRCLRRTSSET_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + RAWDCF_ROOTDELAY, + RAWDCF_BASEDELAY, + DCF_A_ID, + RAWDCFDTRCLRRTSSET75_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + B75, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 22 - like 2 with POWERUP trust */ + MBG_FLAGS | PARSE_F_POWERUPTRUST, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_LCLDATA, + DCFUA31_ROOTDELAY, + DCFUA31_BASEDELAY, + DCF_A_ID, + DCFUA31_DESCRIPTION, + DCFUA31_FORMAT, + DCF_TYPE, + DCFUA31_MAXUNSYNC, + DCFUA31_SPEED, + DCFUA31_CFLAG, + DCFUA31_IFLAG, + DCFUA31_OFLAG, + DCFUA31_LFLAG, + DCFUA31_SAMPLES, + DCFUA31_KEEP + }, + { /* mode 23 - like 7 with POWERUP trust */ + MBG_FLAGS | PARSE_F_POWERUPTRUST, + GPS16X_POLL, + GPS16X_INIT, + NO_EVENT, + GPS16X_END, + GPS16X_MESSAGE, + GPS16X_DATA, + GPS16X_ROOTDELAY, + GPS16X_BASEDELAY, + GPS16X_ID, + GPS16X_DESCRIPTION, + GPS16X_FORMAT, + GPS_TYPE, + GPS16X_MAXUNSYNC, + GPS16X_SPEED, + GPS16X_CFLAG, + GPS16X_IFLAG, + GPS16X_OFLAG, + GPS16X_LFLAG, + GPS16X_SAMPLES, + GPS16X_KEEP + }, + { /* mode 24 */ + SEL240X_FLAGS, + SEL240X_POLL, + SEL240X_INIT, + NO_EVENT, + SEL240X_END, + NO_MESSAGE, + SEL240X_DATA, + SEL240X_ROOTDELAY, + SEL240X_BASEDELAY, + SEL240X_ID, + SEL240X_DESCRIPTION, + SEL240X_FORMAT, + GPS_TYPE, + SEL240X_MAXUNSYNC, + SEL240X_SPEED, + SEL240X_CFLAG, + SEL240X_IFLAG, + SEL240X_OFLAG, + SEL240X_LFLAG, + SEL240X_SAMPLES, + SEL240X_KEEP + }, +}; + +static int ncltypes = sizeof(parse_clockinfo) / sizeof(struct parse_clockinfo); + +#define CLK_REALTYPE(x) ((int)(((x)->ttl) & 0x7F)) +#define CLK_TYPE(x) ((CLK_REALTYPE(x) >= ncltypes) ? ~0 : CLK_REALTYPE(x)) +#define CLK_UNIT(x) ((int)REFCLOCKUNIT(&(x)->srcadr)) +#define CLK_PPS(x) (((x)->ttl) & 0x80) + +/* + * Other constant stuff + */ +#define PARSEHSREFID 0x7f7f08ff /* 127.127.8.255 refid for hi strata */ + +#define PARSESTATISTICS (60*60) /* output state statistics every hour */ + +static int notice = 0; + +#define PARSE_STATETIME(parse, i) ((parse->generic->currentstatus == i) ? parse->statetime[i] + current_time - parse->lastchange : parse->statetime[i]) + +static void parse_event (struct parseunit *, int); +static void parse_process (struct parseunit *, parsetime_t *); +static void clear_err (struct parseunit *, u_long); +static int list_err (struct parseunit *, u_long); +static char * l_mktime (u_long); + +/**=========================================================================== + ** implementation error message regression module + **/ +static void +clear_err( + struct parseunit *parse, + u_long lstate + ) +{ + if (lstate == ERR_ALL) + { + size_t i; + + for (i = 0; i < ERR_CNT; i++) + { + parse->errors[i].err_stage = err_tbl[i]; + parse->errors[i].err_cnt = 0; + parse->errors[i].err_last = 0; + parse->errors[i].err_started = 0; + parse->errors[i].err_suppressed = 0; + } + } + else + { + parse->errors[lstate].err_stage = err_tbl[lstate]; + parse->errors[lstate].err_cnt = 0; + parse->errors[lstate].err_last = 0; + parse->errors[lstate].err_started = 0; + parse->errors[lstate].err_suppressed = 0; + } +} + +static int +list_err( + struct parseunit *parse, + u_long lstate + ) +{ + int do_it; + struct errorinfo *err = &parse->errors[lstate]; + + if (err->err_started == 0) + { + err->err_started = current_time; + } + + do_it = (current_time - err->err_last) >= err->err_stage->err_delay; + + if (do_it) + err->err_cnt++; + + if (err->err_stage->err_count && + (err->err_cnt >= err->err_stage->err_count)) + { + err->err_stage++; + err->err_cnt = 0; + } + + if (!err->err_cnt && do_it) + msyslog(LOG_INFO, "PARSE receiver #%d: interval for following error message class is at least %s", + CLK_UNIT(parse->peer), l_mktime(err->err_stage->err_delay)); + + if (!do_it) + err->err_suppressed++; + else + err->err_last = current_time; + + if (do_it && err->err_suppressed) + { + msyslog(LOG_INFO, "PARSE receiver #%d: %ld message%s suppressed, error condition class persists for %s", + CLK_UNIT(parse->peer), err->err_suppressed, (err->err_suppressed == 1) ? " was" : "s where", + l_mktime(current_time - err->err_started)); + err->err_suppressed = 0; + } + + return do_it; +} + +/*-------------------------------------------------- + * mkreadable - make a printable ascii string (without + * embedded quotes so that the ntpq protocol isn't + * fooled + */ +#ifndef isprint +#define isprint(_X_) (((_X_) > 0x1F) && ((_X_) < 0x7F)) +#endif + +static char * +mkreadable( + char *buffer, + size_t blen, + const char *src, + size_t srclen, + int hex + ) +{ + static const char ellipsis[] = "..."; + char *b = buffer; + char *endb = NULL; + + if (blen < 4) + return NULL; /* don't bother with mini buffers */ + + endb = buffer + blen - sizeof(ellipsis); + + blen--; /* account for '\0' */ + + while (blen && srclen--) + { + if (!hex && /* no binary only */ + (*src != '\\') && /* no plain \ */ + (*src != '"') && /* no " */ + isprint((unsigned char)*src)) /* only printables */ + { /* they are easy... */ + *buffer++ = *src++; + blen--; + } + else + { + if (blen < 4) + { + while (blen--) + { + *buffer++ = '.'; + } + *buffer = '\0'; + return b; + } + else + { + if (*src == '\\') + { + memcpy(buffer, "\\\\", 2); + buffer += 2; + blen -= 2; + src++; + } + else + { + snprintf(buffer, blen, "\\x%02x", *src++); + blen -= 4; + buffer += 4; + } + } + } + if (srclen && !blen && endb) /* overflow - set last chars to ... */ + memcpy(endb, ellipsis, sizeof(ellipsis)); + } + + *buffer = '\0'; + return b; +} + + +/*-------------------------------------------------- + * mkascii - make a printable ascii string + * assumes (unless defined better) 7-bit ASCII + */ +static char * +mkascii( + char *buffer, + long blen, + const char *src, + u_long srclen + ) +{ + return mkreadable(buffer, blen, src, srclen, 0); +} + +/**=========================================================================== + ** implementation of i/o handling methods + ** (all STREAM, partial STREAM, user level) + **/ + +/* + * define possible io handling methods + */ +#ifdef STREAM +static int ppsclock_init (struct parseunit *); +static int stream_init (struct parseunit *); +static void stream_end (struct parseunit *); +static int stream_enable (struct parseunit *); +static int stream_disable (struct parseunit *); +static int stream_setcs (struct parseunit *, parsectl_t *); +static int stream_getfmt (struct parseunit *, parsectl_t *); +static int stream_setfmt (struct parseunit *, parsectl_t *); +static int stream_timecode (struct parseunit *, parsectl_t *); +static void stream_receive (struct recvbuf *); +#endif + +static int local_init (struct parseunit *); +static void local_end (struct parseunit *); +static int local_nop (struct parseunit *); +static int local_setcs (struct parseunit *, parsectl_t *); +static int local_getfmt (struct parseunit *, parsectl_t *); +static int local_setfmt (struct parseunit *, parsectl_t *); +static int local_timecode (struct parseunit *, parsectl_t *); +static void local_receive (struct recvbuf *); +static int local_input (struct recvbuf *); + +static bind_t io_bindings[] = +{ +#ifdef STREAM + { + "parse STREAM", + stream_init, + stream_end, + stream_setcs, + stream_disable, + stream_enable, + stream_getfmt, + stream_setfmt, + stream_timecode, + stream_receive, + 0, + }, + { + "ppsclock STREAM", + ppsclock_init, + local_end, + local_setcs, + local_nop, + local_nop, + local_getfmt, + local_setfmt, + local_timecode, + local_receive, + local_input, + }, +#endif + { + "normal", + local_init, + local_end, + local_setcs, + local_nop, + local_nop, + local_getfmt, + local_setfmt, + local_timecode, + local_receive, + local_input, + }, + { + (char *)0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + } +}; + +#ifdef STREAM + +/*-------------------------------------------------- + * ppsclock STREAM init + */ +static int +ppsclock_init( + struct parseunit *parse + ) +{ + static char m1[] = "ppsclocd"; + static char m2[] = "ppsclock"; + + /* + * now push the parse streams module + * it will ensure exclusive access to the device + */ + if (ioctl(parse->ppsfd, I_PUSH, (caddr_t)m1) == -1 && + ioctl(parse->ppsfd, I_PUSH, (caddr_t)m2) == -1) + { + if (errno != EINVAL) + { + msyslog(LOG_ERR, "PARSE receiver #%d: ppsclock_init: ioctl(fd, I_PUSH, \"ppsclock\"): %m", + CLK_UNIT(parse->peer)); + } + return 0; + } + if (!local_init(parse)) + { + (void)ioctl(parse->ppsfd, I_POP, (caddr_t)0); + return 0; + } + + parse->flags |= PARSE_PPSCLOCK; + return 1; +} + +/*-------------------------------------------------- + * parse STREAM init + */ +static int +stream_init( + struct parseunit *parse + ) +{ + static char m1[] = "parse"; + /* + * now push the parse streams module + * to test whether it is there (neat interface 8-( ) + */ + if (ioctl(parse->generic->io.fd, I_PUSH, (caddr_t)m1) == -1) + { + if (errno != EINVAL) /* accept non-existence */ + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_init: ioctl(fd, I_PUSH, \"parse\"): %m", CLK_UNIT(parse->peer)); + } + return 0; + } + else + { + while(ioctl(parse->generic->io.fd, I_POP, (caddr_t)0) == 0) + /* empty loop */; + + /* + * now push it a second time after we have removed all + * module garbage + */ + if (ioctl(parse->generic->io.fd, I_PUSH, (caddr_t)m1) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_init: ioctl(fd, I_PUSH, \"parse\"): %m", CLK_UNIT(parse->peer)); + return 0; + } + else + { + return 1; + } + } +} + +/*-------------------------------------------------- + * parse STREAM end + */ +static void +stream_end( + struct parseunit *parse + ) +{ + while(ioctl(parse->generic->io.fd, I_POP, (caddr_t)0) == 0) + /* empty loop */; +} + +/*-------------------------------------------------- + * STREAM setcs + */ +static int +stream_setcs( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_SETCS; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_setcs: ioctl(fd, I_STR, PARSEIOC_SETCS): %m", CLK_UNIT(parse->peer)); + return 0; + } + return 1; +} + +/*-------------------------------------------------- + * STREAM enable + */ +static int +stream_enable( + struct parseunit *parse + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_ENABLE; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)0; + strioc.ic_len = 0; + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_enable: ioctl(fd, I_STR, PARSEIOC_ENABLE): %m", CLK_UNIT(parse->peer)); + return 0; + } + parse->generic->io.clock_recv = stream_receive; /* ok - parse input in kernel */ + return 1; +} + +/*-------------------------------------------------- + * STREAM disable + */ +static int +stream_disable( + struct parseunit *parse + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_DISABLE; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)0; + strioc.ic_len = 0; + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_disable: ioctl(fd, I_STR, PARSEIOC_DISABLE): %m", CLK_UNIT(parse->peer)); + return 0; + } + parse->generic->io.clock_recv = local_receive; /* ok - parse input in daemon */ + return 1; +} + +/*-------------------------------------------------- + * STREAM getfmt + */ +static int +stream_getfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_GETFMT; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: ioctl(fd, I_STR, PARSEIOC_GETFMT): %m", CLK_UNIT(parse->peer)); + return 0; + } + return 1; +} + +/*-------------------------------------------------- + * STREAM setfmt + */ +static int +stream_setfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_SETFMT; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_setfmt: ioctl(fd, I_STR, PARSEIOC_SETFMT): %m", CLK_UNIT(parse->peer)); + return 0; + } + return 1; +} + + +/*-------------------------------------------------- + * STREAM timecode + */ +static int +stream_timecode( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_TIMECODE; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: stream_timecode: ioctl(fd, I_STR, PARSEIOC_TIMECODE): %m", CLK_UNIT(parse->peer)); + return 0; + } + clear_err(parse, ERR_INTERNAL); + return 1; +} + +/*-------------------------------------------------- + * STREAM receive + */ +static void +stream_receive( + struct recvbuf *rbufp + ) +{ + struct parseunit * parse; + parsetime_t parsetime; + + parse = (struct parseunit *)rbufp->recv_peer->procptr->unitptr; + if (!parse->peer) + return; + + if (rbufp->recv_length != sizeof(parsetime_t)) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR,"PARSE receiver #%d: stream_receive: bad size (got %d expected %d)", + CLK_UNIT(parse->peer), rbufp->recv_length, (int)sizeof(parsetime_t)); + parse_event(parse, CEVNT_BADREPLY); + return; + } + clear_err(parse, ERR_BADIO); + + memmove((caddr_t)&parsetime, + (caddr_t)rbufp->recv_buffer, + sizeof(parsetime_t)); + +#ifdef DEBUG + if (debug > 3) + { + printf("PARSE receiver #%d: status %06x, state %08x, time %lx.%08lx, stime %lx.%08lx, ptime %lx.%08lx\n", + CLK_UNIT(parse->peer), + (unsigned int)parsetime.parse_status, + (unsigned int)parsetime.parse_state, + (unsigned long)parsetime.parse_time.tv.tv_sec, + (unsigned long)parsetime.parse_time.tv.tv_usec, + (unsigned long)parsetime.parse_stime.tv.tv_sec, + (unsigned long)parsetime.parse_stime.tv.tv_usec, + (unsigned long)parsetime.parse_ptime.tv.tv_sec, + (unsigned long)parsetime.parse_ptime.tv.tv_usec); + } +#endif + + /* + * switch time stamp world - be sure to normalize small usec field + * errors. + */ + + parsetime.parse_stime.fp = tval_stamp_to_lfp(parsetime.parse_stime.tv); + + if (PARSE_TIMECODE(parsetime.parse_state)) + { + parsetime.parse_time.fp = tval_stamp_to_lfp(parsetime.parse_time.tv); + } + + if (PARSE_PPS(parsetime.parse_state)) + { + parsetime.parse_ptime.fp = tval_stamp_to_lfp(parsetime.parse_ptime.tv); + } + + parse_process(parse, &parsetime); +} +#endif + +/*-------------------------------------------------- + * local init + */ +static int +local_init( + struct parseunit *parse + ) +{ + return parse_ioinit(&parse->parseio); +} + +/*-------------------------------------------------- + * local end + */ +static void +local_end( + struct parseunit *parse + ) +{ + parse_ioend(&parse->parseio); +} + + +/*-------------------------------------------------- + * local nop + */ +static int +local_nop( + struct parseunit *parse + ) +{ + return 1; +} + +/*-------------------------------------------------- + * local setcs + */ +static int +local_setcs( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_setcs(tcl, &parse->parseio); +} + +/*-------------------------------------------------- + * local getfmt + */ +static int +local_getfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_getfmt(tcl, &parse->parseio); +} + +/*-------------------------------------------------- + * local setfmt + */ +static int +local_setfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_setfmt(tcl, &parse->parseio); +} + +/*-------------------------------------------------- + * local timecode + */ +static int +local_timecode( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_timecode(tcl, &parse->parseio); +} + + +/*-------------------------------------------------- + * local input + */ +static int +local_input( + struct recvbuf *rbufp + ) +{ + struct parseunit * parse; + + int count; + unsigned char *s; + timestamp_t ts; + + parse = (struct parseunit *)rbufp->recv_peer->procptr->unitptr; + if (!parse->peer) + return 0; + + /* + * eat all characters, parsing then and feeding complete samples + */ + count = rbufp->recv_length; + s = (unsigned char *)rbufp->recv_buffer; + ts.fp = rbufp->recv_time; + + while (count--) + { + if (parse_ioread(&parse->parseio, (unsigned int)(*s++), &ts)) + { + struct recvbuf *buf; + + /* + * got something good to eat + */ + if (!PARSE_PPS(parse->parseio.parse_dtime.parse_state)) + { +#ifdef HAVE_PPSAPI + if (parse->flags & PARSE_PPSCLOCK) + { + struct timespec pps_timeout; + pps_info_t pps_info; + + pps_timeout.tv_sec = 0; + pps_timeout.tv_nsec = 0; + + if (time_pps_fetch(parse->atom.handle, PPS_TSFMT_TSPEC, &pps_info, + &pps_timeout) == 0) + { + if (pps_info.assert_sequence + pps_info.clear_sequence != parse->ppsserial) + { + double dtemp; + + struct timespec pts; + /* + * add PPS time stamp if available via ppsclock module + * and not supplied already. + */ + if (parse->flags & PARSE_CLEAR) + pts = pps_info.clear_timestamp; + else + pts = pps_info.assert_timestamp; + + parse->parseio.parse_dtime.parse_ptime.fp.l_ui = (uint32_t) (pts.tv_sec + JAN_1970); + + dtemp = (double) pts.tv_nsec / 1e9; + if (dtemp < 0.) { + dtemp += 1; + parse->parseio.parse_dtime.parse_ptime.fp.l_ui--; + } + if (dtemp > 1.) { + dtemp -= 1; + parse->parseio.parse_dtime.parse_ptime.fp.l_ui++; + } + parse->parseio.parse_dtime.parse_ptime.fp.l_uf = (uint32_t)(dtemp * FRAC); + + parse->parseio.parse_dtime.parse_state |= PARSEB_PPS|PARSEB_S_PPS; +#ifdef DEBUG + if (debug > 3) + { + printf( + "parse: local_receive: fd %ld PPSAPI seq %ld - PPS %s\n", + (long)rbufp->fd, + (long)pps_info.assert_sequence + (long)pps_info.clear_sequence, + lfptoa(&parse->parseio.parse_dtime.parse_ptime.fp, 6)); + } +#endif + } +#ifdef DEBUG + else + { + if (debug > 3) + { + printf( + "parse: local_receive: fd %ld PPSAPI seq assert %ld, seq clear %ld - NO PPS event\n", + (long)rbufp->fd, + (long)pps_info.assert_sequence, (long)pps_info.clear_sequence); + } + } +#endif + parse->ppsserial = pps_info.assert_sequence + pps_info.clear_sequence; + } +#ifdef DEBUG + else + { + if (debug > 3) + { + printf( + "parse: local_receive: fd %ld PPSAPI time_pps_fetch errno = %d\n", + (long)rbufp->fd, + errno); + } + } +#endif + } +#else +#ifdef TIOCDCDTIMESTAMP + struct timeval dcd_time; + + if (ioctl(parse->ppsfd, TIOCDCDTIMESTAMP, &dcd_time) != -1) + { + l_fp tstmp; + + TVTOTS(&dcd_time, &tstmp); + tstmp.l_ui += JAN_1970; + L_SUB(&ts.fp, &tstmp); + if (ts.fp.l_ui == 0) + { +#ifdef DEBUG + if (debug) + { + printf( + "parse: local_receive: fd %d DCDTIMESTAMP %s\n", + parse->ppsfd, + lfptoa(&tstmp, 6)); + printf(" sigio %s\n", + lfptoa(&ts.fp, 6)); + } +#endif + parse->parseio.parse_dtime.parse_ptime.fp = tstmp; + parse->parseio.parse_dtime.parse_state |= PARSEB_PPS|PARSEB_S_PPS; + } + } +#else /* TIOCDCDTIMESTAMP */ +#if defined(HAVE_STRUCT_PPSCLOCKEV) && (defined(HAVE_CIOGETEV) || defined(HAVE_TIOCGPPSEV)) + if (parse->flags & PARSE_PPSCLOCK) + { + l_fp tts; + struct ppsclockev ev; + +#ifdef HAVE_CIOGETEV + if (ioctl(parse->ppsfd, CIOGETEV, (caddr_t)&ev) == 0) +#endif +#ifdef HAVE_TIOCGPPSEV + if (ioctl(parse->ppsfd, TIOCGPPSEV, (caddr_t)&ev) == 0) +#endif + { + if (ev.serial != parse->ppsserial) + { + /* + * add PPS time stamp if available via ppsclock module + * and not supplied already. + */ + if (!buftvtots((const char *)&ev.tv, &tts)) + { + ERR(ERR_BADDATA) + msyslog(LOG_ERR,"parse: local_receive: timestamp conversion error (buftvtots) (ppsclockev.tv)"); + } + else + { + parse->parseio.parse_dtime.parse_ptime.fp = tts; + parse->parseio.parse_dtime.parse_state |= PARSEB_PPS|PARSEB_S_PPS; + } + } + parse->ppsserial = ev.serial; + } + } +#endif +#endif /* TIOCDCDTIMESTAMP */ +#endif /* !HAVE_PPSAPI */ + } + if (count) + { /* simulate receive */ + buf = get_free_recv_buffer(); + if (buf != NULL) { + memmove((caddr_t)buf->recv_buffer, + (caddr_t)&parse->parseio.parse_dtime, + sizeof(parsetime_t)); + buf->recv_length = sizeof(parsetime_t); + buf->recv_time = rbufp->recv_time; +#ifndef HAVE_IO_COMPLETION_PORT + buf->srcadr = rbufp->srcadr; +#endif + buf->dstadr = rbufp->dstadr; + buf->receiver = rbufp->receiver; + buf->fd = rbufp->fd; + buf->X_from_where = rbufp->X_from_where; + parse->generic->io.recvcount++; + packets_received++; + add_full_recv_buffer(buf); +#ifdef HAVE_IO_COMPLETION_PORT + SetEvent(WaitableIoEventHandle); +#endif + } + parse_iodone(&parse->parseio); + } + else + { + memmove((caddr_t)rbufp->recv_buffer, + (caddr_t)&parse->parseio.parse_dtime, + sizeof(parsetime_t)); + parse_iodone(&parse->parseio); + rbufp->recv_length = sizeof(parsetime_t); + return 1; /* got something & in place return */ + } + } + } + return 0; /* nothing to pass up */ +} + +/*-------------------------------------------------- + * local receive + */ +static void +local_receive( + struct recvbuf *rbufp + ) +{ + struct parseunit * parse; + parsetime_t parsetime; + + parse = (struct parseunit *)rbufp->recv_peer->procptr->unitptr; + if (!parse->peer) + return; + + if (rbufp->recv_length != sizeof(parsetime_t)) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR,"PARSE receiver #%d: local_receive: bad size (got %d expected %d)", + CLK_UNIT(parse->peer), rbufp->recv_length, (int)sizeof(parsetime_t)); + parse_event(parse, CEVNT_BADREPLY); + return; + } + clear_err(parse, ERR_BADIO); + + memmove((caddr_t)&parsetime, + (caddr_t)rbufp->recv_buffer, + sizeof(parsetime_t)); + +#ifdef DEBUG + if (debug > 3) + { + printf("PARSE receiver #%d: status %06x, state %08x, time(fp) %lx.%08lx, stime(fp) %lx.%08lx, ptime(fp) %lx.%08lx\n", + CLK_UNIT(parse->peer), + (unsigned int)parsetime.parse_status, + (unsigned int)parsetime.parse_state, + (unsigned long)parsetime.parse_time.fp.l_ui, + (unsigned long)parsetime.parse_time.fp.l_uf, + (unsigned long)parsetime.parse_stime.fp.l_ui, + (unsigned long)parsetime.parse_stime.fp.l_uf, + (unsigned long)parsetime.parse_ptime.fp.l_ui, + (unsigned long)parsetime.parse_ptime.fp.l_uf); + } +#endif + + parse_process(parse, &parsetime); +} + +/*-------------------------------------------------- + * init_iobinding - find and initialize lower layers + */ +static bind_t * +init_iobinding( + struct parseunit *parse + ) +{ + bind_t *b = io_bindings; + + while (b->bd_description != (char *)0) + { + if ((*b->bd_init)(parse)) + { + return b; + } + b++; + } + return (bind_t *)0; +} + +/**=========================================================================== + ** support routines + **/ + +static NTP_PRINTF(4, 5) char * +ap(char *buffer, size_t len, char *pos, const char *fmt, ...) +{ + va_list va; + int l; + size_t rem = len - (pos - buffer); + + if (rem == 0) + return pos; + + va_start(va, fmt); + l = vsnprintf(pos, rem, fmt, va); + va_end(va); + + if (l != -1) { + rem--; + if (rem >= (size_t)l) + pos += l; + else + pos += rem; + } + + return pos; +} + +/*-------------------------------------------------- + * convert a flag field to a string + */ +static char * +parsestate( + u_long lstate, + char *buffer, + int size + ) +{ + static struct bits + { + u_long bit; + const char *name; + } flagstrings[] = + { + { PARSEB_ANNOUNCE, "DST SWITCH WARNING" }, + { PARSEB_POWERUP, "NOT SYNCHRONIZED" }, + { PARSEB_NOSYNC, "TIME CODE NOT CONFIRMED" }, + { PARSEB_DST, "DST" }, + { PARSEB_UTC, "UTC DISPLAY" }, + { PARSEB_LEAPADD, "LEAP ADD WARNING" }, + { PARSEB_LEAPDEL, "LEAP DELETE WARNING" }, + { PARSEB_LEAPSECOND, "LEAP SECOND" }, + { PARSEB_CALLBIT, "CALL BIT" }, + { PARSEB_TIMECODE, "TIME CODE" }, + { PARSEB_PPS, "PPS" }, + { PARSEB_POSITION, "POSITION" }, + { 0, NULL } + }; + + static struct sbits + { + u_long bit; + const char *name; + } sflagstrings[] = + { + { PARSEB_S_LEAP, "LEAP INDICATION" }, + { PARSEB_S_PPS, "PPS SIGNAL" }, + { PARSEB_S_CALLBIT, "CALLBIT" }, + { PARSEB_S_POSITION, "POSITION" }, + { 0, NULL } + }; + int i; + char *s, *t; + + *buffer = '\0'; + s = t = buffer; + + i = 0; + while (flagstrings[i].bit) + { + if (flagstrings[i].bit & lstate) + { + if (s != t) + t = ap(buffer, size, t, "; "); + t = ap(buffer, size, t, "%s", flagstrings[i].name); + } + i++; + } + + if (lstate & (PARSEB_S_LEAP|PARSEB_S_CALLBIT|PARSEB_S_PPS|PARSEB_S_POSITION)) + { + if (s != t) + t = ap(buffer, size, t, "; "); + + t = ap(buffer, size, t, "("); + + s = t; + + i = 0; + while (sflagstrings[i].bit) + { + if (sflagstrings[i].bit & lstate) + { + if (t != s) + { + t = ap(buffer, size, t, "; "); + } + + t = ap(buffer, size, t, "%s", + sflagstrings[i].name); + } + i++; + } + t = ap(buffer, size, t, ")"); + /* t is unused here, but if we don't track it and + * need it later, that's a bug waiting to happen. + */ + } + return buffer; +} + +/*-------------------------------------------------- + * convert a status flag field to a string + */ +static char * +parsestatus( + u_long lstate, + char *buffer, + int size + ) +{ + static struct bits + { + u_long bit; + const char *name; + } flagstrings[] = + { + { CVT_OK, "CONVERSION SUCCESSFUL" }, + { CVT_NONE, "NO CONVERSION" }, + { CVT_FAIL, "CONVERSION FAILED" }, + { CVT_BADFMT, "ILLEGAL FORMAT" }, + { CVT_BADDATE, "DATE ILLEGAL" }, + { CVT_BADTIME, "TIME ILLEGAL" }, + { CVT_ADDITIONAL, "ADDITIONAL DATA" }, + { 0, NULL } + }; + int i; + char *t; + + t = buffer; + *buffer = '\0'; + + i = 0; + while (flagstrings[i].bit) + { + if (flagstrings[i].bit & lstate) + { + if (t != buffer) + t = ap(buffer, size, t, "; "); + t = ap(buffer, size, t, "%s", flagstrings[i].name); + } + i++; + } + + return buffer; +} + +/*-------------------------------------------------- + * convert a clock status flag field to a string + */ +static const char * +clockstatus( + u_long lstate + ) +{ + static char buffer[20]; + static struct status + { + u_long value; + const char *name; + } flagstrings[] = + { + { CEVNT_NOMINAL, "NOMINAL" }, + { CEVNT_TIMEOUT, "NO RESPONSE" }, + { CEVNT_BADREPLY,"BAD FORMAT" }, + { CEVNT_FAULT, "FAULT" }, + { CEVNT_PROP, "PROPAGATION DELAY" }, + { CEVNT_BADDATE, "ILLEGAL DATE" }, + { CEVNT_BADTIME, "ILLEGAL TIME" }, + { (unsigned)~0L, NULL } + }; + int i; + + i = 0; + while (flagstrings[i].value != (u_int)~0) + { + if (flagstrings[i].value == lstate) + { + return flagstrings[i].name; + } + i++; + } + + snprintf(buffer, sizeof(buffer), "unknown #%ld", (u_long)lstate); + + return buffer; +} + + +/*-------------------------------------------------- + * l_mktime - make representation of a relative time + */ +static char * +l_mktime( + u_long delta + ) +{ + u_long tmp, m, s; + static char buffer[40]; + char *t; + + buffer[0] = '\0'; + t = buffer; + + if ((tmp = delta / (60*60*24)) != 0) + { + t = ap(buffer, sizeof(buffer), t, "%ldd+", (u_long)tmp); + delta -= tmp * 60*60*24; + } + + s = delta % 60; + delta /= 60; + m = delta % 60; + delta /= 60; + + t = ap(buffer, sizeof(buffer), t, "%02d:%02d:%02d", + (int)delta, (int)m, (int)s); + + return buffer; +} + + +/*-------------------------------------------------- + * parse_statistics - list summary of clock states + */ +static void +parse_statistics( + struct parseunit *parse + ) +{ + int i; + + NLOG(NLOG_CLOCKSTATIST) /* conditional if clause for conditional syslog */ + { + msyslog(LOG_INFO, "PARSE receiver #%d: running time: %s", + CLK_UNIT(parse->peer), + l_mktime(current_time - parse->generic->timestarted)); + + msyslog(LOG_INFO, "PARSE receiver #%d: current status: %s", + CLK_UNIT(parse->peer), + clockstatus(parse->generic->currentstatus)); + + for (i = 0; i <= CEVNT_MAX; i++) + { + u_long s_time; + u_long percent, d = current_time - parse->generic->timestarted; + + percent = s_time = PARSE_STATETIME(parse, i); + + while (((u_long)(~0) / 10000) < percent) + { + percent /= 10; + d /= 10; + } + + if (d) + percent = (percent * 10000) / d; + else + percent = 10000; + + if (s_time) + msyslog(LOG_INFO, "PARSE receiver #%d: state %18s: %13s (%3ld.%02ld%%)", + CLK_UNIT(parse->peer), + clockstatus((unsigned int)i), + l_mktime(s_time), + percent / 100, percent % 100); + } + } +} + +/*-------------------------------------------------- + * cparse_statistics - wrapper for statistics call + */ +static void +cparse_statistics( + struct parseunit *parse + ) +{ + if (parse->laststatistic + PARSESTATISTICS < current_time) + parse_statistics(parse); + parse->laststatistic = current_time; +} + +/**=========================================================================== + ** ntp interface routines + **/ + +/*-------------------------------------------------- + * parse_shutdown - shut down a PARSE clock + */ +static void +parse_shutdown( + int unit, + struct peer *peer + ) +{ + struct parseunit *parse = NULL; + + if (peer && peer->procptr) + parse = peer->procptr->unitptr; + + if (!parse) + { + /* nothing to clean up */ + return; + } + + if (!parse->peer) + { + msyslog(LOG_INFO, "PARSE receiver #%d: INTERNAL ERROR - unit already inactive - shutdown ignored", unit); + return; + } + +#ifdef HAVE_PPSAPI + if (parse->flags & PARSE_PPSCLOCK) + { + (void)time_pps_destroy(parse->atom.handle); + } +#endif + if (parse->generic->io.fd != parse->ppsfd && parse->ppsfd != -1) + (void)closeserial(parse->ppsfd); /* close separate PPS source */ + + /* + * print statistics a last time and + * stop statistics machine + */ + parse_statistics(parse); + + if (parse->parse_type->cl_end) + { + parse->parse_type->cl_end(parse); + } + + /* + * cleanup before leaving this world + */ + if (parse->binding) + PARSE_END(parse); + + /* + * Tell the I/O module to turn us off. We're history. + */ + io_closeclock(&parse->generic->io); + + free_varlist(parse->kv); + + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "PARSE receiver #%d: reference clock \"%s\" removed", + CLK_UNIT(parse->peer), parse->parse_type->cl_description); + + parse->peer = (struct peer *)0; /* unused now */ + peer->procptr->unitptr = (caddr_t)0; + free(parse); +} + +#ifdef HAVE_PPSAPI +/*---------------------------------------- + * set up HARDPPS via PPSAPI + */ +static void +parse_hardpps( + struct parseunit *parse, + int mode + ) +{ + if (parse->hardppsstate == mode) + return; + + if (CLK_PPS(parse->peer) && (parse->flags & PARSE_PPSKERNEL)) { + int i = 0; + + if (mode == PARSE_HARDPPS_ENABLE) + { + if (parse->flags & PARSE_CLEAR) + i = PPS_CAPTURECLEAR; + else + i = PPS_CAPTUREASSERT; + } + + if (time_pps_kcbind(parse->atom.handle, PPS_KC_HARDPPS, i, + PPS_TSFMT_TSPEC) < 0) { + msyslog(LOG_ERR, "PARSE receiver #%d: time_pps_kcbind failed: %m", + CLK_UNIT(parse->peer)); + } else { + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_INFO, "PARSE receiver #%d: kernel PPS synchronisation %sabled", + CLK_UNIT(parse->peer), (mode == PARSE_HARDPPS_ENABLE) ? "en" : "dis"); + /* + * tell the rest, that we have a kernel PPS source, iff we ever enable HARDPPS + */ + if (mode == PARSE_HARDPPS_ENABLE) + hardpps_enable = 1; + } + } + + parse->hardppsstate = mode; +} + +/*---------------------------------------- + * set up PPS via PPSAPI + */ +static int +parse_ppsapi( + struct parseunit *parse + ) +{ + int cap, mode_ppsoffset; + const char *cp; + + parse->flags &= (u_char) (~PARSE_PPSCLOCK); + + /* + * collect PPSAPI offset capability - should move into generic handling + */ + if (time_pps_getcap(parse->atom.handle, &cap) < 0) { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_ppsapi: time_pps_getcap failed: %m", + CLK_UNIT(parse->peer)); + + return 0; + } + + /* + * initialize generic PPSAPI interface + * + * we leave out CLK_FLAG3 as time_pps_kcbind() + * is handled here for now. Ideally this should also + * be part of the generic PPSAPI interface + */ + if (!refclock_params(parse->flags & (CLK_FLAG1|CLK_FLAG2|CLK_FLAG4), &parse->atom)) + return 0; + + /* nb. only turn things on, if someone else has turned something + * on before we get here, leave it alone! + */ + + if (parse->flags & PARSE_CLEAR) { + cp = "CLEAR"; + mode_ppsoffset = PPS_OFFSETCLEAR; + } else { + cp = "ASSERT"; + mode_ppsoffset = PPS_OFFSETASSERT; + } + + msyslog(LOG_INFO, "PARSE receiver #%d: initializing PPS to %s", + CLK_UNIT(parse->peer), cp); + + if (!(mode_ppsoffset & cap)) { + msyslog(LOG_WARNING, "PARSE receiver #%d: Cannot set PPS_%sCLEAR, this will increase jitter (PPS API capabilities=0x%x)", + CLK_UNIT(parse->peer), cp, cap); + mode_ppsoffset = 0; + } else { + if (mode_ppsoffset == PPS_OFFSETCLEAR) + { + parse->atom.pps_params.clear_offset.tv_sec = (time_t)(-parse->ppsphaseadjust); + parse->atom.pps_params.clear_offset.tv_nsec = (long)(-1e9*(parse->ppsphaseadjust - (double)(long)parse->ppsphaseadjust)); + } + + if (mode_ppsoffset == PPS_OFFSETASSERT) + { + parse->atom.pps_params.assert_offset.tv_sec = (time_t)(-parse->ppsphaseadjust); + parse->atom.pps_params.assert_offset.tv_nsec = (long)(-1e9*(parse->ppsphaseadjust - (double)(long)parse->ppsphaseadjust)); + } + } + + parse->atom.pps_params.mode |= mode_ppsoffset; + + if (time_pps_setparams(parse->atom.handle, &parse->atom.pps_params) < 0) { + msyslog(LOG_ERR, "PARSE receiver #%d: FAILED set PPS parameters: %m", + CLK_UNIT(parse->peer)); + return 0; + } + + parse->flags |= PARSE_PPSCLOCK; + return 1; +} +#else +#define parse_hardpps(_PARSE_, _MODE_) /* empty */ +#endif + +/*-------------------------------------------------- + * parse_start - open the PARSE devices and initialize data for processing + */ +static int +parse_start( + int sysunit, + struct peer *peer + ) +{ + u_int unit; + int fd232; +#ifdef HAVE_TERMIOS + struct termios tio; /* NEEDED FOR A LONG TIME ! */ +#endif +#ifdef HAVE_SYSV_TTYS + struct termio tio; /* NEEDED FOR A LONG TIME ! */ +#endif + struct parseunit * parse; + char parsedev[sizeof(PARSEDEVICE)+20]; + char parseppsdev[sizeof(PARSEPPSDEVICE)+20]; + parsectl_t tmp_ctl; + u_int type; + + /* + * get out Copyright information once + */ + if (!notice) + { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "NTP PARSE support: Copyright (c) 1989-2015, Frank Kardel"); + notice = 1; + } + + type = CLK_TYPE(peer); + unit = CLK_UNIT(peer); + + if ((type == (u_int)~0) || (parse_clockinfo[type].cl_description == (char *)0)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: unsupported clock type %d (max %d)", + unit, CLK_REALTYPE(peer), ncltypes-1); + return 0; + } + + /* + * Unit okay, attempt to open the device. + */ + (void) snprintf(parsedev, sizeof(parsedev), PARSEDEVICE, unit); + (void) snprintf(parseppsdev, sizeof(parsedev), PARSEPPSDEVICE, unit); + +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif +#ifndef O_NONBLOCK +#define O_NONBLOCK 0 +#endif + + fd232 = tty_open(parsedev, O_RDWR | O_NOCTTY | O_NONBLOCK, 0777); + + if (fd232 == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: open of %s failed: %m", unit, parsedev); + return 0; + } + + parse = emalloc_zero(sizeof(*parse)); + + parse->generic = peer->procptr; /* link up */ + parse->generic->unitptr = (caddr_t)parse; /* link down */ + + /* + * Set up the structures + */ + parse->generic->timestarted = current_time; + parse->lastchange = current_time; + + parse->flags = 0; + parse->pollneeddata = 0; + parse->laststatistic = current_time; + parse->lastformat = (unsigned short)~0; /* assume no format known */ + parse->timedata.parse_status = (unsigned short)~0; /* be sure to mark initial status change */ + parse->lastmissed = 0; /* assume got everything */ + parse->ppsserial = 0; + parse->ppsfd = -1; + parse->localdata = (void *)0; + parse->localstate = 0; + parse->kv = (struct ctl_var *)0; + + clear_err(parse, ERR_ALL); + + parse->parse_type = &parse_clockinfo[type]; + + parse->maxunsync = parse->parse_type->cl_maxunsync; + + parse->generic->fudgetime1 = parse->parse_type->cl_basedelay; + + parse->generic->fudgetime2 = 0.0; + parse->ppsphaseadjust = parse->generic->fudgetime2; + + parse->generic->clockdesc = parse->parse_type->cl_description; + + peer->rootdelay = parse->parse_type->cl_rootdelay; + peer->sstclktype = parse->parse_type->cl_type; + peer->precision = sys_precision; + + peer->stratum = STRATUM_REFCLOCK; + + if (peer->stratum <= 1) + memmove((char *)&parse->generic->refid, parse->parse_type->cl_id, 4); + else + parse->generic->refid = htonl(PARSEHSREFID); + + parse->generic->io.fd = fd232; + + parse->peer = peer; /* marks it also as busy */ + + /* + * configure terminal line + */ + if (TTY_GETATTR(fd232, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: tcgetattr(%d, &tio): %m", unit, fd232); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } + else + { +#ifndef _PC_VDISABLE + memset((char *)tio.c_cc, 0, sizeof(tio.c_cc)); +#else + int disablec; + errno = 0; /* pathconf can deliver -1 without changing errno ! */ + + disablec = fpathconf(parse->generic->io.fd, _PC_VDISABLE); + if (disablec == -1 && errno) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: fpathconf(fd, _PC_VDISABLE): %m", CLK_UNIT(parse->peer)); + memset((char *)tio.c_cc, 0, sizeof(tio.c_cc)); /* best guess */ + } + else + if (disablec != -1) + memset((char *)tio.c_cc, disablec, sizeof(tio.c_cc)); +#endif + +#if defined (VMIN) || defined(VTIME) + if ((parse_clockinfo[type].cl_lflag & ICANON) == 0) + { +#ifdef VMIN + tio.c_cc[VMIN] = 1; +#endif +#ifdef VTIME + tio.c_cc[VTIME] = 0; +#endif + } +#endif + + tio.c_cflag = (tcflag_t) parse_clockinfo[type].cl_cflag; + tio.c_iflag = (tcflag_t) parse_clockinfo[type].cl_iflag; + tio.c_oflag = (tcflag_t) parse_clockinfo[type].cl_oflag; + tio.c_lflag = (tcflag_t) parse_clockinfo[type].cl_lflag; + + +#ifdef HAVE_TERMIOS + if ((cfsetospeed(&tio, (speed_t) parse_clockinfo[type].cl_speed) == -1) || + (cfsetispeed(&tio, (speed_t) parse_clockinfo[type].cl_speed) == -1)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: tcset{i,o}speed(&tio, speed): %m", unit); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } +#else + tio.c_cflag |= parse_clockinfo[type].cl_speed; +#endif + + /* + * set up pps device + * if the PARSEPPSDEVICE can be opened that will be used + * for PPS else PARSEDEVICE will be used + */ + parse->ppsfd = tty_open(parseppsdev, O_RDWR | O_NOCTTY | O_NONBLOCK, 0777); + + if (parse->ppsfd == -1) + { + parse->ppsfd = fd232; + } + +/* + * Linux PPS - the old way + */ +#if defined(HAVE_TIO_SERIAL_STUFF) /* Linux hack: define PPS interface */ + { + struct serial_struct ss; + if (ioctl(parse->ppsfd, TIOCGSERIAL, &ss) < 0 || + ( +#ifdef ASYNC_LOW_LATENCY + ss.flags |= ASYNC_LOW_LATENCY, +#endif +#ifndef HAVE_PPSAPI +#ifdef ASYNC_PPS_CD_NEG + ss.flags |= ASYNC_PPS_CD_NEG, +#endif +#endif + ioctl(parse->ppsfd, TIOCSSERIAL, &ss)) < 0) { + msyslog(LOG_NOTICE, "refclock_parse: TIOCSSERIAL fd %d, %m", parse->ppsfd); + msyslog(LOG_NOTICE, + "refclock_parse: optional PPS processing not available"); + } else { + parse->flags |= PARSE_PPSCLOCK; +#ifdef ASYNC_PPS_CD_NEG + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_INFO, + "refclock_parse: PPS detection on"); +#endif + } + } +#endif + +/* + * SUN the Solaris way + */ +#ifdef HAVE_TIOCSPPS /* SUN PPS support */ + if (CLK_PPS(parse->peer)) + { + int i = 1; + + if (ioctl(parse->ppsfd, TIOCSPPS, (caddr_t)&i) == 0) + { + parse->flags |= PARSE_PPSCLOCK; + } + } +#endif + +/* + * PPS via PPSAPI + */ +#if defined(HAVE_PPSAPI) + parse->hardppsstate = PARSE_HARDPPS_DISABLE; + if (CLK_PPS(parse->peer)) + { + if (!refclock_ppsapi(parse->ppsfd, &parse->atom)) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: parse_start: could not set up PPS: %m", CLK_UNIT(parse->peer)); + } + else + { + parse_ppsapi(parse); + } + } +#endif + + if (TTY_SETATTR(fd232, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: tcsetattr(%d, &tio): %m", unit, fd232); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } + } + + /* + * pick correct input machine + */ + parse->generic->io.srcclock = peer; + parse->generic->io.datalen = 0; + + parse->binding = init_iobinding(parse); + + if (parse->binding == (bind_t *)0) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: io sub system initialisation failed.", CLK_UNIT(parse->peer)); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + + parse->generic->io.clock_recv = parse->binding->bd_receive; /* pick correct receive routine */ + parse->generic->io.io_input = parse->binding->bd_io_input; /* pick correct input routine */ + + /* + * as we always(?) get 8 bit chars we want to be + * sure, that the upper bits are zero for less + * than 8 bit I/O - so we pass that information on. + * note that there can be only one bit count format + * per file descriptor + */ + + switch (tio.c_cflag & CSIZE) + { + case CS5: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS5; + break; + + case CS6: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS6; + break; + + case CS7: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS7; + break; + + case CS8: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS8; + break; + } + + if (!PARSE_SETCS(parse, &tmp_ctl)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: parse_setcs() FAILED.", unit); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + + strlcpy(tmp_ctl.parseformat.parse_buffer, parse->parse_type->cl_format, sizeof(tmp_ctl.parseformat.parse_buffer)); + tmp_ctl.parseformat.parse_count = (u_short) strlen(tmp_ctl.parseformat.parse_buffer); + + if (!PARSE_SETFMT(parse, &tmp_ctl)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: parse_setfmt() FAILED.", unit); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + + /* + * get rid of all IO accumulated so far + */ +#ifdef HAVE_TERMIOS + (void) tcflush(parse->generic->io.fd, TCIOFLUSH); +#else +#if defined(TCFLSH) && defined(TCIOFLUSH) + { + int flshcmd = TCIOFLUSH; + + (void) ioctl(parse->generic->io.fd, TCFLSH, (caddr_t)&flshcmd); + } +#endif +#endif + + /* + * try to do any special initializations + */ + if (parse->parse_type->cl_init) + { + if (parse->parse_type->cl_init(parse)) + { + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + } + + /* + * Insert in async io device list. + */ + if (!io_addclock(&parse->generic->io)) + { + msyslog(LOG_ERR, + "PARSE receiver #%d: parse_start: addclock %s fails (ABORT - clock type requires async io)", CLK_UNIT(parse->peer), parsedev); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } + + /* + * print out configuration + */ + NLOG(NLOG_CLOCKINFO) + { + /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "PARSE receiver #%d: reference clock \"%s\" (I/O device %s, PPS device %s) added", + CLK_UNIT(parse->peer), + parse->parse_type->cl_description, parsedev, + (parse->ppsfd != parse->generic->io.fd) ? parseppsdev : parsedev); + + msyslog(LOG_INFO, "PARSE receiver #%d: Stratum %d, trust time %s, precision %d", + CLK_UNIT(parse->peer), + parse->peer->stratum, + l_mktime(parse->maxunsync), parse->peer->precision); + + msyslog(LOG_INFO, "PARSE receiver #%d: rootdelay %.6f s, phase adjustment %.6f s, PPS phase adjustment %.6f s, %s IO handling", + CLK_UNIT(parse->peer), + parse->parse_type->cl_rootdelay, + parse->generic->fudgetime1, + parse->ppsphaseadjust, + parse->binding->bd_description); + + msyslog(LOG_INFO, "PARSE receiver #%d: Format recognition: %s", CLK_UNIT(parse->peer), + parse->parse_type->cl_format); + msyslog(LOG_INFO, "PARSE receiver #%d: %sPPS support%s", CLK_UNIT(parse->peer), + CLK_PPS(parse->peer) ? "" : "NO ", + CLK_PPS(parse->peer) ? +#ifdef PPS_METHOD + " (implementation " PPS_METHOD ")" +#else + "" +#endif + : "" + ); + } + + return 1; +} + +/*-------------------------------------------------- + * parse_ctl - process changes on flags/time values + */ +static void +parse_ctl( + struct parseunit *parse, + const struct refclockstat *in + ) +{ + if (in) + { + if (in->haveflags & (CLK_HAVEFLAG1|CLK_HAVEFLAG2|CLK_HAVEFLAG3|CLK_HAVEFLAG4)) + { + u_char mask = CLK_FLAG1|CLK_FLAG2|CLK_FLAG3|CLK_FLAG4; + parse->flags = (parse->flags & (u_char)(~mask)) | (in->flags & mask); +#if defined(HAVE_PPSAPI) + if (CLK_PPS(parse->peer)) + { + parse_ppsapi(parse); + } +#endif + } + + if (in->haveflags & CLK_HAVETIME1) + { + parse->generic->fudgetime1 = in->fudgetime1; + msyslog(LOG_INFO, "PARSE receiver #%d: new phase adjustment %.6f s", + CLK_UNIT(parse->peer), + parse->generic->fudgetime1); + } + + if (in->haveflags & CLK_HAVETIME2) + { + parse->generic->fudgetime2 = in->fudgetime2; + if (parse->flags & PARSE_TRUSTTIME) + { + parse->maxunsync = (u_long)ABS(in->fudgetime2); + msyslog(LOG_INFO, "PARSE receiver #%d: new trust time %s", + CLK_UNIT(parse->peer), + l_mktime(parse->maxunsync)); + } + else + { + parse->ppsphaseadjust = in->fudgetime2; + msyslog(LOG_INFO, "PARSE receiver #%d: new PPS phase adjustment %.6f s", + CLK_UNIT(parse->peer), + parse->ppsphaseadjust); +#if defined(HAVE_PPSAPI) + if (CLK_PPS(parse->peer)) + { + parse_ppsapi(parse); + } +#endif + } + } + } +} + +/*-------------------------------------------------- + * parse_poll - called by the transmit procedure + */ +static void +parse_poll( + int unit, + struct peer *peer + ) +{ + struct parseunit *parse = peer->procptr->unitptr; + + if (peer != parse->peer) + { + msyslog(LOG_ERR, + "PARSE receiver #%d: poll: INTERNAL: peer incorrect", + unit); + return; + } + + /* + * Update clock stat counters + */ + parse->generic->polls++; + + if (parse->pollneeddata && + ((int)(current_time - parse->pollneeddata) > (1<<(max(min(parse->peer->hpoll, parse->peer->ppoll), parse->peer->minpoll))))) + { + /* + * start worrying when exceeding a poll inteval + * bad news - didn't get a response last time + */ + parse->lastmissed = current_time; + parse_event(parse, CEVNT_TIMEOUT); + + ERR(ERR_NODATA) + msyslog(LOG_WARNING, "PARSE receiver #%d: no data from device within poll interval (check receiver / wiring)", CLK_UNIT(parse->peer)); + } + + /* + * we just mark that we want the next sample for the clock filter + */ + parse->pollneeddata = current_time; + + if (parse->parse_type->cl_poll) + { + parse->parse_type->cl_poll(parse); + } + + cparse_statistics(parse); + + return; +} + +#define LEN_STATES 300 /* length of state string */ + +/*-------------------------------------------------- + * parse_control - set fudge factors, return statistics + */ +static void +parse_control( + int unit, + const struct refclockstat *in, + struct refclockstat *out, + struct peer *peer + ) +{ + struct parseunit *parse = peer->procptr->unitptr; + parsectl_t tmpctl; + + static char outstatus[400]; /* status output buffer */ + + if (out) + { + out->lencode = 0; + out->p_lastcode = 0; + out->kv_list = (struct ctl_var *)0; + } + + if (!parse || !parse->peer) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_control: unit invalid (UNIT INACTIVE)", + unit); + return; + } + + unit = CLK_UNIT(parse->peer); + + /* + * handle changes + */ + parse_ctl(parse, in); + + /* + * supply data + */ + if (out) + { + u_long sum = 0; + char *tt, *start; + int i; + + outstatus[0] = '\0'; + + out->type = REFCLK_PARSE; + + /* + * keep fudgetime2 in sync with TRUSTTIME/MAXUNSYNC flag1 + */ + parse->generic->fudgetime2 = (parse->flags & PARSE_TRUSTTIME) ? (double)parse->maxunsync : parse->ppsphaseadjust; + + /* + * figure out skew between PPS and RS232 - just for informational + * purposes + */ + if (PARSE_SYNC(parse->timedata.parse_state)) + { + if (PARSE_PPS(parse->timedata.parse_state) && PARSE_TIMECODE(parse->timedata.parse_state)) + { + l_fp off; + + /* + * we have a PPS and RS232 signal - calculate the skew + * WARNING: assumes on TIMECODE == PULSE (timecode after pulse) + */ + off = parse->timedata.parse_stime.fp; + L_SUB(&off, &parse->timedata.parse_ptime.fp); /* true offset */ + tt = add_var(&out->kv_list, 80, RO); + snprintf(tt, 80, "refclock_ppsskew=%s", lfptoms(&off, 6)); + } + } + + if (PARSE_PPS(parse->timedata.parse_state)) + { + tt = add_var(&out->kv_list, 80, RO|DEF); + snprintf(tt, 80, "refclock_ppstime=\"%s\"", gmprettydate(&parse->timedata.parse_ptime.fp)); + } + + start = tt = add_var(&out->kv_list, 128, RO|DEF); + tt = ap(start, 128, tt, "refclock_time=\""); + + if (parse->timedata.parse_time.fp.l_ui == 0) + { + tt = ap(start, 128, tt, "<UNDEFINED>\""); + } + else + { + tt = ap(start, 128, tt, "%s\"", + gmprettydate(&parse->timedata.parse_time.fp)); + } + + if (!PARSE_GETTIMECODE(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_control: parse_timecode() FAILED", unit); + } + else + { + start = tt = add_var(&out->kv_list, 512, RO|DEF); + tt = ap(start, 512, tt, "refclock_status=\""); + + /* + * copy PPS flags from last read transaction (informational only) + */ + tmpctl.parsegettc.parse_state |= parse->timedata.parse_state & + (PARSEB_PPS|PARSEB_S_PPS); + + (void)parsestate(tmpctl.parsegettc.parse_state, tt, BUFFER_SIZES(start, tt, 512)); + + tt += strlen(tt); + + tt = ap(start, 512, tt, "\""); + + if (tmpctl.parsegettc.parse_count) + mkascii(outstatus+strlen(outstatus), (int)(sizeof(outstatus)- strlen(outstatus) - 1), + tmpctl.parsegettc.parse_buffer, (unsigned)(tmpctl.parsegettc.parse_count)); + + } + + tmpctl.parseformat.parse_format = tmpctl.parsegettc.parse_format; + + if (!PARSE_GETFMT(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_control: parse_getfmt() FAILED", unit); + } + else + { + int count = tmpctl.parseformat.parse_count - 1; + + start = tt = add_var(&out->kv_list, 80, RO|DEF); + tt = ap(start, 80, tt, "refclock_format=\""); + + if (count > 0) { + tt = ap(start, 80, tt, "%*.*s", + count, + count, + tmpctl.parseformat.parse_buffer); + } + + tt = ap(start, 80, tt, "\""); + } + + /* + * gather state statistics + */ + + start = tt = add_var(&out->kv_list, LEN_STATES, RO|DEF); + tt = ap(start, LEN_STATES, tt, "refclock_states=\""); + + for (i = 0; i <= CEVNT_MAX; i++) + { + u_long s_time; + u_long d = current_time - parse->generic->timestarted; + u_long percent; + + percent = s_time = PARSE_STATETIME(parse, i); + + while (((u_long)(~0) / 10000) < percent) + { + percent /= 10; + d /= 10; + } + + if (d) + percent = (percent * 10000) / d; + else + percent = 10000; + + if (s_time) + { + char item[80]; + int count; + + snprintf(item, 80, "%s%s%s: %s (%d.%02d%%)", + sum ? "; " : "", + (parse->generic->currentstatus == i) ? "*" : "", + clockstatus((unsigned int)i), + l_mktime(s_time), + (int)(percent / 100), (int)(percent % 100)); + if ((count = (int) strlen(item)) < (LEN_STATES - 40 - (tt - start))) + { + tt = ap(start, LEN_STATES, tt, + "%s", item); + } + sum += s_time; + } + } + + tt = ap(start, LEN_STATES, tt, + "; running time: %s\"", l_mktime(sum)); + + tt = add_var(&out->kv_list, 32, RO); + snprintf(tt, 32, "refclock_id=\"%s\"", parse->parse_type->cl_id); + + tt = add_var(&out->kv_list, 80, RO); + snprintf(tt, 80, "refclock_iomode=\"%s\"", parse->binding->bd_description); + + tt = add_var(&out->kv_list, 128, RO); + snprintf(tt, 128, "refclock_driver_version=\"%s\"", rcsid); + + { + struct ctl_var *k; + + k = parse->kv; + while (k && !(k->flags & EOV)) + { + set_var(&out->kv_list, k->text, strlen(k->text)+1, k->flags); + k++; + } + } + + out->lencode = (u_short) strlen(outstatus); + out->p_lastcode = outstatus; + } +} + +/**=========================================================================== + ** processing routines + **/ + +/*-------------------------------------------------- + * event handling - note that nominal events will also be posted + * keep track of state dwelling times + */ +static void +parse_event( + struct parseunit *parse, + int event + ) +{ + if (parse->generic->currentstatus != (u_char) event) + { + parse->statetime[parse->generic->currentstatus] += current_time - parse->lastchange; + parse->lastchange = current_time; + + if (parse->parse_type->cl_event) + parse->parse_type->cl_event(parse, event); + + if (event == CEVNT_NOMINAL) + { + NLOG(NLOG_CLOCKSTATUS) + msyslog(LOG_INFO, "PARSE receiver #%d: SYNCHRONIZED", + CLK_UNIT(parse->peer)); + } + + refclock_report(parse->peer, event); + } +} + +/*-------------------------------------------------- + * process a PARSE time sample + */ +static void +parse_process( + struct parseunit *parse, + parsetime_t *parsetime + ) +{ + l_fp off, rectime, reftime; + double fudge; + + /* silence warning: 'off.Ul_i.Xl_i' may be used uninitialized in this function */ + ZERO(off); + + /* + * check for changes in conversion status + * (only one for each new status !) + */ + if (((parsetime->parse_status & CVT_MASK) != CVT_OK) && + ((parsetime->parse_status & CVT_MASK) != CVT_NONE) && + (parse->timedata.parse_status != parsetime->parse_status)) + { + char buffer[400]; + + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_WARNING, "PARSE receiver #%d: conversion status \"%s\"", + CLK_UNIT(parse->peer), parsestatus(parsetime->parse_status, buffer, sizeof(buffer))); + + if ((parsetime->parse_status & CVT_MASK) == CVT_FAIL) + { + /* + * tell more about the story - list time code + * there is a slight change for a race condition and + * the time code might be overwritten by the next packet + */ + parsectl_t tmpctl; + + if (!PARSE_GETTIMECODE(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_process: parse_timecode() FAILED", CLK_UNIT(parse->peer)); + } + else + { + ERR(ERR_BADDATA) + msyslog(LOG_WARNING, "PARSE receiver #%d: FAILED TIMECODE: \"%s\" (check receiver configuration / wiring)", + CLK_UNIT(parse->peer), mkascii(buffer, sizeof buffer, tmpctl.parsegettc.parse_buffer, (unsigned)(tmpctl.parsegettc.parse_count - 1))); + } + /* copy status to show only changes in case of failures */ + parse->timedata.parse_status = parsetime->parse_status; + } + } + + /* + * examine status and post appropriate events + */ + if ((parsetime->parse_status & CVT_MASK) != CVT_OK) + { + /* + * got bad data - tell the rest of the system + */ + switch (parsetime->parse_status & CVT_MASK) + { + case CVT_NONE: + if ((parsetime->parse_status & CVT_ADDITIONAL) && + parse->parse_type->cl_message) + parse->parse_type->cl_message(parse, parsetime); + /* + * save PPS information that comes piggyback + */ + if (PARSE_PPS(parsetime->parse_state)) + { + parse->timedata.parse_state |= PARSEB_PPS|PARSEB_S_PPS; + parse->timedata.parse_ptime = parsetime->parse_ptime; + } + break; /* well, still waiting - timeout is handled at higher levels */ + + case CVT_FAIL: + if (parsetime->parse_status & CVT_BADFMT) + { + parse_event(parse, CEVNT_BADREPLY); + } + else + if (parsetime->parse_status & CVT_BADDATE) + { + parse_event(parse, CEVNT_BADDATE); + } + else + if (parsetime->parse_status & CVT_BADTIME) + { + parse_event(parse, CEVNT_BADTIME); + } + else + { + parse_event(parse, CEVNT_BADREPLY); /* for the lack of something better */ + } + } + return; /* skip the rest - useless */ + } + + /* + * check for format changes + * (in case somebody has swapped clocks 8-) + */ + if (parse->lastformat != parsetime->parse_format) + { + parsectl_t tmpctl; + + tmpctl.parseformat.parse_format = parsetime->parse_format; + + if (!PARSE_GETFMT(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_getfmt() FAILED", CLK_UNIT(parse->peer)); + } + else + { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "PARSE receiver #%d: packet format \"%s\"", + CLK_UNIT(parse->peer), tmpctl.parseformat.parse_buffer); + } + parse->lastformat = parsetime->parse_format; + } + + /* + * now, any changes ? + */ + if ((parse->timedata.parse_state ^ parsetime->parse_state) & + ~(unsigned)(PARSEB_PPS|PARSEB_S_PPS)) + { + char tmp1[200]; + char tmp2[200]; + /* + * something happend - except for PPS events + */ + + (void) parsestate(parsetime->parse_state, tmp1, sizeof(tmp1)); + (void) parsestate(parse->timedata.parse_state, tmp2, sizeof(tmp2)); + + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO,"PARSE receiver #%d: STATE CHANGE: %s -> %s", + CLK_UNIT(parse->peer), tmp2, tmp1); + } + + /* + * carry on PPS information if still usable + */ + if (PARSE_PPS(parse->timedata.parse_state) && !PARSE_PPS(parsetime->parse_state)) + { + parsetime->parse_state |= PARSEB_PPS|PARSEB_S_PPS; + parsetime->parse_ptime = parse->timedata.parse_ptime; + } + + /* + * remember for future + */ + parse->timedata = *parsetime; + + /* + * check to see, whether the clock did a complete powerup or lost PZF signal + * and post correct events for current condition + */ + if (PARSE_POWERUP(parsetime->parse_state)) + { + /* + * this is bad, as we have completely lost synchronisation + * well this is a problem with the receiver here + * for PARSE Meinberg DCF77 receivers the lost synchronisation + * is true as it is the powerup state and the time is taken + * from a crude real time clock chip + * for the PZF/GPS series this is only partly true, as + * PARSE_POWERUP only means that the pseudo random + * phase shift sequence cannot be found. this is only + * bad, if we have never seen the clock in the SYNC + * state, where the PHASE and EPOCH are correct. + * for reporting events the above business does not + * really matter, but we can use the time code + * even in the POWERUP state after having seen + * the clock in the synchronized state (PZF class + * receivers) unless we have had a telegram disruption + * after having seen the clock in the SYNC state. we + * thus require having seen the clock in SYNC state + * *after* having missed telegrams (noresponse) from + * the clock. one problem remains: we might use erroneously + * POWERUP data if the disruption is shorter than 1 polling + * interval. fortunately powerdowns last usually longer than 64 + * seconds and the receiver is at least 2 minutes in the + * POWERUP or NOSYNC state before switching to SYNC + * for GPS receivers this can mean antenna problems and other causes. + * the additional grace period can be enables by a clock + * mode having the PARSE_F_POWERUPTRUST flag in cl_flag set. + */ + parse_event(parse, CEVNT_FAULT); + NLOG(NLOG_CLOCKSTATUS) + ERR(ERR_BADSTATUS) + msyslog(LOG_ERR,"PARSE receiver #%d: NOT SYNCHRONIZED/RECEIVER PROBLEMS", + CLK_UNIT(parse->peer)); + } + else + { + /* + * we have two states left + * + * SYNC: + * this state means that the EPOCH (timecode) and PHASE + * information has be read correctly (at least two + * successive PARSE timecodes were received correctly) + * this is the best possible state - full trust + * + * NOSYNC: + * The clock should be on phase with respect to the second + * signal, but the timecode has not been received correctly within + * at least the last two minutes. this is a sort of half baked state + * for PARSE Meinberg DCF77 clocks this is bad news (clock running + * without timecode confirmation) + * PZF 535 has also no time confirmation, but the phase should be + * very precise as the PZF signal can be decoded + */ + + if (PARSE_SYNC(parsetime->parse_state)) + { + /* + * currently completely synchronized - best possible state + */ + parse->lastsync = current_time; + clear_err(parse, ERR_BADSTATUS); + } + else + { + /* + * we have had some problems receiving the time code + */ + parse_event(parse, CEVNT_PROP); + NLOG(NLOG_CLOCKSTATUS) + ERR(ERR_BADSTATUS) + msyslog(LOG_ERR,"PARSE receiver #%d: TIMECODE NOT CONFIRMED", + CLK_UNIT(parse->peer)); + } + } + + fudge = parse->generic->fudgetime1; /* standard RS232 Fudgefactor */ + + if (PARSE_TIMECODE(parsetime->parse_state)) + { + rectime = parsetime->parse_stime.fp; + off = reftime = parsetime->parse_time.fp; + + L_SUB(&off, &rectime); /* prepare for PPS adjustments logic */ + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: Reftime %s, Recvtime %s - initial offset %s\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime), + lfptoa(&off,6)); +#endif + } + + if (PARSE_PPS(parsetime->parse_state) && CLK_PPS(parse->peer)) + { + l_fp offset; + double ppsphaseadjust = parse->ppsphaseadjust; + +#ifdef HAVE_PPSAPI + /* + * set fudge = 0.0 if already included in PPS time stamps + */ + if (parse->atom.pps_params.mode & (PPS_OFFSETCLEAR|PPS_OFFSETASSERT)) + { + ppsphaseadjust = 0.0; + } +#endif + + /* + * we have a PPS signal - much better than the RS232 stuff (we hope) + */ + offset = parsetime->parse_ptime.fp; + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: PPStime %s\n", + CLK_UNIT(parse->peer), + prettydate(&offset)); +#endif + if (PARSE_TIMECODE(parsetime->parse_state)) + { + if (M_ISGEQ(off.l_i, off.l_uf, -1, 0x80000000) && + M_ISGEQ(0, 0x7fffffff, off.l_i, off.l_uf)) + { + fudge = ppsphaseadjust; /* pick PPS fudge factor */ + + /* + * RS232 offsets within [-0.5..0.5[ - take PPS offsets + */ + + if (parse->parse_type->cl_flags & PARSE_F_PPSONSECOND) + { + reftime = off = offset; + if (reftime.l_uf & 0x80000000) + reftime.l_ui++; + reftime.l_uf = 0; + + + /* + * implied on second offset + */ + off.l_uf = ~off.l_uf; /* map [0.5..1[ -> [-0.5..0[ */ + off.l_i = (off.l_uf & 0x80000000) ? -1 : 0; /* sign extend */ + } + else + { + /* + * time code describes pulse + */ + reftime = off = parsetime->parse_time.fp; + + L_SUB(&off, &offset); /* true offset */ + } + } + /* + * take RS232 offset when PPS when out of bounds + */ + } + else + { + fudge = ppsphaseadjust; /* pick PPS fudge factor */ + /* + * Well, no time code to guide us - assume on second pulse + * and pray, that we are within [-0.5..0.5[ + */ + off = offset; + reftime = offset; + if (reftime.l_uf & 0x80000000) + reftime.l_ui++; + reftime.l_uf = 0; + /* + * implied on second offset + */ + off.l_uf = ~off.l_uf; /* map [0.5..1[ -> [-0.5..0[ */ + off.l_i = (off.l_uf & 0x80000000) ? -1 : 0; /* sign extend */ + } + } + else + { + if (!PARSE_TIMECODE(parsetime->parse_state)) + { + /* + * Well, no PPS, no TIMECODE, no more work ... + */ + if ((parsetime->parse_status & CVT_ADDITIONAL) && + parse->parse_type->cl_message) + parse->parse_type->cl_message(parse, parsetime); + return; + } + } + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: Reftime %s, Recvtime %s - final offset %s\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime), + lfptoa(&off,6)); +#endif + + + rectime = reftime; + L_SUB(&rectime, &off); /* just to keep the ntp interface happy */ + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: calculated Reftime %s, Recvtime %s\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime)); +#endif + + if ((parsetime->parse_status & CVT_ADDITIONAL) && + parse->parse_type->cl_message) + parse->parse_type->cl_message(parse, parsetime); + + if (PARSE_SYNC(parsetime->parse_state)) + { + /* + * log OK status + */ + parse_event(parse, CEVNT_NOMINAL); + } + + clear_err(parse, ERR_BADIO); + clear_err(parse, ERR_BADDATA); + clear_err(parse, ERR_NODATA); + clear_err(parse, ERR_INTERNAL); + + /* + * and now stick it into the clock machine + * samples are only valid iff lastsync is not too old and + * we have seen the clock in sync at least once + * after the last time we didn't see an expected data telegram + * at startup being not in sync is also bad just like + * POWERUP state unless PARSE_F_POWERUPTRUST is set + * see the clock states section above for more reasoning + */ + if (((current_time - parse->lastsync) > parse->maxunsync) || + (parse->lastsync < parse->lastmissed) || + ((parse->lastsync == 0) && !PARSE_SYNC(parsetime->parse_state)) || + (((parse->parse_type->cl_flags & PARSE_F_POWERUPTRUST) == 0) && + PARSE_POWERUP(parsetime->parse_state))) + { + parse->generic->leap = LEAP_NOTINSYNC; + parse->lastsync = 0; /* wait for full sync again */ + } + else + { + if (PARSE_LEAPADD(parsetime->parse_state)) + { + /* + * we pick this state also for time code that pass leap warnings + * without direction information (as earth is currently slowing + * down). + */ + parse->generic->leap = (parse->flags & PARSE_LEAP_DELETE) ? LEAP_DELSECOND : LEAP_ADDSECOND; + } + else + if (PARSE_LEAPDEL(parsetime->parse_state)) + { + parse->generic->leap = LEAP_DELSECOND; + } + else + { + parse->generic->leap = LEAP_NOWARNING; + } + } + + if (parse->generic->leap != LEAP_NOTINSYNC) + { + /* + * only good/trusted samples are interesting + */ +#ifdef DEBUG + if (debug > 2) + { + printf("PARSE receiver #%d: refclock_process_offset(reftime=%s, rectime=%s, Fudge=%f)\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime), + fudge); + } +#endif + parse->generic->lastref = reftime; + + refclock_process_offset(parse->generic, reftime, rectime, fudge); + +#ifdef HAVE_PPSAPI + /* + * pass PPS information on to PPS clock + */ + if (PARSE_PPS(parsetime->parse_state) && CLK_PPS(parse->peer)) + { + parse->peer->flags |= (FLAG_PPS | FLAG_TSTAMP_PPS); + parse_hardpps(parse, PARSE_HARDPPS_ENABLE); + } +#endif + } else { + parse_hardpps(parse, PARSE_HARDPPS_DISABLE); + parse->peer->flags &= ~(FLAG_PPS | FLAG_TSTAMP_PPS); + } + + /* + * ready, unless the machine wants a sample or + * we are in fast startup mode (peer->dist > MAXDISTANCE) + */ + if (!parse->pollneeddata && parse->peer->disp <= MAXDISTANCE) + return; + + parse->pollneeddata = 0; + + parse->timedata.parse_state &= ~(unsigned)(PARSEB_PPS|PARSEB_S_PPS); + + refclock_receive(parse->peer); +} + +/**=========================================================================== + ** special code for special clocks + **/ + +static void +mk_utcinfo( + char *t, // pointer to the output string buffer + int wnt, + int wnlsf, + int dn, + int dtls, + int dtlsf, + int size // size of the output string buffer + ) +{ + /* + * The week number transmitted by the GPS satellites for the leap date + * is truncated to 8 bits only. If the nearest leap second date is off + * the current date by more than +/- 128 weeks then conversion to a + * calendar date is ambiguous. On the other hand, if a leap second is + * currently being announced (i.e. dtlsf != dtls) then the week number + * wnlsf is close enough, and we can unambiguously determine the date + * for which the leap second is scheduled. + */ + if ( dtlsf != dtls ) + { + time_t t_ls; + struct tm *tm; + int n = 0; + + if (wnlsf < GPSWRAP) + wnlsf += GPSWEEKS; + + if (wnt < GPSWRAP) + wnt += GPSWEEKS; + + t_ls = (time_t) wnlsf * SECSPERWEEK + + (time_t) dn * SECSPERDAY + + GPS_SEC_BIAS - 1; + + tm = gmtime( &t_ls ); + if (tm == NULL) // gmtime() failed + { + snprintf( t, size, "** (gmtime() failed in mk_utcinfo())" ); + return; + } + + n += snprintf( t, size, "UTC offset transition from %is to %is due to leap second %s", + dtls, dtlsf, ( dtls < dtlsf ) ? "insertion" : "deletion" ); + n += snprintf( t + n, size - n, " at UTC midnight at the end of %s, %04i-%02i-%02i", + daynames[tm->tm_wday], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday ); + } + else + snprintf( t, size, "UTC offset parameter: %is, no leap second announced.\n", dtls ); + +} + +#ifdef CLOCK_MEINBERG +/**=========================================================================== + ** Meinberg GPS receiver support + **/ + +/*------------------------------------------------------------ + * gps16x_message - process messages from Meinberg GPS receiver + */ +static void +gps16x_message( + struct parseunit *parse, + parsetime_t *parsetime + ) +{ + if (parse->timedata.parse_msglen && parsetime->parse_msg[0] == SOH) + { + GPS_MSG_HDR header; + unsigned char *bufp = (unsigned char *)parsetime->parse_msg + 1; + +#ifdef DEBUG + if (debug > 2) + { + char msgbuffer[600]; + + mkreadable(msgbuffer, sizeof(msgbuffer), (char *)parsetime->parse_msg, parsetime->parse_msglen, 1); + printf("PARSE receiver #%d: received message (%d bytes) >%s<\n", + CLK_UNIT(parse->peer), + parsetime->parse_msglen, + msgbuffer); + } +#endif + get_mbg_header(&bufp, &header); + if (header.hdr_csum == mbg_csum(parsetime->parse_msg + 1, 6) && + (header.len == 0 || + (header.len < sizeof(parsetime->parse_msg) && + header.data_csum == mbg_csum(bufp, header.len)))) + { + /* + * clean message + */ + switch (header.cmd) + { + case GPS_SW_REV: + { + char buffer[64]; + SW_REV gps_sw_rev; + + get_mbg_sw_rev(&bufp, &gps_sw_rev); + snprintf(buffer, sizeof(buffer), "meinberg_gps_version=\"%x.%02x%s%s\"", + (gps_sw_rev.code >> 8) & 0xFF, + gps_sw_rev.code & 0xFF, + gps_sw_rev.name[0] ? " " : "", + gps_sw_rev.name); + set_var(&parse->kv, buffer, strlen(buffer)+1, RO|DEF); + } + break; + + case GPS_BVAR_STAT: + { + static struct state + { + BVAR_STAT flag; /* status flag */ + const char *string; /* bit name */ + } states[] = + { + { BVAR_CFGH_INVALID, "Configuration/Health" }, + { BVAR_ALM_NOT_COMPLETE, "Almanachs" }, + { BVAR_UTC_INVALID, "UTC Correction" }, + { BVAR_IONO_INVALID, "Ionospheric Correction" }, + { BVAR_RCVR_POS_INVALID, "Receiver Position" }, + { 0, "" } + }; + BVAR_STAT status; + struct state *s = states; + char buffer[512]; + char *p, *b; + + status = (BVAR_STAT) get_lsb_short(&bufp); + p = b = buffer; + p = ap(buffer, sizeof(buffer), p, + "meinberg_gps_status=\"[0x%04x] ", + status); + + if (status) + { + p = ap(buffer, sizeof(buffer), p, "incomplete buffered data: "); + b = p; + while (s->flag) + { + if (status & s->flag) + { + if (p != b) + { + p = ap(buffer, sizeof(buffer), p, ", "); + } + + p = ap(buffer, sizeof(buffer), p, "%s", (const char *)s->string); + } + s++; + } + p = ap(buffer, sizeof(buffer), p, "\""); + } + else + { + p = ap(buffer, sizeof(buffer), p, "<all buffered data complete>\""); + } + + set_var(&parse->kv, buffer, strlen(buffer)+1, RO|DEF); + } + break; + + case GPS_POS_XYZ: + { + XYZ xyz; + char buffer[256]; + + get_mbg_xyz(&bufp, xyz); + snprintf(buffer, sizeof(buffer), "gps_position(XYZ)=\"%s m, %s m, %s m\"", + mfptoa(xyz[XP].l_ui, xyz[XP].l_uf, 1), + mfptoa(xyz[YP].l_ui, xyz[YP].l_uf, 1), + mfptoa(xyz[ZP].l_ui, xyz[ZP].l_uf, 1)); + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_POS_LLA: + { + LLA lla; + char buffer[256]; + + get_mbg_lla(&bufp, lla); + + snprintf(buffer, sizeof(buffer), "gps_position(LLA)=\"%s deg, %s deg, %s m\"", + mfptoa(lla[LAT].l_ui, lla[LAT].l_uf, 4), + mfptoa(lla[LON].l_ui, lla[LON].l_uf, 4), + mfptoa(lla[ALT].l_ui, lla[ALT].l_uf, 1)); + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_TZDL: + break; + + case GPS_PORT_PARM: + break; + + case GPS_SYNTH: + break; + + case GPS_ANT_INFO: + { + ANT_INFO antinfo; + char buffer[512]; + char *p, *q; + + get_mbg_antinfo(&bufp, &antinfo); + p = buffer; + p = ap(buffer, sizeof(buffer), p, "meinberg_antenna_status=\""); + switch (antinfo.status) + { + case ANT_INVALID: // No other fields valid since antenna has not yet been disconnected + p = ap(buffer, sizeof(buffer), + p, "<OK>"); + break; + + case ANT_DISCONN: // Antenna is disconnected, tm_reconn and delta_t not yet set + q = ap(buffer, sizeof(buffer), + p, "DISCONNECTED since "); + NLOG(NLOG_CLOCKSTATUS) + ERR(ERR_BADSTATUS) + msyslog(LOG_ERR,"PARSE receiver #%d: ANTENNA FAILURE: %s", + CLK_UNIT(parse->peer), p); + + p = q; + mbg_tm_str(&p, &antinfo.tm_disconn, BUFFER_SIZE(buffer, p), 0); + *p = '\0'; + break; + + case ANT_RECONN: // Antenna had been disconnect, but receiver sync. after reconnect, so all fields valid + p = ap(buffer, sizeof(buffer), + p, "SYNC AFTER RECONNECT on "); + mbg_tm_str(&p, &antinfo.tm_reconn, BUFFER_SIZE(buffer, p), 0); + p = ap(buffer, sizeof(buffer), + p, ", clock offset at reconnect %c%ld.%07ld s, disconnect time ", + (antinfo.delta_t < 0) ? '-' : '+', + (long) ABS(antinfo.delta_t) / 10000, + (long) ABS(antinfo.delta_t) % 10000); + mbg_tm_str(&p, &antinfo.tm_disconn, BUFFER_SIZE(buffer, p), 0); + *p = '\0'; + break; + + default: + p = ap(buffer, sizeof(buffer), + p, "bad status 0x%04x", + antinfo.status); + break; + } + + p = ap(buffer, sizeof(buffer), p, "\""); + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_UCAP: + break; + + case GPS_CFGH: + { + CFGH cfgh; + char buffer[512]; + char *p; + + get_mbg_cfgh(&bufp, &cfgh); + if (cfgh.valid) + { + const char *cp; + uint16_t tmp_val; + int i; + + p = buffer; + p = ap(buffer, sizeof(buffer), + p, "gps_tot_51=\""); + mbg_tgps_str(&p, &cfgh.tot_51, BUFFER_SIZE(buffer, p)); + p = ap(buffer, sizeof(buffer), + p, "\""); + set_var(&parse->kv, buffer, sizeof(buffer), RO|COND_DEF); + + p = buffer; + p = ap(buffer, sizeof(buffer), + p, "gps_tot_63=\""); + mbg_tgps_str(&p, &cfgh.tot_63, BUFFER_SIZE(buffer, p)); + p = ap(buffer, sizeof(buffer), + p, "\""); + set_var(&parse->kv, buffer, sizeof(buffer), RO|COND_DEF); + + p = buffer; + p = ap(buffer, sizeof(buffer), + p, "gps_t0a=\""); + mbg_tgps_str(&p, &cfgh.t0a, BUFFER_SIZE(buffer, p)); + p = ap(buffer, sizeof(buffer), + p, "\""); + set_var(&parse->kv, buffer, sizeof(buffer), RO|COND_DEF); + + for (i = 0; i < N_SVNO_GPS; i++) + { + p = buffer; + p = ap(buffer, sizeof(buffer), p, "sv_info[%d]=\"PRN%d", i, i + N_SVNO_GPS); + + tmp_val = cfgh.health[i]; /* a 6 bit SV health code */ + p = ap(buffer, sizeof(buffer), p, "; health=0x%02x (", tmp_val); + /* "All Ones" has a special meaning" */ + if (tmp_val == 0x3F) /* satellite is unusable or doesn't even exist */ + cp = "SV UNAVAILABLE"; + else { + /* The MSB contains a summary of the 3 MSBs of the 8 bit health code, + * indicating if the data sent by the satellite is OK or not. */ + p = ap(buffer, sizeof(buffer), p, "DATA %s, ", (tmp_val & 0x20) ? "BAD" : "OK" ); + + /* The 5 LSBs contain the status of the different signals sent by the satellite. */ + switch (tmp_val & 0x1F) + { + case 0x00: cp = "SIGNAL OK"; break; + /* codes 0x01 through 0x1B indicate that one or more + * specific signal components are weak or dead. + * We don't decode this here in detail. */ + case 0x1C: cp = "SV IS TEMP OUT"; break; + case 0x1D: cp = "SV WILL BE TEMP OUT"; break; + default: cp = "TRANSMISSION PROBLEMS"; break; + } + } + p = ap(buffer, sizeof(buffer), p, "%s)", cp ); + + tmp_val = cfgh.cfg[i]; /* a 4 bit SV configuration/type code */ + p = ap(buffer, sizeof(buffer), p, "; cfg=0x%02x (", tmp_val); + switch (tmp_val & 0x7) + { + case 0x00: cp = "(reserved)"; break; + case 0x01: cp = "BLOCK II/IIA/IIR"; break; + case 0x02: cp = "BLOCK IIR-M"; break; + case 0x03: cp = "BLOCK IIF"; break; + case 0x04: cp = "BLOCK III"; break; + default: cp = "unknown SV type"; break; + } + p = ap(buffer, sizeof(buffer), p, "%s", cp ); + if (tmp_val & 0x08) /* A-S is on, P-code is encrypted */ + p = ap( buffer, sizeof(buffer), p, ", A-S on" ); + + p = ap(buffer, sizeof(buffer), p, ")\""); + set_var(&parse->kv, buffer, sizeof(buffer), RO|COND_DEF); + } + } + } + break; + + case GPS_ALM: + break; + + case GPS_EPH: + break; + + case GPS_UTC: + { + UTC utc; + char buffer[512]; + char *p; + + p = buffer; + + get_mbg_utc(&bufp, &utc); + + if (utc.valid) + { + p = ap(buffer, sizeof(buffer), p, "gps_utc_correction=\""); + mk_utcinfo(p, utc.t0t.wn, utc.WNlsf, utc.DNt, utc.delta_tls, utc.delta_tlsf, BUFFER_SIZE(buffer, p)); + p += strlen(p); + p = ap(buffer, sizeof(buffer), p, "\""); + } + else + { + p = ap(buffer, sizeof(buffer), p, "gps_utc_correction=\"<NO UTC DATA>\""); + } + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_IONO: + break; + + case GPS_ASCII_MSG: + { + ASCII_MSG gps_ascii_msg; + char buffer[128]; + + get_mbg_ascii_msg(&bufp, &gps_ascii_msg); + + if (gps_ascii_msg.valid) + { + char buffer1[128]; + mkreadable(buffer1, sizeof(buffer1), gps_ascii_msg.s, strlen(gps_ascii_msg.s), (int)0); + + snprintf(buffer, sizeof(buffer), "gps_message=\"%s\"", buffer1); + } + else + snprintf(buffer, sizeof(buffer), "gps_message=<NONE>"); + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + + break; + + default: + break; + } + } + else + { + msyslog(LOG_DEBUG, "PARSE receiver #%d: gps16x_message: message checksum error: hdr_csum = 0x%x (expected 0x%x), " + "data_len = %d, data_csum = 0x%x (expected 0x%x)", + CLK_UNIT(parse->peer), + header.hdr_csum, mbg_csum(parsetime->parse_msg + 1, 6), + header.len, + header.data_csum, mbg_csum(bufp, (unsigned)((header.len < sizeof(parsetime->parse_msg)) ? header.len : 0))); + } + } + + return; +} + +/*------------------------------------------------------------ + * gps16x_poll - query the reciver peridically + */ +static void +gps16x_poll( + struct peer *peer + ) +{ + struct parseunit *parse = peer->procptr->unitptr; + + static GPS_MSG_HDR sequence[] = + { + { GPS_SW_REV, 0, 0, 0 }, + { GPS_BVAR_STAT, 0, 0, 0 }, + { GPS_UTC, 0, 0, 0 }, + { GPS_ASCII_MSG, 0, 0, 0 }, + { GPS_ANT_INFO, 0, 0, 0 }, + { GPS_CFGH, 0, 0, 0 }, + { GPS_POS_XYZ, 0, 0, 0 }, + { GPS_POS_LLA, 0, 0, 0 }, + { (unsigned short)~0, 0, 0, 0 } + }; + + int rtc; + unsigned char cmd_buffer[64]; + unsigned char *outp = cmd_buffer; + GPS_MSG_HDR *header; + + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->procptr->nextaction = current_time + ((poll_info_t *)parse->parse_type->cl_data)->rate; + } + + if (sequence[parse->localstate].cmd == (unsigned short)~0) + parse->localstate = 0; + + header = sequence + parse->localstate++; + + *outp++ = SOH; /* start command */ + + put_mbg_header(&outp, header); + outp = cmd_buffer + 1; + + header->hdr_csum = (short)mbg_csum(outp, 6); + put_mbg_header(&outp, header); + +#ifdef DEBUG + if (debug > 2) + { + char buffer[128]; + + mkreadable(buffer, sizeof(buffer), (char *)cmd_buffer, (unsigned)(outp - cmd_buffer), 1); + printf("PARSE receiver #%d: transmitted message #%ld (%d bytes) >%s<\n", + CLK_UNIT(parse->peer), + parse->localstate - 1, + (int)(outp - cmd_buffer), + buffer); + } +#endif + + rtc = (int) write(parse->generic->io.fd, cmd_buffer, (unsigned long)(outp - cmd_buffer)); + + if (rtc < 0) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: gps16x_poll: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + } + else + if (rtc != outp - cmd_buffer) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: gps16x_poll: failed to send cmd incomplete (%d of %d bytes sent)", CLK_UNIT(parse->peer), rtc, (int)(outp - cmd_buffer)); + } + + clear_err(parse, ERR_BADIO); + return; +} + +/*-------------------------------------------------- + * init routine - setup timer + */ +static int +gps16x_poll_init( + struct parseunit *parse + ) +{ + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->procptr->action = gps16x_poll; + gps16x_poll(parse->peer); + } + + return 0; +} + +#else +static void +gps16x_message( + struct parseunit *parse, + parsetime_t *parsetime + ) +{} +static int +gps16x_poll_init( + struct parseunit *parse + ) +{ + return 1; +} +#endif /* CLOCK_MEINBERG */ + +/**=========================================================================== + ** clock polling support + **/ + +/*-------------------------------------------------- + * direct poll routine + */ +static void +poll_dpoll( + struct parseunit *parse + ) +{ + long rtc; + const char *ps = ((poll_info_t *)parse->parse_type->cl_data)->string; + long ct = ((poll_info_t *)parse->parse_type->cl_data)->count; + + rtc = write(parse->generic->io.fd, ps, ct); + if (rtc < 0) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: poll_dpoll: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + } + else + if (rtc != ct) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: poll_dpoll: failed to send cmd incomplete (%ld of %ld bytes sent)", CLK_UNIT(parse->peer), rtc, ct); + } + clear_err(parse, ERR_BADIO); +} + +/*-------------------------------------------------- + * periodic poll routine + */ +static void +poll_poll( + struct peer *peer + ) +{ + struct parseunit *parse = peer->procptr->unitptr; + + if (parse->parse_type->cl_poll) + parse->parse_type->cl_poll(parse); + + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->procptr->nextaction = current_time + ((poll_info_t *)parse->parse_type->cl_data)->rate; + } +} + +/*-------------------------------------------------- + * init routine - setup timer + */ +static int +poll_init( + struct parseunit *parse + ) +{ + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->procptr->action = poll_poll; + poll_poll(parse->peer); + } + + return 0; +} + +/**=========================================================================== + ** Trimble support + **/ + +/*------------------------------------------------------------- + * trimble TAIP init routine - setup EOL and then do poll_init. + */ +static int +trimbletaip_init( + struct parseunit *parse + ) +{ +#ifdef HAVE_TERMIOS + struct termios tio; +#endif +#ifdef HAVE_SYSV_TTYS + struct termio tio; +#endif + /* + * configure terminal line for trimble receiver + */ + if (TTY_GETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_init: tcgetattr(fd, &tio): %m", CLK_UNIT(parse->peer)); + return 0; + } + else + { + tio.c_cc[VEOL] = TRIMBLETAIP_EOL; + + if (TTY_SETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_init: tcsetattr(fd, &tio): %m", CLK_UNIT(parse->peer)); + return 0; + } + } + return poll_init(parse); +} + +/*-------------------------------------------------- + * trimble TAIP event routine - reset receiver upon data format trouble + */ +static const char *taipinit[] = { + ">FPV00000000<", + ">SRM;ID_FLAG=F;CS_FLAG=T;EC_FLAG=F;FR_FLAG=T;CR_FLAG=F<", + ">FTM00020001<", + (char *)0 +}; + +static void +trimbletaip_event( + struct parseunit *parse, + int event + ) +{ + switch (event) + { + case CEVNT_BADREPLY: /* reset on garbled input */ + case CEVNT_TIMEOUT: /* reset on no input */ + { + const char **iv; + + iv = taipinit; + while (*iv) + { + int rtc = (int) write(parse->generic->io.fd, *iv, strlen(*iv)); + if (rtc < 0) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_event: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + return; + } + else + { + if (rtc != (int)strlen(*iv)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_event: failed to send cmd incomplete (%d of %d bytes sent)", + CLK_UNIT(parse->peer), rtc, (int)strlen(*iv)); + return; + } + } + iv++; + } + + NLOG(NLOG_CLOCKINFO) + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_event: RECEIVER INITIALIZED", + CLK_UNIT(parse->peer)); + } + break; + + default: /* ignore */ + break; + } +} + +/* + * This driver supports the Trimble SVee Six Plus GPS receiver module. + * It should support other Trimble receivers which use the Trimble Standard + * Interface Protocol (see below). + * + * The module has a serial I/O port for command/data and a 1 pulse-per-second + * output, about 1 microsecond wide. The leading edge of the pulse is + * coincident with the change of the GPS second. This is the same as + * the change of the UTC second +/- ~1 microsecond. Some other clocks + * specifically use a feature in the data message as a timing reference, but + * the SVee Six Plus does not do this. In fact there is considerable jitter + * on the timing of the messages, so this driver only supports the use + * of the PPS pulse for accurate timing. Where it is determined that + * the offset is way off, when first starting up ntpd for example, + * the timing of the data stream is used until the offset becomes low enough + * (|offset| < CLOCK_MAX), at which point the pps offset is used. + * + * It can use either option for receiving PPS information - the 'ppsclock' + * stream pushed onto the serial data interface to timestamp the Carrier + * Detect interrupts, where the 1PPS connects to the CD line. This only + * works on SunOS 4.1.x currently. To select this, define PPSPPS in + * Config.local. The other option is to use a pulse-stretcher/level-converter + * to convert the PPS pulse into a RS232 start pulse & feed this into another + * tty port. To use this option, define PPSCLK in Config.local. The pps input, + * by whichever method, is handled in ntp_loopfilter.c + * + * The receiver uses a serial message protocol called Trimble Standard + * Interface Protocol (it can support others but this driver only supports + * TSIP). Messages in this protocol have the following form: + * + * <DLE><id> ... <data> ... <DLE><ETX> + * + * Any bytes within the <data> portion of value 10 hex (<DLE>) are doubled + * on transmission and compressed back to one on reception. Otherwise + * the values of data bytes can be anything. The serial interface is RS-422 + * asynchronous using 9600 baud, 8 data bits with odd party (**note** 9 bits + * in total!), and 1 stop bit. The protocol supports byte, integer, single, + * and double datatypes. Integers are two bytes, sent most significant first. + * Singles are IEEE754 single precision floating point numbers (4 byte) sent + * sign & exponent first. Doubles are IEEE754 double precision floating point + * numbers (8 byte) sent sign & exponent first. + * The receiver supports a large set of messages, only a small subset of + * which are used here. From driver to receiver the following are used: + * + * ID Description + * + * 21 Request current time + * 22 Mode Select + * 2C Set/Request operating parameters + * 2F Request UTC info + * 35 Set/Request I/O options + + * From receiver to driver the following are recognised: + * + * ID Description + * + * 41 GPS Time + * 44 Satellite selection, PDOP, mode + * 46 Receiver health + * 4B Machine code/status + * 4C Report operating parameters (debug only) + * 4F UTC correction data (used to get leap second warnings) + * 55 I/O options (debug only) + * + * All others are accepted but ignored. + * + */ + +#define PI 3.1415926535898 /* lots of sig figs */ +#define D2R PI/180.0 + +/*------------------------------------------------------------------- + * sendcmd, sendbyte, sendetx, sendflt, sendint implement the command + * interface to the receiver. + * + * CAVEAT: the sendflt, sendint routines are byte order dependend and + * float implementation dependend - these must be converted to portable + * versions ! + * + * CURRENT LIMITATION: float implementation. This runs only on systems + * with IEEE754 floats as native floats + */ + +typedef struct trimble +{ + u_long last_msg; /* last message received */ + u_long last_reset; /* last time a reset was issued */ + u_char qtracking; /* query tracking status */ + u_long ctrack; /* current tracking set */ + u_long ltrack; /* last tracking set */ +} trimble_t; + +union uval { + u_char bd[8]; + int iv; + float fv; + double dv; +}; + +struct txbuf +{ + short idx; /* index to first unused byte */ + u_char *txt; /* pointer to actual data buffer */ +}; + +void sendcmd (struct txbuf *buf, int c); +void sendbyte (struct txbuf *buf, int b); +void sendetx (struct txbuf *buf, struct parseunit *parse); +void sendint (struct txbuf *buf, int a); +void sendflt (struct txbuf *buf, double a); + +void +sendcmd( + struct txbuf *buf, + int c + ) +{ + buf->txt[0] = DLE; + buf->txt[1] = (u_char)c; + buf->idx = 2; +} + +void sendcmd (struct txbuf *buf, int c); +void sendbyte (struct txbuf *buf, int b); +void sendetx (struct txbuf *buf, struct parseunit *parse); +void sendint (struct txbuf *buf, int a); +void sendflt (struct txbuf *buf, double a); + +void +sendbyte( + struct txbuf *buf, + int b + ) +{ + if (b == DLE) + buf->txt[buf->idx++] = DLE; + buf->txt[buf->idx++] = (u_char)b; +} + +void +sendetx( + struct txbuf *buf, + struct parseunit *parse + ) +{ + buf->txt[buf->idx++] = DLE; + buf->txt[buf->idx++] = ETX; + + if (write(parse->generic->io.fd, buf->txt, (unsigned long)buf->idx) != buf->idx) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: sendetx: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + } + else + { +#ifdef DEBUG + if (debug > 2) + { + char buffer[256]; + + mkreadable(buffer, sizeof(buffer), (char *)buf->txt, (unsigned)buf->idx, 1); + printf("PARSE receiver #%d: transmitted message (%d bytes) >%s<\n", + CLK_UNIT(parse->peer), + buf->idx, buffer); + } +#endif + clear_err(parse, ERR_BADIO); + } +} + +void +sendint( + struct txbuf *buf, + int a + ) +{ + /* send 16bit int, msbyte first */ + sendbyte(buf, (u_char)((a>>8) & 0xff)); + sendbyte(buf, (u_char)(a & 0xff)); +} + +void +sendflt( + struct txbuf *buf, + double a + ) +{ + int i; + union uval uval; + + uval.fv = (float) a; +#ifdef WORDS_BIGENDIAN + for (i=0; i<=3; i++) +#else + for (i=3; i>=0; i--) +#endif + sendbyte(buf, uval.bd[i]); +} + +#define TRIM_POS_OPT 0x13 /* output position with high precision */ +#define TRIM_TIME_OPT 0x03 /* use UTC time stamps, on second */ + +/*-------------------------------------------------- + * trimble TSIP setup routine + */ +static int +trimbletsip_setup( + struct parseunit *parse, + const char *reason + ) +{ + u_char buffer[256]; + struct txbuf buf; + trimble_t *t = parse->localdata; + + if (t && t->last_reset && + ((t->last_reset + TRIMBLE_RESET_HOLDOFF) > current_time)) { + return 1; /* not yet */ + } + + if (t) + t->last_reset = current_time; + + buf.txt = buffer; + + sendcmd(&buf, CMD_CVERSION); /* request software versions */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_COPERPARAM); /* set operating parameters */ + sendbyte(&buf, 4); /* static */ + sendflt(&buf, 5.0*D2R); /* elevation angle mask = 10 deg XXX */ + sendflt(&buf, 4.0); /* s/n ratio mask = 6 XXX */ + sendflt(&buf, 12.0); /* PDOP mask = 12 */ + sendflt(&buf, 8.0); /* PDOP switch level = 8 */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CMODESEL); /* fix mode select */ + sendbyte(&buf, 1); /* time transfer mode */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CMESSAGE); /* request system message */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CSUPER); /* superpacket fix */ + sendbyte(&buf, 0x2); /* binary mode */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CIOOPTIONS); /* set I/O options */ + sendbyte(&buf, TRIM_POS_OPT); /* position output */ + sendbyte(&buf, 0x00); /* no velocity output */ + sendbyte(&buf, TRIM_TIME_OPT); /* UTC, compute on seconds */ + sendbyte(&buf, 0x00); /* no raw measurements */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CUTCPARAM); /* request UTC correction data */ + sendetx(&buf, parse); + + NLOG(NLOG_CLOCKINFO) + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletsip_setup: RECEIVER RE-INITIALIZED (%s)", CLK_UNIT(parse->peer), reason); + + return 0; +} + +/*-------------------------------------------------- + * TRIMBLE TSIP check routine + */ +static void +trimble_check( + struct peer *peer + ) +{ + struct parseunit *parse = peer->procptr->unitptr; + trimble_t *t = parse->localdata; + u_char buffer[256]; + struct txbuf buf; + buf.txt = buffer; + + if (t) + { + if (current_time > t->last_msg + TRIMBLETSIP_IDLE_TIME) + (void)trimbletsip_setup(parse, "message timeout"); + } + + poll_poll(parse->peer); /* emit query string and re-arm timer */ + + if (t && t->qtracking) + { + u_long oldsats = t->ltrack & ~t->ctrack; + + t->qtracking = 0; + t->ltrack = t->ctrack; + + if (oldsats) + { + int i; + + for (i = 0; oldsats; i++) { + if (oldsats & (1 << i)) + { + sendcmd(&buf, CMD_CSTATTRACK); + sendbyte(&buf, i+1); /* old sat */ + sendetx(&buf, parse); + } + oldsats &= ~(1 << i); + } + } + + sendcmd(&buf, CMD_CSTATTRACK); + sendbyte(&buf, 0x00); /* current tracking set */ + sendetx(&buf, parse); + } +} + +/*-------------------------------------------------- + * TRIMBLE TSIP end routine + */ +static void +trimbletsip_end( + struct parseunit *parse + ) +{ trimble_t *t = parse->localdata; + + if (t) + { + free(t); + parse->localdata = NULL; + } + parse->peer->procptr->nextaction = 0; + parse->peer->procptr->action = NULL; +} + +/*-------------------------------------------------- + * TRIMBLE TSIP init routine + */ +static int +trimbletsip_init( + struct parseunit *parse + ) +{ +#if defined(VEOL) || defined(VEOL2) +#ifdef HAVE_TERMIOS + struct termios tio; /* NEEDED FOR A LONG TIME ! */ +#endif +#ifdef HAVE_SYSV_TTYS + struct termio tio; /* NEEDED FOR A LONG TIME ! */ +#endif + /* + * allocate local data area + */ + if (!parse->localdata) + { + trimble_t *t; + + t = (trimble_t *)(parse->localdata = emalloc(sizeof(trimble_t))); + + if (t) + { + memset((char *)t, 0, sizeof(trimble_t)); + t->last_msg = current_time; + } + } + + parse->peer->procptr->action = trimble_check; + parse->peer->procptr->nextaction = current_time; + + /* + * configure terminal line for ICANON mode with VEOL characters + */ + if (TTY_GETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletsip_init: tcgetattr(%d, &tio): %m", CLK_UNIT(parse->peer), parse->generic->io.fd); + return 0; + } + else + { + if ((parse_clockinfo[CLK_TYPE(parse->peer)].cl_lflag & ICANON)) + { +#ifdef VEOL + tio.c_cc[VEOL] = ETX; +#endif +#ifdef VEOL2 + tio.c_cc[VEOL2] = DLE; +#endif + } + + if (TTY_SETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletsip_init: tcsetattr(%d, &tio): %m", CLK_UNIT(parse->peer), parse->generic->io.fd); + return 0; + } + } +#endif + return trimbletsip_setup(parse, "initial startup"); +} + +/*------------------------------------------------------------ + * trimbletsip_event - handle Trimble events + * simple evente handler - attempt to re-initialize receiver + */ +static void +trimbletsip_event( + struct parseunit *parse, + int event + ) +{ + switch (event) + { + case CEVNT_BADREPLY: /* reset on garbled input */ + case CEVNT_TIMEOUT: /* reset on no input */ + (void)trimbletsip_setup(parse, "event BAD_REPLY/TIMEOUT"); + break; + + default: /* ignore */ + break; + } +} + +/* + * getflt, getint convert fields in the incoming data into the + * appropriate type of item + * + * CAVEAT: these routines are currently definitely byte order dependent + * and assume Representation(float) == IEEE754 + * These functions MUST be converted to portable versions (especially + * converting the float representation into ntp_fp formats in order + * to avoid floating point operations at all! + */ + +static float +getflt( + u_char *bp + ) +{ + union uval uval; + +#ifdef WORDS_BIGENDIAN + uval.bd[0] = *bp++; + uval.bd[1] = *bp++; + uval.bd[2] = *bp++; + uval.bd[3] = *bp; +#else /* ! WORDS_BIGENDIAN */ + uval.bd[3] = *bp++; + uval.bd[2] = *bp++; + uval.bd[1] = *bp++; + uval.bd[0] = *bp; +#endif /* ! WORDS_BIGENDIAN */ + return uval.fv; +} + +static double +getdbl( + u_char *bp + ) +{ + union uval uval; + +#ifdef WORDS_BIGENDIAN + uval.bd[0] = *bp++; + uval.bd[1] = *bp++; + uval.bd[2] = *bp++; + uval.bd[3] = *bp++; + uval.bd[4] = *bp++; + uval.bd[5] = *bp++; + uval.bd[6] = *bp++; + uval.bd[7] = *bp; +#else /* ! WORDS_BIGENDIAN */ + uval.bd[7] = *bp++; + uval.bd[6] = *bp++; + uval.bd[5] = *bp++; + uval.bd[4] = *bp++; + uval.bd[3] = *bp++; + uval.bd[2] = *bp++; + uval.bd[1] = *bp++; + uval.bd[0] = *bp; +#endif /* ! WORDS_BIGENDIAN */ + return uval.dv; +} + +static int +getshort( + unsigned char *p + ) +{ + return (int) get_msb_short(&p); +} + +/*-------------------------------------------------- + * trimbletsip_message - process trimble messages + */ +#define RTOD (180.0 / 3.1415926535898) +#define mb(_X_) (buffer[2+(_X_)]) /* shortcut for buffer access */ + +static void +trimbletsip_message( + struct parseunit *parse, + parsetime_t *parsetime + ) +{ + unsigned char *buffer = parsetime->parse_msg; + unsigned int size = parsetime->parse_msglen; + + if ((size < 4) || + (buffer[0] != DLE) || + (buffer[size-1] != ETX) || + (buffer[size-2] != DLE)) + { +#ifdef DEBUG + if (debug > 2) { + size_t i; + + printf("TRIMBLE BAD packet, size %d:\n ", size); + for (i = 0; i < size; i++) { + printf ("%2.2x, ", buffer[i]&0xff); + if (i%16 == 15) printf("\n\t"); + } + printf("\n"); + } +#endif + return; + } + else + { + u_short var_flag; + trimble_t *tr = parse->localdata; + unsigned int cmd = buffer[1]; + char pbuffer[200]; + char *t = pbuffer; + cmd_info_t *s; + +#ifdef DEBUG + if (debug > 3) { + size_t i; + + printf("TRIMBLE packet 0x%02x, size %d:\n ", cmd, size); + for (i = 0; i < size; i++) { + printf ("%2.2x, ", buffer[i]&0xff); + if (i%16 == 15) printf("\n\t"); + } + printf("\n"); + } +#endif + + if (tr) + tr->last_msg = current_time; + + s = trimble_convert(cmd, trimble_rcmds); + + if (s) + { + t = ap(pbuffer, sizeof(pbuffer), t, "%s=\"", s->varname); + } + else + { + DPRINTF(1, ("TRIMBLE UNKNOWN COMMAND 0x%02x\n", cmd)); + return; + } + + var_flag = (u_short) s->varmode; + + switch(cmd) + { + case CMD_RCURTIME: + t = ap(pbuffer, sizeof(pbuffer), t, "%f, %d, %f", + getflt((unsigned char *)&mb(0)), getshort((unsigned char *)&mb(4)), + getflt((unsigned char *)&mb(6))); + break; + + case CMD_RBEST4: + t = ap(pbuffer, sizeof(pbuffer), t, "mode: "); + switch (mb(0) & 0xF) + { + default: + t = ap(pbuffer, sizeof(pbuffer), t, + "0x%x", mb(0) & 0x7); + break; + + case 1: + t = ap(pbuffer, sizeof(pbuffer), t, "0D"); + break; + + case 3: + t = ap(pbuffer, sizeof(pbuffer), t, "2D"); + break; + + case 4: + t = ap(pbuffer, sizeof(pbuffer), t, "3D"); + break; + } + if (mb(0) & 0x10) + t = ap(pbuffer, sizeof(pbuffer), t, "-MANUAL, "); + else + t = ap(pbuffer, sizeof(pbuffer), t, "-AUTO, "); + + t = ap(pbuffer, sizeof(pbuffer), t, "satellites %02d %02d %02d %02d, PDOP %.2f, HDOP %.2f, VDOP %.2f, TDOP %.2f", + mb(1), mb(2), mb(3), mb(4), + getflt((unsigned char *)&mb(5)), + getflt((unsigned char *)&mb(9)), + getflt((unsigned char *)&mb(13)), + getflt((unsigned char *)&mb(17))); + + break; + + case CMD_RVERSION: + t = ap(pbuffer, sizeof(pbuffer), t, "%d.%d (%d/%d/%d)", + mb(0)&0xff, mb(1)&0xff, 1900+(mb(4)&0xff), mb(2)&0xff, mb(3)&0xff); + break; + + case CMD_RRECVHEALTH: + { + static const char *msgs[] = + { + "Battery backup failed", + "Signal processor error", + "Alignment error, channel or chip 1", + "Alignment error, channel or chip 2", + "Antenna feed line fault", + "Excessive ref freq. error", + "<BIT 6>", + "<BIT 7>" + }; + + int i, bits; + + switch (mb(0) & 0xFF) + { + default: + t = ap(pbuffer, sizeof(pbuffer), t, "illegal value 0x%02x", mb(0) & 0xFF); + break; + case 0x00: + t = ap(pbuffer, sizeof(pbuffer), t, "doing position fixes"); + break; + case 0x01: + t = ap(pbuffer, sizeof(pbuffer), t, "no GPS time yet"); + break; + case 0x03: + t = ap(pbuffer, sizeof(pbuffer), t, "PDOP too high"); + break; + case 0x08: + t = ap(pbuffer, sizeof(pbuffer), t, "no usable satellites"); + break; + case 0x09: + t = ap(pbuffer, sizeof(pbuffer), t, "only ONE usable satellite"); + break; + case 0x0A: + t = ap(pbuffer, sizeof(pbuffer), t, "only TWO usable satellites"); + break; + case 0x0B: + t = ap(pbuffer, sizeof(pbuffer), t, "only THREE usable satellites"); + break; + case 0x0C: + t = ap(pbuffer, sizeof(pbuffer), t, "the chosen satellite is unusable"); + break; + } + + bits = mb(1) & 0xFF; + + for (i = 0; i < 8; i++) + if (bits & (0x1<<i)) + { + t = ap(pbuffer, sizeof(pbuffer), t, ", %s", msgs[i]); + } + } + break; + + case CMD_RMESSAGE: + mkreadable(t, (int)BUFFER_SIZE(pbuffer, t), (char *)&mb(0), (unsigned)(size - 2 - (&mb(0) - buffer)), 0); + break; + + case CMD_RMACHSTAT: + { + static const char *msgs[] = + { + "Synthesizer Fault", + "Battery Powered Time Clock Fault", + "A-to-D Converter Fault", + "The almanac stored in the receiver is not complete and current", + "<BIT 4>", + "<BIT 5", + "<BIT 6>", + "<BIT 7>" + }; + + int i, bits; + + t = ap(pbuffer, sizeof(pbuffer), t, "machine id 0x%02x", mb(0) & 0xFF); + bits = mb(1) & 0xFF; + + for (i = 0; i < 8; i++) + if (bits & (0x1<<i)) + { + t = ap(pbuffer, sizeof(pbuffer), t, ", %s", msgs[i]); + } + + t = ap(pbuffer, sizeof(pbuffer), t, ", Superpackets %ssupported", (mb(2) & 0xFF) ? "" :"un" ); + } + break; + + case CMD_ROPERPARAM: + t = ap(pbuffer, sizeof(pbuffer), t, "%2x %.1f %.1f %.1f %.1f", + mb(0), getflt((unsigned char *)&mb(1)), getflt((unsigned char *)&mb(5)), + getflt((unsigned char *)&mb(9)), getflt((unsigned char *)&mb(13))); + break; + + case CMD_RUTCPARAM: + { + float t0t = getflt((unsigned char *)&mb(14)); + short wnt = (short) getshort((unsigned char *)&mb(18)); + short dtls = (short) getshort((unsigned char *)&mb(12)); + short wnlsf = (short) getshort((unsigned char *)&mb(20)); + short dn = (short) getshort((unsigned char *)&mb(22)); + short dtlsf = (short) getshort((unsigned char *)&mb(24)); + + if ((int)t0t != 0) + { + mk_utcinfo(t, wnt, wnlsf, dn, dtls, dtlsf, BUFFER_SIZE(pbuffer, t)); + } + else + { + t = ap(pbuffer, sizeof(pbuffer), t, "<NO UTC DATA>"); + } + } + break; + + case CMD_RSAT1BIAS: + t = ap(pbuffer, sizeof(pbuffer), t, "%.1fm %.2fm/s at %.1fs", + getflt(&mb(0)), getflt(&mb(4)), getflt(&mb(8))); + break; + + case CMD_RIOOPTIONS: + { + t = ap(pbuffer, sizeof(pbuffer), t, "%02x %02x %02x %02x", + mb(0), mb(1), mb(2), mb(3)); + if (mb(0) != TRIM_POS_OPT || + mb(2) != TRIM_TIME_OPT) + { + (void)trimbletsip_setup(parse, "bad io options"); + } + } + break; + + case CMD_RSPOSXYZ: + { + double x = getflt((unsigned char *)&mb(0)); + double y = getflt((unsigned char *)&mb(4)); + double z = getflt((unsigned char *)&mb(8)); + double f = getflt((unsigned char *)&mb(12)); + + if (f > 0.0) + t = ap(pbuffer, sizeof(pbuffer), t, "x= %.1fm, y= %.1fm, z= %.1fm, time_of_fix= %f sec", + x, y, z, + f); + else + return; + } + break; + + case CMD_RSLLAPOS: + { + double lat = getflt((unsigned char *)&mb(0)); + double lng = getflt((unsigned char *)&mb(4)); + double f = getflt((unsigned char *)&mb(12)); + + if (f > 0.0) + t = ap(pbuffer, sizeof(pbuffer), t, "lat %f %c, long %f %c, alt %.2fm", + ((lat < 0.0) ? (-lat) : (lat))*RTOD, (lat < 0.0 ? 'S' : 'N'), + ((lng < 0.0) ? (-lng) : (lng))*RTOD, (lng < 0.0 ? 'W' : 'E'), + getflt((unsigned char *)&mb(8))); + else + return; + } + break; + + case CMD_RDOUBLEXYZ: + { + double x = getdbl((unsigned char *)&mb(0)); + double y = getdbl((unsigned char *)&mb(8)); + double z = getdbl((unsigned char *)&mb(16)); + t = ap(pbuffer, sizeof(pbuffer), t, "x= %.1fm, y= %.1fm, z= %.1fm", + x, y, z); + } + break; + + case CMD_RDOUBLELLA: + { + double lat = getdbl((unsigned char *)&mb(0)); + double lng = getdbl((unsigned char *)&mb(8)); + t = ap(pbuffer, sizeof(pbuffer), t, "lat %f %c, lon %f %c, alt %.2fm", + ((lat < 0.0) ? (-lat) : (lat))*RTOD, (lat < 0.0 ? 'S' : 'N'), + ((lng < 0.0) ? (-lng) : (lng))*RTOD, (lng < 0.0 ? 'W' : 'E'), + getdbl((unsigned char *)&mb(16))); + } + break; + + case CMD_RALLINVIEW: + { + int i, sats; + + t = ap(pbuffer, sizeof(pbuffer), t, "mode: "); + switch (mb(0) & 0x7) + { + default: + t = ap(pbuffer, sizeof(pbuffer), t, "0x%x", mb(0) & 0x7); + break; + + case 3: + t = ap(pbuffer, sizeof(pbuffer), t, "2D"); + break; + + case 4: + t = ap(pbuffer, sizeof(pbuffer), t, "3D"); + break; + } + if (mb(0) & 0x8) + t = ap(pbuffer, sizeof(pbuffer), t, "-MANUAL, "); + else + t = ap(pbuffer, sizeof(pbuffer), t, "-AUTO, "); + + sats = (mb(0)>>4) & 0xF; + + t = ap(pbuffer, sizeof(pbuffer), t, "PDOP %.2f, HDOP %.2f, VDOP %.2f, TDOP %.2f, %d satellite%s in view: ", + getflt((unsigned char *)&mb(1)), + getflt((unsigned char *)&mb(5)), + getflt((unsigned char *)&mb(9)), + getflt((unsigned char *)&mb(13)), + sats, (sats == 1) ? "" : "s"); + + for (i=0; i < sats; i++) + { + t = ap(pbuffer, sizeof(pbuffer), t, "%s%02d", i ? ", " : "", mb(17+i)); + if (tr) + tr->ctrack |= (1 << (mb(17+i)-1)); + } + + if (tr) + { /* mark for tracking status query */ + tr->qtracking = 1; + } + } + break; + + case CMD_RSTATTRACK: + { + t = ap(pbuffer, sizeof(pbuffer), t-2, "[%02d]=\"", mb(0)); /* add index to var name */ + if (getflt((unsigned char *)&mb(4)) < 0.0) + { + t = ap(pbuffer, sizeof(pbuffer), t, "<NO MEASUREMENTS>"); + var_flag &= (u_short)(~DEF); + } + else + { + t = ap(pbuffer, sizeof(pbuffer), t, "ch=%d, acq=%s, eph=%d, signal_level= %5.2f, elevation= %5.2f, azimuth= %6.2f", + (mb(1) & 0xFF)>>3, + mb(2) ? ((mb(2) == 1) ? "ACQ" : "SRCH") : "NEVER", + mb(3), + getflt((unsigned char *)&mb(4)), + getflt((unsigned char *)&mb(12)) * RTOD, + getflt((unsigned char *)&mb(16)) * RTOD); + if (mb(20)) + { + var_flag &= (u_short)(~DEF); + t = ap(pbuffer, sizeof(pbuffer), t, ", OLD"); + } + if (mb(22)) + { + if (mb(22) == 1) + t = ap(pbuffer, sizeof(pbuffer), t, ", BAD PARITY"); + else + if (mb(22) == 2) + t = ap(pbuffer, sizeof(pbuffer), t, ", BAD EPH HEALTH"); + } + if (mb(23)) + t = ap(pbuffer, sizeof(pbuffer), t, ", collecting data"); + } + } + break; + + default: + t = ap(pbuffer, sizeof(pbuffer), t, "<UNDECODED>"); + break; + } + + t = ap(pbuffer, sizeof(pbuffer), t, "\""); + set_var(&parse->kv, pbuffer, sizeof(pbuffer), var_flag); + } +} + + +/**============================================================ + ** RAWDCF support + **/ + +/*-------------------------------------------------- + * rawdcf_init_1 - set up modem lines for RAWDCF receivers + * SET DTR line + */ +#if defined(TIOCMSET) && (defined(TIOCM_DTR) || defined(CIOCM_DTR)) +static int +rawdcf_init_1( + struct parseunit *parse + ) +{ + /* fixed 2000 for using with Linux by Wolfram Pienkoss <wp@bszh.de> */ + /* + * You can use the RS232 to supply the power for a DCF77 receiver. + * Here a voltage between the DTR and the RTS line is used. Unfortunately + * the name has changed from CIOCM_DTR to TIOCM_DTR recently. + */ + int sl232; + + if (ioctl(parse->generic->io.fd, TIOCMGET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_1: WARNING: ioctl(fd, TIOCMGET, [C|T]IOCM_DTR): %m", CLK_UNIT(parse->peer)); + return 0; + } + +#ifdef TIOCM_DTR + sl232 = (sl232 & ~TIOCM_RTS) | TIOCM_DTR; /* turn on DTR, clear RTS for power supply */ +#else + sl232 = (sl232 & ~CIOCM_RTS) | CIOCM_DTR; /* turn on DTR, clear RTS for power supply */ +#endif + + if (ioctl(parse->generic->io.fd, TIOCMSET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_1: WARNING: ioctl(fd, TIOCMSET, [C|T]IOCM_DTR): %m", CLK_UNIT(parse->peer)); + } + return 0; +} +#else +static int +rawdcfdtr_init_1( + struct parseunit *parse + ) +{ + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_1: WARNING: OS interface incapable of setting DTR to power DCF modules", CLK_UNIT(parse->peer)); + return 0; +} +#endif /* DTR initialisation type */ + +/*-------------------------------------------------- + * rawdcf_init_2 - set up modem lines for RAWDCF receivers + * CLR DTR line, SET RTS line + */ +#if defined(TIOCMSET) && (defined(TIOCM_RTS) || defined(CIOCM_RTS)) +static int +rawdcf_init_2( + struct parseunit *parse + ) +{ + /* fixed 2000 for using with Linux by Wolfram Pienkoss <wp@bszh.de> */ + /* + * You can use the RS232 to supply the power for a DCF77 receiver. + * Here a voltage between the DTR and the RTS line is used. Unfortunately + * the name has changed from CIOCM_DTR to TIOCM_DTR recently. + */ + int sl232; + + if (ioctl(parse->generic->io.fd, TIOCMGET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_2: WARNING: ioctl(fd, TIOCMGET, [C|T]IOCM_RTS): %m", CLK_UNIT(parse->peer)); + return 0; + } + +#ifdef TIOCM_RTS + sl232 = (sl232 & ~TIOCM_DTR) | TIOCM_RTS; /* turn on RTS, clear DTR for power supply */ +#else + sl232 = (sl232 & ~CIOCM_DTR) | CIOCM_RTS; /* turn on RTS, clear DTR for power supply */ +#endif + + if (ioctl(parse->generic->io.fd, TIOCMSET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_2: WARNING: ioctl(fd, TIOCMSET, [C|T]IOCM_RTS): %m", CLK_UNIT(parse->peer)); + } + return 0; +} +#else +static int +rawdcf_init_2( + struct parseunit *parse + ) +{ + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_2: WARNING: OS interface incapable of setting RTS to power DCF modules", CLK_UNIT(parse->peer)); + return 0; +} +#endif /* DTR initialisation type */ + +#else /* defined(REFCLOCK) && defined(PARSE) */ +NONEMPTY_TRANSLATION_UNIT +#endif /* defined(REFCLOCK) && defined(PARSE) */ + +/* + * History: + * + * refclock_parse.c,v + * Revision 4.81 2009/05/01 10:15:29 kardel + * use new refclock_ppsapi interface + * + * Revision 4.80 2007/08/11 12:06:29 kardel + * update comments wrt/ to PPS + * + * Revision 4.79 2007/08/11 11:52:23 kardel + * - terminate io bindings before io_closeclock() will close our file descriptor + * + * Revision 4.78 2006/12/22 20:08:27 kardel + * Bug 746 (RFE): add configuration for Expert mouseCLOCK USB v2.0 as mode 19 + * + * Revision 4.77 2006/08/05 07:44:49 kardel + * support optionally separate PPS devices via /dev/refclockpps-{0..3} + * + * Revision 4.76 2006/06/22 18:40:47 kardel + * clean up signedness (gcc 4) + * + * Revision 4.75 2006/06/22 16:58:10 kardel + * Bug #632: call parse_ppsapi() in parse_ctl() when updating + * the PPS offset. Fix sign of offset passed to kernel. + * + * Revision 4.74 2006/06/18 21:18:37 kardel + * NetBSD Coverity CID 3796: possible NULL deref + * + * Revision 4.73 2006/05/26 14:23:46 kardel + * cleanup of copyright info + * + * Revision 4.72 2006/05/26 14:19:43 kardel + * cleanup of ioctl cruft + * + * Revision 4.71 2006/05/26 14:15:57 kardel + * delay adding refclock to async refclock io after all initializations + * + * Revision 4.70 2006/05/25 18:20:50 kardel + * bug #619 + * terminate parse io engine after de-registering + * from refclock io engine + * + * Revision 4.69 2006/05/25 17:28:02 kardel + * complete refclock io structure initialization *before* inserting it into the + * refclock input machine (avoids null pointer deref) (bug #619) + * + * Revision 4.68 2006/05/01 17:02:51 kardel + * copy receiver method also for newlwy created receive buffers + * + * Revision 4.67 2006/05/01 14:37:29 kardel + * If an input buffer parses into more than one message do insert the + * parsed message in a new input buffer instead of processing it + * directly. This avoids deed complicated processing in signal + * handling. + * + * Revision 4.66 2006/03/18 00:45:30 kardel + * coverity fixes found in NetBSD coverity scan + * + * Revision 4.65 2006/01/26 06:08:33 kardel + * output errno on PPS setup failure + * + * Revision 4.64 2005/11/09 20:44:47 kardel + * utilize full PPS timestamp resolution from PPS API + * + * Revision 4.63 2005/10/07 22:10:25 kardel + * bounded buffer implementation + * + * Revision 4.62.2.2 2005/09/25 10:20:16 kardel + * avoid unexpected buffer overflows due to sprintf("%f") on strange floats: + * replace almost all str* and *printf functions be their buffer bounded + * counterparts + * + * Revision 4.62.2.1 2005/08/27 16:19:27 kardel + * limit re-set rate of trimble clocks + * + * Revision 4.62 2005/08/06 17:40:00 kardel + * cleanup size handling wrt/ to buffer boundaries + * + * Revision 4.61 2005/07/27 21:16:19 kardel + * fix a long (> 11 years) misconfiguration wrt/ Meinberg cflag factory + * default setup. CSTOPB was missing for the 7E2 default data format of + * the DCF77 clocks. + * + * Revision 4.60 2005/07/17 21:14:44 kardel + * change contents of version string to include the RCS/CVS Id + * + * Revision 4.59 2005/07/06 06:56:38 kardel + * syntax error + * + * Revision 4.58 2005/07/04 13:10:40 kardel + * fix bug 455: tripping over NULL pointer on cleanup + * fix shadow storage logic for ppsphaseadjust and trustime wrt/ time2 + * fix compiler warnings for some platforms wrt/ printf formatstrings and + * varying structure element sizes + * reorder assignment in binding to avoid tripping over NULL pointers + * + * Revision 4.57 2005/06/25 09:25:19 kardel + * sort out log output sequence + * + * Revision 4.56 2005/06/14 21:47:27 kardel + * collect samples only if samples are ok (sync or trusted flywheel) + * propagate pps phase adjustment value to kernel via PPSAPI to help HARDPPS + * en- and dis-able HARDPPS in correlation to receiver sync state + * + * Revision 4.55 2005/06/02 21:28:31 kardel + * clarify trust logic + * + * Revision 4.54 2005/06/02 17:06:49 kardel + * change status reporting to use fixed refclock_report() + * + * Revision 4.53 2005/06/02 16:33:31 kardel + * fix acceptance of clocks unsync clocks right at start + * + * Revision 4.52 2005/05/26 21:55:06 kardel + * cleanup status reporting + * + * Revision 4.51 2005/05/26 19:19:14 kardel + * implement fast refclock startup + * + * Revision 4.50 2005/04/16 20:51:35 kardel + * set hardpps_enable = 1 when binding a kernel PPS source + * + * Revision 4.49 2005/04/16 17:29:26 kardel + * add non polling clock type 18 for just listenning to Meinberg clocks + * + * Revision 4.48 2005/04/16 16:22:27 kardel + * bk sync 20050415 ntp-dev + * + * Revision 4.47 2004/11/29 10:42:48 kardel + * bk sync ntp-dev 20041129 + * + * Revision 4.46 2004/11/29 10:26:29 kardel + * keep fudgetime2 in sync with trusttime/ppsphaseadjust depending in flag1 + * + * Revision 4.45 2004/11/14 20:53:20 kardel + * clear PPS flags after using them + * + * Revision 4.44 2004/11/14 15:29:41 kardel + * support PPSAPI, upgrade Copyright to Berkeley style + * + * Revision 4.43 2001/05/26 22:53:16 kardel + * 20010526 reconcilation + * + * Revision 4.42 2000/05/14 15:31:51 kardel + * PPSAPI && RAWDCF modemline support + * + * Revision 4.41 2000/04/09 19:50:45 kardel + * fixed rawdcfdtr_init() -> rawdcf_init_1 + * + * Revision 4.40 2000/04/09 15:27:55 kardel + * modem line fiddle in rawdcf_init_2 + * + * Revision 4.39 2000/03/18 09:16:55 kardel + * PPSAPI integration + * + * Revision 4.38 2000/03/05 20:25:06 kardel + * support PPSAPI + * + * Revision 4.37 2000/03/05 20:11:14 kardel + * 4.0.99g reconcilation + * + * Revision 4.36 1999/11/28 17:18:20 kardel + * disabled burst mode + * + * Revision 4.35 1999/11/28 09:14:14 kardel + * RECON_4_0_98F + * + * Revision 4.34 1999/05/14 06:08:05 kardel + * store current_time in a suitable container (u_long) + * + * Revision 4.33 1999/05/13 21:48:38 kardel + * double the no response timeout interval + * + * Revision 4.32 1999/05/13 20:09:13 kardel + * complain only about missing polls after a full poll interval + * + * Revision 4.31 1999/05/13 19:59:32 kardel + * add clock type 16 for RTS set DTR clr in RAWDCF + * + * Revision 4.30 1999/02/28 20:36:43 kardel + * fixed printf fmt + * + * Revision 4.29 1999/02/28 19:58:23 kardel + * updated copyright information + * + * Revision 4.28 1999/02/28 19:01:50 kardel + * improved debug out on sent Meinberg messages + * + * Revision 4.27 1999/02/28 18:05:55 kardel + * no linux/ppsclock.h stuff + * + * Revision 4.26 1999/02/28 15:27:27 kardel + * wharton clock integration + * + * Revision 4.25 1999/02/28 14:04:46 kardel + * added missing double quotes to UTC information string + * + * Revision 4.24 1999/02/28 12:06:50 kardel + * (parse_control): using gmprettydate instead of prettydate() + * (mk_utcinfo): new function for formatting GPS derived UTC information + * (gps16x_message): changed to use mk_utcinfo() + * (trimbletsip_message): changed to use mk_utcinfo() + * ignoring position information in unsynchronized mode + * (parse_start): augument linux support for optional ASYNC_LOW_LATENCY + * + * Revision 4.23 1999/02/23 19:47:53 kardel + * fixed #endifs + * (stream_receive): fixed formats + * + * Revision 4.22 1999/02/22 06:21:02 kardel + * use new autoconfig symbols + * + * Revision 4.21 1999/02/21 12:18:13 kardel + * 4.91f reconcilation + * + * Revision 4.20 1999/02/21 10:53:36 kardel + * initial Linux PPSkit version + * + * Revision 4.19 1999/02/07 09:10:45 kardel + * clarify STREAMS mitigation rules in comment + * + * Revision 4.18 1998/12/20 23:45:34 kardel + * fix types and warnings + * + * Revision 4.17 1998/11/15 21:24:51 kardel + * cannot access mbg_ routines when CLOCK_MEINBERG + * is not defined + * + * Revision 4.16 1998/11/15 20:28:17 kardel + * Release 4.0.73e13 reconcilation + * + * Revision 4.15 1998/08/22 21:56:08 kardel + * fixed IO handling for non-STREAM IO + * + * Revision 4.14 1998/08/16 19:00:48 kardel + * (gps16x_message): reduced UTC parameter information (dropped A0,A1) + * made uval a local variable (killed one of the last globals) + * (sendetx): added logging of messages when in debug mode + * (trimble_check): added periodic checks to facilitate re-initialization + * (trimbletsip_init): made use of EOL character if in non-kernel operation + * (trimbletsip_message): extended message interpretation + * (getdbl): fixed data conversion + * + * Revision 4.13 1998/08/09 22:29:13 kardel + * Trimble TSIP support + * + * Revision 4.12 1998/07/11 10:05:34 kardel + * Release 4.0.73d reconcilation + * + * Revision 4.11 1998/06/14 21:09:42 kardel + * Sun acc cleanup + * + * Revision 4.10 1998/06/13 12:36:45 kardel + * signed/unsigned, name clashes + * + * Revision 4.9 1998/06/12 15:30:00 kardel + * prototype fixes + * + * Revision 4.8 1998/06/12 11:19:42 kardel + * added direct input processing routine for refclocks in + * order to avaiod that single character io gobbles up all + * receive buffers and drops input data. (Problem started + * with fast machines so a character a buffer was possible + * one of the few cases where faster machines break existing + * allocation algorithms) + * + * Revision 4.7 1998/06/06 18:35:20 kardel + * (parse_start): added BURST mode initialisation + * + * Revision 4.6 1998/05/27 06:12:46 kardel + * RAWDCF_BASEDELAY default added + * old comment removed + * casts for ioctl() + * + * Revision 4.5 1998/05/25 22:05:09 kardel + * RAWDCF_SETDTR option removed + * clock type 14 attempts to set DTR for + * power supply of RAWDCF receivers + * + * Revision 4.4 1998/05/24 16:20:47 kardel + * updated comments referencing Meinberg clocks + * added RAWDCF clock with DTR set option as type 14 + * + * Revision 4.3 1998/05/24 10:48:33 kardel + * calibrated CONRAD RAWDCF default fudge factor + * + * Revision 4.2 1998/05/24 09:59:35 kardel + * corrected version information (ntpq support) + * + * Revision 4.1 1998/05/24 09:52:31 kardel + * use fixed format only (new IO model) + * output debug to stdout instead of msyslog() + * don't include >"< in ASCII output in order not to confuse + * ntpq parsing + * + * Revision 4.0 1998/04/10 19:52:11 kardel + * Start 4.0 release version numbering + * + * Revision 1.2 1998/04/10 19:28:04 kardel + * initial NTP VERSION 4 integration of PARSE with GPS166 binary support + * derived from 3.105.1.2 from V3 tree + * + * Revision information 3.1 - 3.105 from log deleted 1998/04/10 kardel + * + */ |