refclock_msfees.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright (c) 1996, 1999 by Sun Microsystems, Inc.
* All Rights Reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* refclock_ees - clock driver for the EES M201 receiver */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_unixtime.h"
#include "ntp_calendar.h"
#if defined(HAVE_BSD_TTYS)
#include <sgtty.h>
#endif /* HAVE_BSD_TTYS */
#if defined(HAVE_SYSV_TTYS)
#include <termio.h>
#endif /* HAVE_SYSV_TTYS */
#include <termios.h>
#include <stropts.h>
#if defined(PPS) && !defined(SYS_SOLARIS)
#include <sys/ppsclock.h>
#endif /* PPS && !SYS_SOLARIS */
#include "ntp_stdlib.h"
/* Currently REQUIRES STREAM and PPSCD. CLK and CBREAK modes
* were removed as the code was overly hairy, they weren't in use
* (hence probably didn't work). Still in RCS file at cl.cam.ac.uk
*/
/*
fudgefactor = fudgetime1;
os_delay = fudgetime2;
offset_fudge = os_delay + fudgefactor + inherent_delay;
stratumtouse = fudgeval1 & 0xf
debug = fudgeval2;
sloppyclockflag = flags & CLK_FLAG1;
1 log smoothing summary when processing sample
4 dump the buffer from the clock
8 EIOGETKD the last n uS time stamps
if (flags & CLK_FLAG2 && unitinuse) ees->leaphold = 0;
ees->dump_vals = flags & CLK_FLAG3;
ees->usealldata = flags & CLK_FLAG4;
bug->values[0] = (ees->lasttime) ? current_time - ees->lasttime : 0;
bug->values[1] = (ees->clocklastgood)?current_time-ees->clocklastgood:0;
bug->values[2] = (u_long)ees->status;
bug->values[3] = (u_long)ees->lastevent;
bug->values[4] = (u_long)ees->reason;
bug->values[5] = (u_long)ees->nsamples;
bug->values[6] = (u_long)ees->codestate;
bug->values[7] = (u_long)ees->day;
bug->values[8] = (u_long)ees->hour;
bug->values[9] = (u_long)ees->minute;
bug->values[10] = (u_long)ees->second;
bug->values[11] = (u_long)ees->tz;
bug->values[12] = ees->yearstart;
bug->values[13] = (ees->leaphold > current_time) ?
ees->leaphold - current_time : 0;
bug->values[14] = inherent_delay[unit].l_uf;
bug->values[15] = offset_fudge[unit].l_uf;
bug->times[0] = ees->reftime;
bug->times[1] = ees->arrvtime;
bug->times[2] = ees->lastsampletime;
bug->times[3] = ees->offset;
bug->times[4] = ees->lowoffset;
bug->times[5] = ees->highoffset;
bug->times[6] = inherent_delay[unit];
bug->times[8] = os_delay[unit];
bug->times[7] = fudgefactor[unit];
bug->times[9] = offset_fudge[unit];
bug->times[10]= ees->yearstart, 0;
*/
/* This should support the use of an EES M201 receiver with RS232
* output (modified to transmit time once per second).
*
* For the format of the message sent by the clock, see the EESM_
* definitions below.
*
* It appears to run free for an integral number of minutes, until the error
* reaches 4mS, at which point it steps at second = 01.
* It appears that sometimes it steps 4mS (say at 7 min interval),
* then the next minute it decides that it was an error, so steps back.
* On the next minute it steps forward again :-(
* This is typically 16.5uS/S then 3975uS at the 4min re-sync,
* or 9.5uS/S then 3990.5uS at a 7min re-sync,
* at which point it may loose the "00" second time stamp.
* I assume that the most accurate time is just AFTER the re-sync.
* Hence remember the last cycle interval,
*
* Can run in any one of:
*
* PPSCD PPS signal sets CD which interupts, and grabs the current TOD
* (sun) *in the interupt code*, so as to avoid problems with
* the STREAMS scheduling.
*
* It appears that it goes 16.5 uS slow each second, then every 4 mins it
* generates no "00" second tick, and gains 3975 uS. Ho Hum ! (93/2/7)
*/
/* Definitions */
#ifndef MAXUNITS
#endif
#ifndef EES232
#endif
/* Other constant stuff */
#ifndef EESPRECISION
#endif
#ifndef EESREFID
#endif
#ifndef EESHSREFID
#endif
/* Description of clock */
#define EESDESCRIPTION "EES M201 MSF Receiver"
/* Speed we run the clock port at. If this is changed the UARTDELAY
* value should be recomputed to suit.
*/
#ifndef SPEED232
#endif
/* What is the inherent delay for this mode of working, i.e. when is the
* data time stamped.
*/
#ifndef STREAM_PP1
#define STREAM_PP1 "ppsclocd\0<-- patch space for module name1 -->"
#endif
#ifndef STREAM_PP2
#define STREAM_PP2 "ppsclock\0<-- patch space for module name2 -->"
#endif
/* Offsets of the bytes of the serial line code. The clock gives
* give offsets into ees->lastcode.
*/
#define EESM_CSEC 0 /* centiseconds - always zero in our clock */
/* followed by a frame alignment byte (0xff) /
/ which is not put into the lastcode buffer*/
/* Length of the serial time code, in characters. The first length
* is less the frame alignment byte.
*/
/* Code state. */
#define EESCS_WAIT 0 /* waiting for start of timecode */
/* Default fudge factor and character to receive */
#define DEFFUDGETIME 0 /* Default user supplied fudge factor */
#ifndef DEFOSTIME
#define DEFOSTIME 0 /* Default OS delay -- passed by Make ? */
#endif
/* Limits on things. Reduce the number of samples to SAMPLEREDUCE by median
* elimination. If we're running with an accurate clock, chose the BESTSAMPLE
* as the estimated offset, otherwise average the remainder.
*/
/* Towards the high ( Why ?) end of half */
/* Leap hold time. After a leap second the clock will no longer be
* reliable until it resynchronizes. Hope 40 minutes is enough. */
/* debug is a bit mask of debugging that is wanted */
#define DB_SYSLOG_SMPLI 0x0001
#define DB_SYSLOG_SMPLE 0x0002
#define DB_SYSLOG_SMTHI 0x0004
#define DB_SYSLOG_NSMTHE 0x0008
#define DB_SYSLOG_NSMTHI 0x0010
#define DB_SYSLOG_SMTHE 0x0020
#define DB_PRINT_EV 0x0040
#define DB_PRINT_CDT 0x0080
#define DB_PRINT_CDTC 0x0100
#define DB_SYSLOG_KEEPD 0x0800
#define DB_SYSLOG_KEEPE 0x1000
#define DB_LOG_DELTAS 0x2000
#define DB_PRINT_DELTAS 0x4000
#define DB_LOG_AWAITMORE 0x8000
#define DB_LOG_SAMPLES 0x10000
#define DB_NO_PPS 0x20000
#define DB_INC_PPS 0x40000
#define DB_DUMP_DELTAS 0x80000
struct eesunit { /* EES unit control structure. */
char tz; /* timezone from clock */
long last_pps_no; /* The serial # of the last PPS */
char fix_pending; /* Is a "sync to time" pending ? */
/* Fine tuning - compensate for 4 mS ramping .... */
int best_av_step; /* Best guess at average step */
char best_av_step_count; /* # of steps over used above */
char this_step; /* Current pos in buffer */
int last_step_late; /* How late the last step was (0-59) */
long jump_fsecs; /* # of fractions of a sec last jump */
int last_step_secs; /* Number of seconds in last step */
int using_ramp; /* 1 -> noemal, -1 -> over stepped */
};
/* Bitmask for what methods to try to use -- currently only PPS enabled */
#define T_CBREAK 1
#define T_PPS 8
/* macros to test above */
/* Data space for the unit structures. Note that we allocate these on
* the fly, but never give them back. */
/* Keep the fudge factors separately so they can be set even
* when no clock is configured. */
static int deltas[60];
/* Imported from the timer module */
extern u_long current_time;
extern s_char sys_precision;
#ifdef DEBUG
static int debug;
#endif
#ifndef DUMP_BUF_SIZE /* Size of buffer to be used by dump_buf */
#define DUMP_BUF_SIZE 10112
#endif
/* ees_reset - reset the count back to zero */
/* ees_event - record and report an event */
/* Find the precision of the system clock by reading it */
#define USECS 1000000
int from;
int to;
char *text;
{
int i;
}
}
/* msfees_init - initialize internal ees driver data */
static void msfees_init()
{
register int i;
/* Just zero the data arrays */
acceptable_slop.l_ui = 0;
/* Initialize fudge factors to default. */
for (i = 0; i < MAXUNITS; i++) {
fudgefactor[i].l_ui = 0;
inherent_delay[i].l_ui = 0;
offset_fudge[i] = os_delay[i];
stratumtouse[i] = 0;
sloppyclockflag[i] = 0;
}
}
/* msfees_start - open the EES devices and initialize data for processing */
int unit;
{
register int i;
int fd232 = -1;
char eesdev[20];
static void ees_receive();
extern int io_addclock();
extern void io_closeclock();
extern char *emalloc();
struct refclockproc *pp;
return 0;
}
return 0;
}
/* Unit okay, attempt to open the devices. We do them both at
* once to make sure we can */
if (fd232 == -1) {
return 0;
}
#ifdef TIOCEXCL
/* Set for exclusive use */
goto screwed;
}
#endif
/* STRIPPED DOWN VERSION: Only PPS CD is supported at the moment */
/* Set port characteristics. If we don't have a STREAMS module or
* a clock line discipline, cooked mode is just usable, even though it
* strips the top bit. The only EES byte which uses the top
* bit is the year, and we don't use that anyway. If we do
* have the line discipline, we choose raw mode, and the
* line discipline code will block up the messages.
*/
/* STIPPED DOWN VERSION: Only PPS CD is supported at the moment */
goto screwed;
}
goto screwed;
}
goto screwed;
}
/* offset fudge (how *late* the timestamp is) = fudge + os delays */
/* Looks like this might succeed. Find memory for the structure.
* Look to see if there are any unused ones, if not we malloc() one.
*/
else {
/* Look for an unused, but allocated struct */
for (i = 0; i < MAXUNITS; i++) {
break;
}
if (i < MAXUNITS) { /* Reclaim this one */
eesunits[i] = 0;
} /* no spare -- make a new one */
}
/* Set up the structures */
/* Okay. Push one of the two (linked into the kernel, or dynamically
* loaded) STREAMS module, and give it to the I/O code to start
* receiving stuff.
*/
{
int rc1;
/* Pop any existing onews first ... */
/* Now try pushing either of the possible modules */
"ees clock: Push of `%s' and `%s' to %s failed %m",
goto screwed;
}
else {
}
}
/* Add the clock */
/* Oh shit. Just close and return. */
goto screwed;
}
/* All done. Initialize a few random peer variables, then
* return success. */
} else {
}
return (1);
if (fd232 != -1)
return (0);
}
/* msfees_shutdown - shut down a EES clock */
int unit;
{
extern void io_closeclock();
"ees clock: INTERNAL ERROR, unit number %d invalid (max %d)",
return;
}
"ees clock: INTERNAL ERROR, unit number %d not in use", unit);
return;
}
/* Tell the I/O module to turn us off. We're history. */
}
/* ees_report_event - note the occurance of an event */
int code;
{
if (code != CEVNT_NOMINAL)
/* Should report event to trap handler in here.
* Soon...
*/
}
}
/* ees_receive - receive data from the serial interface on an EES clock */
static void ees_receive(rbufp)
{
register int n_sample;
register int day;
register char *cp;
static void ees_process();
int call_pps_sample = 0;
int sincelast;
int pps_step = 0;
int suspect_4ms_step = 0;
struct ppsclockev ppsclockev;
long *ptr = (long *) &ppsclockev;
extern errno;
int rc;
/* Get the clock this applies to and a pointer to the data */
/* Check out our state and process appropriately */
case EESCS_WAIT:
/* Set an initial guess at the timestamp as the recv time.
* If just running in CBREAK mode, we can't improve this.
* If we have the CLOCK Line Discipline, PPSCD, or sime such,
* then we will do better later ....
*/
/*FALLSTHROUGH*/
case EESCS_GOTSOME:
/* Gobble the bytes until the final (possibly stripped) 0xff */
/* Oh dear -- too many bytes .. */
"I: ees clock: %d + %d > %d [%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x]",
D(0), D(1), D(2), D(3), D(4), D(5), D(6),
D(7), D(8), D(9), D(10), D(11), D(12));
#undef D
return;
}
}
/* Gave up because it was end of the buffer, rather than ff */
/* Incomplete. Wait for more. */
if (debug & DB_LOG_AWAITMORE)
"I: ees clock %d: %d == %d: await more",
return;
}
/* This shouldn't happen ... ! */
return;
}
/* Skip the 0xff */
dpt++;
/* Finally, got a complete buffer. Mainline code will
* continue on. */
break;
default:
return;
}
/* Boy! After all that crap, the lastcode buffer now contains
* something we hope will be a valid time code. Do length
* checks and sanity checks on constant data.
*/
return;
}
/* Check that centisecond is zero */
return;
}
/* Check flag formats */
return;
}
return;
}
return;
}
/* So far, so good. Compute day, hours, minutes, seconds,
* time zone. Do range checks on these.
*/
#define istrue(x) ((x)?1:0)
/* Add in lengths of all previous months. Add one more
if it is a leap year and after February.
*/
case 1: break;
return;
}
/* Get timezone. The clocktime routine wants the number
* of hours to add to the delivered time to get UT.
* Currently -1 if BST flag set, 0 otherwise. This
* is the place to tweak things if double summer time
* ever happens.
*/
return;
}
/* Now, compute the reference time value: text -> tmp.l_ui */
return;
}
/* DON'T use ees->arrvtime -- it may be < reftime */
/* If we are synchronised to the radio, update the reference time.
* Also keep a note of when clock was last good.
*/
}
/* Compute the offset. For the fractional part of the
* offset we use the expected delay for the message.
*/
/* Number of seconds since the last step */
"[%x] CIOGETEV u%d %d (%lx %d) gave %d (%d): %08lx %08lx %ld\n",
/* If we managed to get the time of arrival, process the info */
if (rc >= 0) {
int conv = -1;
/* Possible that PPS triggered, but text message didn't */
/* allow for single loss of PPS only */
else { /* if ((ABS(time difference) - 0.25) < 0)
* then believe it ...
*/
conv = 0;
if (debug & DB_PRINT_CDT)
printf("[%x] Have %lx.%08lx and %lx.%08lx -> %lx.%08lx @ %s",
conv++;
}
/* Some loss of some signals around sec = 1 */
suspect_4ms_step |= 2;
conv++;
}
}
}
if (debug & DB_PRINT_CDTC)
"[%x] %08lx %08lx %d u%d (%d %d)\n",
}
/* See if there has been a 4ms jump at a minute boundry */
long delta_f_abs;
/* Dump the deltas each minute */
if (debug & DB_DUMP_DELTAS)
{ if (/*0 <= ees->second && */
/* Dump on second 1, as second 0 sometimes missed */
int i;
}
text+1);
}
}
/* Lets see if we have a 4 mS step at a minute boundaary */
) { /* 4ms jump at min boundry */
int old_sincelast;
int count=0;
int sum = 0;
/* Yes -- so compute the ramp time */
/* First time in, just set "ees->last_step" */
int other_step = 0;
int third_step = 0;
int p;
p= p_step;
p_step++;
/* Find the "average" interval */
while (p != p_step) {
if (this == 0) break;
if (other_step == 0 && (
other_step = this;
if (other_step != this) {
if (third_step == 0 && (
(delta == 1) ? (
:
(
)
)) third_step = this;
if (third_step != this) break;
}
}
p--;
if (p < 0) p += LAST_STEPS;
count++;
}
msyslog(LOG_ERR, "MSF%d: %d: This=%d (%d), other=%d/%d, sum=%d, count=%d, pps_step=%d, suspect=%x", ees->unit, p, ees->last_steps[p], this_step, other_step, third_step, sum, count, pps_step, suspect_4ms_step);
printf("MSF%d: steps %d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
if (sincelast > 170)
sincelast = 0;
}
else { /* First time in -- just save info */
}
printf("MSF%d: d=%3ld.%04ld@%d :%d:%d:$%d:%d:%d\n",
}
/* OK, so not a 4ms step at a minute boundry */
else {
"MSF%d: suspect = %x, but delta of %d.%04d [%d.%04d<%d.%04d<%d.%04d: %d %d]",
static ees_step_notes = EES_STEP_NOTES;
if (ees_step_notes > 0) {
printf("MSF%d: D=%3ld.%04ld@%02d :%d%s\n",
ees->unit, msec(delta_sfsec), subms(delta_sfsec), ees->second, (ees->last_step) ? sincelast : -1, ees_step_notes ? "" : " -- NO MORE !");
}
}
}
}
/* IF we have found that it's ramping
* && it's within twice the expected ramp period
* && there is a non zero step size (avoid /0 !)
* THEN we twiddle things
*/
if (ees->using_ramp &&
long fsecs;
/* Ramp time may vary, so may ramp for longer than last time */
/* sec_of_ramp * ees->jump_fsecs may overflow 2**32 */
"[%x] MSF%d: %3d/%03d -> d=%11d (%d|%d)",
"MSF%d: %3ld/%03d -> d=%11ld (%ld|%ld)\n",
/* Must sign extend the result */
if (debug & DB_INC_PPS)
}
else
}
}
else {
"[%x] MSF%d: ees->using_ramp=%d, sincelast=%x / %x, ees->last_step_secs=%x",
"[%x] MSF%d: ees->using_ramp=%d, sincelast=%x / %x, ees->last_step_secs=%x\n",
}
/* Sigh -- it expects its args negated */
(void) pps_sample(&pps_arrvstamp);
}
/* Subtract off the local clock time stamp */
"MSF%d: [%x] %d (ees: %d %d) (pps: %d %d)%s",
/* Done! */
}
{
}
/* offcompare - auxiliary comparison routine for offset sort */
static int
offcompare(a, b)
l_fp *a, *b;
{
}
/* ees_process - process a pile of samples from the clock */
static void ees_process(ees)
{
static last_samples = -1;
register int i, j;
register int noff;
int samplelog = 0; /* keep "gcc -Wall" happy ! */
/* Reset things to zero so we don't have to worry later */
}
if (samples != last_samples &&
}
if (samples < 1) return;
/* If requested, dump the raw data we have in the buffer */
/* Sort the offsets, trim off the extremes, then choose one. */
i = 0;
while ((noff - i) > samplereduce) {
/* Trim off the sample which is further away
* from the median. We work this out by doubling
* the median, subtracting off the end samples, and
* looking at the sign of the answer, using the
* identity (c-b)-(b-a) == 2*b-a-c
*/
}
/* If requested, dump the reduce data we have in the buffer */
/* What we do next depends on the setting of the sloppy clock flag.
* If it is on, average the remainder to derive our estimate.
* Otherwise, just pick a representative value from the remaining stuff
*/
for (j = i; j < noff; j++)
for (j = samplelog; j > 0; j--)
}
/* Compute the dispersion as the difference between the
* lowest and highest offsets that remain in the
* consideration list.
*
* It looks like MOST clocks have MOD (max error), so halve it !
*/
"I: [%x] Offset=%06d (%d), disp=%06d%s [%d], %d %d=%d %d:%d %d=%d %d",
i,
noff-1,
/* Are we playing silly wotsits ?
* If we are using all data, see if there is a "small" delta,
* and if so, blurr this with 3/4 of the delta from the last value
*/
/* is the delta small enough ? */
long new;
/* Sign change -> need to fix up int part */
}
dispersion /= 4;
"I: [%x] Smooth data: %d -> %d, dispersion now %d",
}
"[%x] No smooth as delta not %d < %d < %d",
}
"I: [%x] No smooth as flag=%x and old=%x=%d (%d:%d)",
/* Collect offset info for debugging info */
/* Determine synchronization status. Can be unsync'd either
* by a report from the clock or by a leap hold.
*
* Loss of the radio signal for a short time does not cause
* us to go unsynchronised, since the receiver keeps quite
* good time on its own. The spec says 20ms in 4 hours; the
* observed drift in our clock (Cambridge) is about a second
* a day, but even that keeps us within the inherent tolerance
* of the clock for about 15 minutes. Observation shows that
* the typical "short" outage is 3 minutes, so to allow us
* to ride out those, we will give it 5 minutes.
*/
/* Done. Use time of last good, synchronised code as the
* reference time, and lastsampletime as the receive time.
*/
if (ees->fix_pending) {
ees->fix_pending = 0;
}
&offset,
0, /* delay */
(isinsync) ? 0 : LEAP_NOTINSYNC);
}
/* msfees_poll - called by the transmit procedure */
int unit;
char *peer;
{
unit);
return;
}
unit);
return;
}
}
#if 0 /* Apparently unused... */
/* msfees_leap - called when a leap second occurs */
static void msfees_leap()
{
register int i;
/* This routine should be entered a few seconds after
* midnight UTC when a leap second occurs. To ensure we
* don't believe foolish time from the clock(s) we set a
* 40 minute hold on them. It shouldn't take anywhere
* near this amount of time to adjust if the clock is getTING
* data, but doing anything else is complicated.
*/
}
#endif
/* msfees_control - set fudge factors, return statistics */
int unit;
struct refclockstat *in;
struct refclockstat *out;
{
return;
}
if (in != 0) {
/* Should actually reselect clock, but
* will wait for the next timecode
*/
EESREFID, 4);
'0' + unit;
}
}
}
}
}
ees->fix_pending++;
/* if (in->flags & CLK_FLAG2 && unitinuse[unit])
ees->leaphold = 0; */
}
}
}
}
if (out != 0) {
/*out->fudgeval2= debug*/;
} else {
}
}
}
/* msfees_buginfo - return clock dependent debugging info */
int unit;
register struct refclockbug *bug;
{
return;
}
return;
}
struct refclock refclock_msfees = {
};
#else /* not defined(REFCLOCK) && defined(MSFEES) && defined(PPS) */
int refclock_msfees_bs;
#endif /* not defined(REFCLOCK) && defined(MSFEES) && defined(PPS) */