Replied: Mon, 07 Jul 1997 20:24:24 -0400
Replied: "Damon at work <dhd@exnet.com> "
Replied: Mon, 07 Jul 1997 18:32:41 -0400
Replied: "Damon at work <dhd@exnet.com> mills@udel.edu"
Return-Path: dhd@exnet.com 
Return-Path: <dhd@exnet.com>
Received: from mailgate.exnet.com ([204.137.193.226])
	by whimsy.udel.edu (8.8.5/8.8.5) with SMTP id JAA08336
	for <stenn@whimsy.udel.edu>; Mon, 7 Jul 1997 09:44:50 GMT
Received: from exnet.com (assam.exnet.com) by mailgate.exnet.com with SMTP id AA03149
	(5.67a/IDA-1.4.4 for stenn@whimsy.udel.edu); Mon, 7 Jul 1997 10:44:12 +0100
Received: from maildrop.exnet.com (oolong.exnet.com) by exnet.com with SMTP id AA10527
	(5.67a/IDA-1.4.4); Mon, 7 Jul 1997 10:43:46 +0100
From: Damon at work <dhd@exnet.com>
Received: by maildrop.exnet.com (SMI-8.6/client-1.4DHD)
	id KAA16049; Mon, 7 Jul 1997 10:43:43 +0100
Date: Mon, 7 Jul 1997 10:43:43 +0100
Message-Id: <199707070943.KAA16049@maildrop.exnet.com>
To: mills@udel.edu, stenn@whimsy.udel.edu
Subject: Updates to ARCRON clock driver.
Cc: dhd@exnet.com
Content-Type: X-sun-attachment

----------
X-Sun-Data-Type: text
X-Sun-Data-Description: text
X-Sun-Data-Name: text
X-Sun-Charset: us-ascii
X-Sun-Content-Lines: 57

Hi folks,

Some updates to the driver:

 1) About half the memory footprint (most of my debugging code
    disabled by default).

 2) Some general tweaks and improvements, including an attempt
    to increase sampling frequency at the point of a potential
    leap-second action (though this did not seem to work on 30th June,
    so it is disabled for now).

 3) The code avoids the clock tty becoming the controlling tty.

Diffs from the 5.90.2 code are supplied.

Harlan, I believe I have preserved your changes for older compilers.

BTW, we don't seemed to have turned up in the copyright document yet.
And I was staking my reputation on it...

Regards,

Damon

BTW, are you aware that (at least under 3.50[.0]):

 a) The current PARSE install/build stuff is way out of sync with
    your new build structure (it still talks about the 3.3/4.3 style
    build flags).

 b) Same with the kernel/ stuff.

 c) They ask you to build stuff into the kernel, and won't build the
    PARSE stuff unless you have copied stuff into the sys area, but
    that is unnecessary (and therefore a BadThing(TM) IMHO) under
    Solaris-2.

 d) The PARSE module for Solaris-2 seems to blow the kernel (2.5/2.5.1)
    up immediately (and at least one other poster on c.p.t.n had the
    same experience).

 e) I think ppsclock paniced my kernel too, but I was getting confused
    at that stage and gave up, since I did not want to modify my sys
    area unnecessarily, and got fed up with hacking the config stuff.

I only report some of these to you rather than Frank, since some of the
pertinent docs have Dave's name on rather than Frank's.

I have had much more success in the past with PARSE and indeed have
written STREAMS modules under SunOS, so I'm sure I can fix all this
when I get a spare month...  B^>

Would you like me to mail Frank directly with this, or would one of you
folks like to pass this on to him?

Damon
----------
X-Sun-Data-Type: default
X-Sun-Data-Description: default
X-Sun-Data-Name: driver27_DIFF
X-Sun-Charset: us-ascii
X-Sun-Content-Lines: 21

24,25c24,25
< <p>This documentation describes version V1.1 (1997/06/23) of the source
< and has been tested (amongst others) against xntpd3-5.90 on Solaris-1 (SunOS 4.1.3_U1 on
---
> <p>This documentation describes version V1.0 (1997/05/21) of the source
> and has been tested against xntpd3-5.90 on Solaris-1 (SunOS 4.1.3_U1 on
33,35d32
< <p>This code has been significantly slimmed down since the V1.0 version,
< roughly halving the memory footprint of its code and data.
< 
323,332d319
< <dt><code>ARCRON_LEAPSECOND_KEEN</code></dt>
< <dd>When is defined, we try to do a resync to MSF as soon as possible
< in the first hour of the morning of the first day of the first and
< seventh months, ie just after a leap-second insertion or deletion would
< happen if it is going to.  This should help compensate for the fact
< that this clock does not continuously sample MSF, which compounds the
< fact that MSF itself gives no warning of an impending leap-second
< event.  This code did not seem functional at the leap-second insertion
< of 30th June 1997 so is by default disabled.
< 
----------
X-Sun-Data-Type: default
X-Sun-Data-Description: default
X-Sun-Data-Name: refclock_arc_DIFF
X-Sun-Charset: us-ascii
X-Sun-Content-Lines: 358

10d9
<     static const char arc_version[] = { "V1.1 1997/06/23" };
12c11
< #undef ARCRON_DEBUG /* Define only while in development... */
---
>     static const char arc_version[] = { "V1.0 1997/05/21" };
19,20c18
< #ifndef ARCRON_OWN_FILTER
< #undef ARCRON_OWN_FILTER /* Use own median filter only for versions before 3-5.90.1. */
---
> #define ARCRON_OWN_FILTER 1 /* Use own median filter to get round 3-5.90 bug. */
22d19
< #endif
28,33d24
< #ifndef ARCRON_NOT_LEAPSECOND_KEEN
< #ifndef ARCRON_LEAPSECOND_KEEN
< #undef ARCRON_LEAPSECOND_KEEN /* Respond quickly to leap seconds: doesn't work yet. */
< #endif
< #endif
< 
78,81d68
< MAJOR CHANGES SINCE V1.0
< ========================
<  1) Removal of pollcnt variable that made the clock go permanently
<     off-line once two time polls failed to gain responses.
83,92d69
<  2) Avoiding (at least on Solaris-2) terminal becoming the controlling
<     terminal of the process when we do a low-level open().
< 
<  3) Additional logic (conditional on ARCRON_LEAPSECOND_KEEN being
<     defined) to try to resync quickly after a potential leap-second
<     insertion or deletion.
< 
<  4) Code significantly slimmer at run-time than V1.0.
< 
< 
217a195,197
>   * Avoid /dev/arcX becoming our controlling terminal.  Do we really
>     want a ^C or somesuch on the tty line aborting xntpd (would it)?
> 
227c207
<     correction would be disabled by default).
---
>     correction would be disabled by defaulr).
442,447d421
< /* Flags for a raw open() of the clock serial device. */
< #ifdef O_NOCTTY /* Good, we can avoid tty becoming controlling tty. */
< #define OPEN_FLAGS (O_RDWR | O_NOCTTY)
< #else           /* Oh well, it may not matter... */
< #define OPEN_FLAGS (O_RDWR)
< #endif
449d422
< 
470a444
>     int pollcnt;        /* Poll message counter. */
487,489d460
< #ifdef ARCRON_LEAPSECOND_KEEN
<     /* The flag `possible_leap' is set non-zero when any MSF unit
<        thinks a leap-second may have happened.
491,511d461
<        Set whenever we receive a valid time sample in the first hour of
<        the first day of the first/seventh months.
< 
<        Outside the special hour this value is unconditionally set
<        to zero by the receive routine.
< 
<        On finding itself in this timeslot, as long as the value is
<        non-negative, the receive routine sets it to a positive value to
<        indicate a resync to MSF should be performed.
< 
<        In the poll routine, if this value is positive and we are not
<        already resyncing (eg from a sync that started just before
<        midnight), start resyncing and set this value negative to
<        indicate that a leap-triggered resync has been started.  Having
<        set this negative prevents the receive routine setting it
<        positive and thus prevents multiple resyncs during the witching
<        hour.
<      */
<     static int possible_leap = 0;       /* No resync required by default. */
< #endif
< 
553d502
< #ifdef ARCRON_DEBUG
555d503
< #endif
576c524
< #ifdef ARCRON_DEBUG
---
> 
578d525
< #endif
586,592c533,534
<         if(c != '\0') {
<             if(write(pp->io.fd, &c, 1) != 1) {
<                 syslog(LOG_NOTICE, "ARCRON: write to fd %d failed", pp->io.fd);
<             }
< #ifdef ARCRON_DEBUG
<         else if(debug) { printf("arc: sent `%2.2x', fd %d.\n", c, pp->io.fd); }
< #endif
---
>         if((c != '\0') && (write(pp->io.fd, &c, 1) != 1)) {
>             syslog(LOG_NOTICE, "ARCRON: could not write to fd %d", pp->io.fd);
593a536,539
> 
>         if(debug && (c != '\0'))
>             { printf("arc: sending `%2.2x' on fd %d.\n", c, pp->io.fd); }
> 
608a555
>         int i;
614d560
< #ifdef ARCRON_DEBUG
618d563
< #endif
628d572
< #ifdef ARCRON_DEBUG
630d573
< #endif
632,634c575
< #ifdef DEBUG
<             if(debug) { printf("arc: failed [TTYCLK] to open %s.\n", device); }
< #endif
---
>             if(debug) { printf("Failed [TTYCLK] to open %s.\n", device); }
638d578
< #ifdef ARCRON_DEBUG
640,641c580
< #endif
<         fd = open(device, OPEN_FLAGS);
---
>         fd = open(device, O_RDWR);
643,645c582
< #ifdef DEBUG
<             if(debug) { printf("arc: failed [open()] to open %s.\n", device); }
< #endif
---
>             if(debug) { printf("Failed [open()] to open %s.\n", device); }
650c587
< #ifdef ARCRON_DEBUG
---
> 
652,653c589
<             { printf("Opened RS232 port with file descriptor %d.\n", fd); }
< #endif
---
>             { printf("Opening RS232 port with file descriptor %d.\n", fd); }
678d613
<         /* Set structure to all zeros... */
695,696d629
<         /* Spread out resyncs so that they should remain separated. */
<         up->next_resync = current_time + INITIAL_RESYNC_DELAY + (67*unit)%1009;
698,700c631
< #if 0 /* Not needed because of zeroing of arcunit structure... */
<         up->resyncing = 0;              /* Not resyncing yet. */
<         up->saved_flags = 0;            /* Default is all flags off. */
---
>         up->pollcnt = 2;
702,703d632
<         {
<         int i;
705,707d633
<         }
< #endif
< 
715a642
>         up->ev.next = up->ev.prev = 0;  /* Be ultra-cautious... */
716a644,648
> 
>         /* Spread out resyncs so that they should remain separated. */
>         up->next_resync = current_time + INITIAL_RESYNC_DELAY + (67*unit)%1009;
>         up->resyncing = 0;              /* Not resyncing yet. */
>         up->saved_flags = 0;            /* Default is all flags off. */
767c699
< #ifdef ARCRON_DEBUG
---
> 
769,771c701,702
< #endif
<     if(spaceleft < sl) { /* Should not normally happen... */
< #ifdef ARCRON_DEBUG
---
> 
>     if(spaceleft < sl) {
774d704
< #endif
825d754
< #ifdef DEBUG
827d755
< #endif
870a799
> 
872c801
< #ifdef DEBUG
---
> 
877d805
< #endif
890d817
< #ifdef ARCRON_DEBUG
895d821
< #endif
902c828
< #ifdef ARCRON_DEBUG
---
> 
913d838
< #endif
931d855
< #ifdef ARCRON_DEBUG
936a861
> 
944d868
< #endif
955d878
< #ifdef ARCRON_DEBUG
958d880
< #endif
980d901
< #ifdef ARCRON_DEBUG
982d902
< #endif
995d914
< #ifdef ARCRON_DEBUG
997d915
< #endif
1002d919
< #ifdef DEBUG
1004d920
< #endif
1006d921
< #ifdef DEBUG
1013d927
< #endif
1043d956
< #ifdef ARCRON_DEBUG
1045d957
< #endif
1049d960
< #ifdef ARCRON_DEBUG
1051d961
< #endif
1055a966,969
> 
>         /* Give us some leeway before deciding that the clock is ill... */
>         up->pollcnt = 2;
> 
1077,1078d990
< #ifdef ARCRON_DEBUG
<             /* Would expect to have caught major problems already... */
1080d991
< #endif
1121c1032
< #ifdef DEBUG
---
> 
1128d1038
< #endif
1146d1055
<                 up->status = status;
1182d1090
< #ifdef ARCRON_DEBUG
1187a1096,1097
>             up->saved_flags = pp->sloppyclockflag;
> 
1194,1195d1103
< #endif
<             up->saved_flags = pp->sloppyclockflag;
1201,1217d1108
< #ifdef ARCRON_LEAPSECOND_KEEN
<         /* Find out if a leap-second might just have happened...
<            (ie is this the first hour of the first day of Jan or Jul?)
<          */
<         if((pp->hour == 0) &&
<            (pp->day == 1) &&
<            ((month == 1) || (month == 7))) {
<             if(possible_leap >= 0) {
<                 /* A leap may have happened, and no resync has started yet...*/
<                 possible_leap = 1;
<                 }
<         } else {
<             /* Definitely not leap-second territory... */
<             possible_leap = 0;
<         }
< #endif
< 
1231,1235c1122
<             {
<             refclock_report(peer, CEVNT_BADTIME);
<             if(debug) { printf("arc: sample rejected.\n"); }
<             return;
<             }
---
>             { refclock_report(peer, CEVNT_BADTIME); return; }
1253d1139
< #ifdef DEBUG
1255d1140
< #endif
1257d1141
< #ifdef ARCRON_DEBUG
1259d1142
< #endif
1276d1158
<         int resync_needed;              /* Should we start a resync? */
1289,1318c1171
<         resync_needed = (up->next_resync <= current_time);
< 
< #ifdef ARCRON_LEAPSECOND_KEEN
<         /*
<          Try to catch a potential leap-second insertion or deletion quickly.
< 
<          In addition to the normal NTP fun of clocks that don't report
<          leap-seconds spooking their hosts, this clock does not even
<          sample the radio sugnal the whole time, so may miss a
<          leap-second insertion or deletion for up to a whole sample
<          time.
< 
<          To try to minimise this effect, if in the first few minutes of
<          the day immediately following a leap-second-insertion point
<          (ie in the first hour of the first day of the first and sixth
<          months), and if the last resync was in the previous day, and a
<          resync is not already in progress, resync the clock
<          immediately.
< 
<          */
<         if((possible_leap > 0) &&       /* Must be 00:XX 01/0{1,7}/XXXX. */
<            (!up->resyncing)) {          /* No resync in progress yet. */
<            resync_needed = 1;
<            possible_leap = -1;          /* Prevent multiple resyncs. */
<            syslog(LOG_NOTICE,"ARCRON: unit %d: checking for leap second",unit);
<            }
< #endif
< 
<         /* Do a resync if required... */
<         if(resync_needed) {
---
>         if(up->next_resync <= current_time) {
1328d1180
< #ifdef DEBUG
1330d1181
< #endif
1352d1202
< #ifdef DEBUG
1356d1205
< #endif
1359a1209,1216
> 
>         if(debug) { printf("arc: *** poll: pollcnt=%d\n", up->pollcnt); }
>         if(up->pollcnt == 0) {
>                 refclock_report(peer, CEVNT_TIMEOUT);
>                 return;
>         }
>         up->pollcnt--;
> 
1430c1287
< #ifdef ARCRON_DEBUG
---
> 
1435d1291
< #endif
----------
X-Sun-Data-Type: c-file
X-Sun-Data-Description: c-file
X-Sun-Data-Name: refclock_arc.c
X-Sun-Charset: us-ascii
X-Sun-Content-Lines: 1536

/*
 * refclock_arc - clock driver for ARCRON MSF receivers
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(ARCRON_MSF)
    static const char arc_version[] = { "V1.1 1997/06/23" };

#undef ARCRON_DEBUG /* Define only while in development... */

#ifndef ARCRON_NOT_KEEN
#define ARCRON_KEEN 1 /* Be keen, and trusting of the clock, if defined. */
#endif

#ifndef ARCRON_NOT_OWN_FILTER
#ifndef ARCRON_OWN_FILTER
#undef ARCRON_OWN_FILTER /* Use own median filter only for versions before 3-5.90.1. */
#endif
#endif

#ifndef ARCRON_NOT_MULTIPLE_SAMPLES
#define ARCRON_MULTIPLE_SAMPLES 1 /* Use all timestamp bytes as samples. */
#endif

#ifndef ARCRON_NOT_LEAPSECOND_KEEN
#ifndef ARCRON_LEAPSECOND_KEEN
#undef ARCRON_LEAPSECOND_KEEN /* Respond quickly to leap seconds: doesn't work yet. */
#endif
#endif

/*
Code by Derek Mulcahy, <derek@toybox.demon.co.uk>, 1997.
Modifications by Damon Hart-Davis, <d@hd.org>, 1997.

THIS CODE IS SUPPLIED AS IS, WITH NO WARRANTY OF ANY KIND.  USE AT
YOUR OWN RISK.

Orginally developed and used with xntp3-5.85 by Derek Mulcahy.

Built against xntp3-5.90 on Solaris 2.5 using gcc 2.7.2.

This code may be freely copied and used and incorporated in other
systems providing the disclaimer and notice of authorship are
reproduced.

-------------------------------------------------------------------------------

Author's original note:

I enclose my xntp driver for the Galleon Systems Arc MSF receiver.

It works (after a fashion) on both Solaris-1 and Solaris-2.

I am currently using xntp3-5.85.  I have been running the code for
about 7 months without any problems.  Even coped with the change to BST!

I had to do some funky things to read from the clock because it uses the
power from the receive lines to drive the transmit lines.  This makes the
code look a bit stupid but it works.  I also had to put in some delays to
allow for the turnaround time from receive to transmit.  These delays
are between characters when requesting a time stamp so that shouldn't affect
the results too drastically.

...

The bottom line is that it works but could easily be improved.  You are
free to do what you will with the code.  I haven't been able to determine
how good the clock is.  I think that this requires a known good clock
to compare it against.

-------------------------------------------------------------------------------

Damon's notes for adjustments:

MAJOR CHANGES SINCE V1.0
========================
 1) Removal of pollcnt variable that made the clock go permanently
    off-line once two time polls failed to gain responses.

 2) Avoiding (at least on Solaris-2) terminal becoming the controlling
    terminal of the process when we do a low-level open().

 3) Additional logic (conditional on ARCRON_LEAPSECOND_KEEN being
    defined) to try to resync quickly after a potential leap-second
    insertion or deletion.

 4) Code significantly slimmer at run-time than V1.0.


GENERAL
=======

 1) The C preprocessor symbol to have the clock built has been changed
    from ARC to ARCRON_MSF to minimise the possiblity of clashes with
    other symbols in the future.

 2) PRECISION should be -4/-5 (63ms/31ms) for the following reasons:

     a) The ARC documentation claims the internal clock is (only)
        accurate to about 20ms relative to Rugby (plus there must be
        noticable drift and delay in the ms range due to transmission
        delays and changing atmospheric effects).  This clock is not
        designed for ms accuracy as NTP has spoilt us all to expect.

     b) The clock oscillator looks like a simple uncompensated quartz
        crystal of the sort used in digital watches (ie 32768Hz) which
        can have large temperature coefficients and drifts; it is not
        clear if this oscillator is properly disciplined to the MSF
        transmission, but as the default is to resync only once per
        *day*, we can imagine that it is not, and is free-running.  We
        can minimise drift by resyncing more often (at the cost of
        reduced battery life), but drift/wander may still be
        significant.

     c) Note that the bit time of 3.3ms adds to the potential error in
        the the clock timestamp, since the bit clock of the serial link
        may effectively be free-running with respect to the host clock
        and the MSF clock.  Actually, the error is probably 1/16th of
        the above, since the input data is probably sampled at at least
        16x the bit rate.

    By keeping the clock marked as not very precise, it will have a
    fairly large dispersion, and thus will tend to be used as a
    `backup' time source and sanity checker, which this clock is
    probably ideal for.  For an isolated network without other time
    sources, this clock can probably be expected to provide *much*
    better than 1s accuracy, which will be fine.

    By default, PRECISION is set to -4, but experience, especially at a
    particular geographic location with a particular clock, may allow
    this to be altered to -5.  (Note that skews of +/- 10ms are to be
    expected from the clock from time-to-time.)  This improvement of
    reported precision can be instigated by setting flag3 to 1, though
    the PRECISION will revert to the normal value while the clock
    signal quality is unknown whatever the flag3 setting.

    IN ANY CASE, BE SURE TO SET AN APPROPRIATE FUDGE FACTOR TO REMOVE
    ANY RESIDUAL SKEW, eg:

        server 127.127.27.0 # ARCRON MSF radio clock unit 0.
        # Fudge timestamps by about 20ms.
        fudge 127.127.27.0 time1 0.020

    You will need to observe your system's behaviour, assuming you have
    some other NTP source to compare it with, to work out what the
    fudge factor should be.  For my Sun SS1 running SunOS 4.1.3_U1 with
    my MSF clock with my distance from the MSF transmitter, +20ms
    seemed about right, after some observation.

 3) REFID has been made "MSFa" to reflect the MSF time source and the
    ARCRON receiver.

 4) DEFAULT_RESYNC_TIME is the time in seconds (by default) before
    forcing a resync since the last attempt.  This is picked to give a
    little less than an hour between resyncs and to try to avoid
    clashing with any regular event at a regular time-past-the-hour
    which might cause systematic errors.

    The INITIAL_RESYNC_DELAY is to avoid bothering the clock and
    running down its batteries unnecesarily if xntpd is going to crash
    or be killed or reconfigured quickly.  If ARCRON_KEEN is defined
    then this period is long enough for (with normal polling rates)
    enough time samples to have been taken to allow xntpd to sync to
    the clock before the interruption for the clock to resync to MSF.
    This avoids xntpd syncing to another peer first and then
    almost immediately hopping to the MSF clock.

    The RETRY_RESYNC_TIME is used before rescheduling a resync after a
    resync failed to reveal a statisfatory signal quality (too low or
    unknown).

 5) The clock seems quite jittery, so I have increased the
    median-filter size from the typical (previous) value of 3.  I
    discard up to half the results in the filter.  It looks like maybe
    1 sample in 10 or so (maybe less) is a spike, so allow the median
    filter to discard at least 10% of its entries or 1 entry, whichever
    is greater.

 6) Sleeping *before* each character sent to the unit to allow required
    inter-character time but without introducting jitter and delay in
    handling the response if possible.

 7) If the flag ARCRON_KEEN is defined, take time samples whenever
    possible, even while resyncing, etc.  We rely, in this case, on the
    clock always giving us a reasonable time or else telling us in the
    status byte at the end of the timestamp that it failed to sync to
    MSF---thus we should never end up syncing to completely the wrong
    time.

 8) If the flag ARCRON_OWN_FILTER is defined, use own versions of
    refclock median-filter routines to get round small bug in 3-5.90
    code which does not return the median offset.

 9) We would appear to have a year-2000 problem with this clock since
    it returns only the two least-significant digits of the year.  But
    xntpd ignores the year and uses the local-system year instead, so
    this is in fact not a problem.  Nevertheless, we attempt to do a
    sensible thing with the dates, wrapping them into a 100-year
    window.

 10)Logs stats information that can be used by Derek's Tcl/Tk utility
    to show the status of the clock.

 11)The clock documentation insists that the number of bits per
    character to be sent to the clock, and sent by it, is 11, including
    one start bit and two stop bits.  The data format is either 7+even
    or 8+none.


TO-DO LIST
==========

  * Eliminate use of scanf(), and maybe sprintf().

  * Allow user setting of resync interval to trade battery life for
    accuracy; maybe could be done via fudge factor or unit number.

  * Possibly note the time since the last resync of the MSF clock to
    MSF as the age of the last reference timestamp, ie trust the
    clock's oscillator not very much...

  * Add very slow auto-adjustment up to a value of +/- time2 to correct
    for long-term errors in the clock value (time2 defaults to 0 so the
    correction would be disabled by default).

  * Consider trying to use the tty_clk/ppsclock support.

  * Possibly use average or maximum signal quality reported during
    resync, rather than just the last one, which may be atypical.

*/


/* Notes for HKW Elektronik GmBH Radio clock driver */
/* Author Lyndon David, Sentinet Ltd, Feb 1997      */
/* These notes seem also to apply usefully to the ARCRON clock. */

/* The HKW clock module is a radio receiver tuned into the Rugby */
/* MSF time signal tranmitted on 60 kHz. The clock module connects */
/* to the computer via a serial line and transmits the time encoded */
/* in 15 bytes at 300 baud 7 bits two stop bits even parity */

/* Clock communications, from the datasheet */
/* All characters sent to the clock are echoed back to the controlling */
/* device. */
/* Transmit time/date information */
/* syntax ASCII o<cr> */
/* Character o may be replaced if neccesary by a character whose code */
/* contains the lowest four bits f(hex) eg */
/* syntax binary: xxxx1111 00001101 */

/* DHD note:
You have to wait for character echo + 10ms before sending next character.
*/

/* The clock replies to this command with a sequence of 15 characters */
/* which contain the complete time and a final <cr> making 16 characters */
/* in total. */
/* The RC computer clock will not reply immediately to this command because */
/* the start bit edge of the first reply character marks the beginning of */
/* the second. So the RC Computer Clock will reply to this command at the */
/* start of the next second */
/* The characters have the following meaning */
/* 1. hours tens   */
/* 2. hours units  */
/* 3. minutes tens */
/* 4. minutes units */
/* 5. seconds tens  */
/* 6. seconds units */
/* 7. day of week 1-monday 7-sunday */
/* 8. day of month tens */
/* 9. day of month units */
/* 10. month tens */
/* 11. month units */
/* 12. year tens */
/* 13. year units */
/* 14. BST/UTC status */
/*      bit 7   parity */
/*      bit 6   always 0 */
/*      bit 5   always 1 */
/*      bit 4   always 1 */
/*      bit 3   always 0 */
/*      bit 2   =1 if UTC is in effect, complementary to the BST bit */
/*      bit 1   =1 if BST is in effect, according to the BST bit     */
/*      bit 0   BST/UTC change impending bit=1 in case of change impending */
/* 15. status */
/*      bit 7   parity */
/*      bit 6   always 0 */
/*      bit 5   always 1 */
/*      bit 4   always 1 */
/*      bit 3   =1 if low battery is detected */
/*      bit 2   =1 if the very last reception attempt failed and a valid */
/*              time information already exists (bit0=1) */
/*              =0 if the last reception attempt was successful */
/*      bit 1   =1 if at least one reception since 2:30 am was successful */
/*              =0 if no reception attempt since 2:30 am was successful */
/*      bit 0   =1 if the RC Computer Clock contains valid time information */
/*              This bit is zero after reset and one after the first */
/*              successful reception attempt */

/* DHD note:
Also note g<cr> command which confirms that a resync is in progress, and
if so what signal quality (0--5) is available.
Also note h<cr> command which starts a resync to MSF signal.
*/



#include <stdio.h>
#include <ctype.h>
#include <sys/time.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 */

#if defined(HAVE_TERMIOS)
#include <termios.h>
#endif

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"

/*
 * This driver supports the ARCRON MSF Radio Controlled Clock
 */

/*
 * Interface definitions
 */
#define DEVICE          "/dev/arc%d"    /* Device name and unit. */
#define SPEED           B300            /* UART speed (300 baud) */
#define PRECISION       (-4)            /* Precision  (~63 ms). */
#define HIGHPRECISION   (-5)            /* If things are going well... */
#define REFID           "MSFa"          /* Reference ID. */
#define DESCRIPTION     "ARCRON MSF Receiver"

#define NSAMPLES        4               /* Stages of median filter. */
#define NSAMPLESLONG    8               /* Stages of long filter. */

#define LENARC          16              /* Format `o' timecode length. */

#define BITSPERCHAR     11              /* Bits per character. */
#define BITTIME         0x0DA740E       /* Time for 1 bit at 300bps. */
#define CHARTIME10      0x8888888       /* Time for 10-bit char at 300bps. */
#define CHARTIME11      0x962FC96       /* Time for 11-bit char at 300bps. */
#define CHARTIME                        /* Time for char at 300bps. */ \
    ( (BITSPERCHAR == 11) ? CHARTIME11 : ( (BITSPERCHAR == 10) ? CHARTIME10 : \
            (BITSPERCHAR * BITTIME) ) )

/* Allow for UART to accept char half-way through final stop bit. */
#define INITIALOFFSET (-BITTIME/2)

    /*
    charoffsets[x] is the time after the start of the second that byte
    x (with the first byte being byte 1) is received by the UART,
    assuming that the initial edge of the start bit of the first byte
    is on-time.  The values are represented as the fractional part of
    an l_fp.

    We store enough values to have the offset of each byte including
    the trailing \r, on the assumption that the bytes follow one
    another without gaps.
    */
    static const u_int32 charoffsets[LENARC+1] = {
#if BITSPERCHAR == 11 /* Usual case. */
    /* Offsets computed as accurately as possible... */
    0,
    INITIALOFFSET + 0x0962fc96, /*  1 chars,  11 bits */
    INITIALOFFSET + 0x12c5f92c, /*  2 chars,  22 bits */
    INITIALOFFSET + 0x1c28f5c3, /*  3 chars,  33 bits */
    INITIALOFFSET + 0x258bf259, /*  4 chars,  44 bits */
    INITIALOFFSET + 0x2eeeeeef, /*  5 chars,  55 bits */
    INITIALOFFSET + 0x3851eb85, /*  6 chars,  66 bits */
    INITIALOFFSET + 0x41b4e81b, /*  7 chars,  77 bits */
    INITIALOFFSET + 0x4b17e4b1, /*  8 chars,  88 bits */
    INITIALOFFSET + 0x547ae148, /*  9 chars,  99 bits */
    INITIALOFFSET + 0x5dddddde, /* 10 chars, 110 bits */
    INITIALOFFSET + 0x6740da74, /* 11 chars, 121 bits */
    INITIALOFFSET + 0x70a3d70a, /* 12 chars, 132 bits */
    INITIALOFFSET + 0x7a06d3a0, /* 13 chars, 143 bits */
    INITIALOFFSET + 0x8369d037, /* 14 chars, 154 bits */
    INITIALOFFSET + 0x8ccccccd, /* 15 chars, 165 bits */
    INITIALOFFSET + 0x962fc963  /* 16 chars, 176 bits */
#else
    /* Offsets computed with a small rounding error... */
    0,
    INITIALOFFSET +  1 * CHARTIME,
    INITIALOFFSET +  2 * CHARTIME,
    INITIALOFFSET +  3 * CHARTIME,
    INITIALOFFSET +  4 * CHARTIME,
    INITIALOFFSET +  5 * CHARTIME,
    INITIALOFFSET +  6 * CHARTIME,
    INITIALOFFSET +  7 * CHARTIME,
    INITIALOFFSET +  8 * CHARTIME,
    INITIALOFFSET +  9 * CHARTIME,
    INITIALOFFSET + 10 * CHARTIME,
    INITIALOFFSET + 11 * CHARTIME,
    INITIALOFFSET + 12 * CHARTIME,
    INITIALOFFSET + 13 * CHARTIME,
    INITIALOFFSET + 14 * CHARTIME,
    INITIALOFFSET + 15 * CHARTIME,
    INITIALOFFSET + 16 * CHARTIME
#endif
    };

/* Chose filter length dependent on fudge flag 4. */
#define CHOSENSAMPLES(pp) \
    (((pp)->sloppyclockflag & CLK_FLAG4) ? NSAMPLESLONG : NSAMPLES)
/*
Chose how many filter samples to keep.  Several factors are in play.

 1) Discard at least one sample to allow a spike value to be
    discarded.

 2) Discard about 1-in-8 to 1-in-30 samples to handle spikes.

 3) Keep an odd number of samples to avoid median value being biased
    high or low.
*/
#define NKEEP(pp) ((CHOSENSAMPLES(pp) - 1 - (CHOSENSAMPLES(pp)>>3)) | 1)

#define DEFAULT_RESYNC_TIME  (57*60)    /* Gap between resync attempts (s). */
#define RETRY_RESYNC_TIME    (27*60)    /* Gap to emergency resync attempt. */
#ifdef ARCRON_KEEN
#define INITIAL_RESYNC_DELAY 500        /* Delay before first resync. */
#else
#define INITIAL_RESYNC_DELAY 50         /* Delay before first resync. */
#endif

    static const int moff[12] =
        { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
/* Flags for a raw open() of the clock serial device. */
#ifdef O_NOCTTY /* Good, we can avoid tty becoming controlling tty. */
#define OPEN_FLAGS (O_RDWR | O_NOCTTY)
#else           /* Oh well, it may not matter... */
#define OPEN_FLAGS (O_RDWR)
#endif


/*
 * Imported from ntp_timer module
 */
extern u_long current_time;             /* Current time (s). */
extern struct event timerqueue[];       /* Timer queue. */

/*
 * Imported from ntpd module
 */
extern int debug;                       /* Global debug flag. */

/* Length of queue of command bytes to be sent. */
#define CMDQUEUELEN 4                   /* Enough for two cmds + each \r. */
/* Queue tick time; interval in seconds between chars taken off queue. */
/* Must be >= 2 to allow o\r response to come back uninterrupted. */
#define QUEUETICK   2                   /* Allow o\r reply to finish. */

/*
 * ARC unit control structure
 */
struct arcunit {
    l_fp lastrec;       /* Time tag for the receive time (system). */
    int status;         /* Clock status. */

    int quality;        /* Quality of reception 0--5 for unit. */
                        /* We may also use the values -1 or 6 internally. */

    u_long next_resync; /* Next resync time (s) compared to current_time. */
    int resyncing;      /* Resync in progress if true. */

    /* In the outgoing queue, cmdqueue[0] is next to be sent. */
    char cmdqueue[CMDQUEUELEN+1]; /* Queue of outgoing commands + \0. */

    struct event ev;    /* Event tick for sending chars. */

    u_long saved_flags; /* Saved fudge flags. */
};
#ifdef ARCRON_LEAPSECOND_KEEN
    /* The flag `possible_leap' is set non-zero when any MSF unit
       thinks a leap-second may have happened.

       Set whenever we receive a valid time sample in the first hour of
       the first day of the first/seventh months.

       Outside the special hour this value is unconditionally set
       to zero by the receive routine.

       On finding itself in this timeslot, as long as the value is
       non-negative, the receive routine sets it to a positive value to
       indicate a resync to MSF should be performed.

       In the poll routine, if this value is positive and we are not
       already resyncing (eg from a sync that started just before
       midnight), start resyncing and set this value negative to
       indicate that a leap-triggered resync has been started.  Having
       set this negative prevents the receive routine setting it
       positive and thus prevents multiple resyncs during the witching
       hour.
     */
    static int possible_leap = 0;       /* No resync required by default. */
#endif

static void dummy_event_handler P((struct peer *));
static void   arc_event_handler P((struct peer *));

#define QUALITY_UNKNOWN     -1 /* Indicates unknown clock quality. */
#define MIN_CLOCK_QUALITY    0 /* Min quality clock will return. */
#define MIN_CLOCK_QUALITY_OK 3 /* Min quality for OK reception. */
#define MAX_CLOCK_QUALITY    5 /* Max quality clock will return. */

/*
 * Function prototypes
 */
static  int     arc_start       P((int, struct peer *));
static  void    arc_shutdown    P((int, struct peer *));
static  void    arc_receive     P((struct recvbuf *));
static  void    arc_poll        P((int, struct peer *));

/*
 * Transfer vector
 */
struct  refclock refclock_arc = {
        arc_start,              /* start up driver */
        arc_shutdown,           /* shut down driver */
        arc_poll,               /* transmit poll message */
        noentry,                /* not used (old arc_control) */
        noentry,                /* initialize driver (not used) */
        noentry,                /* not used (old arc_buginfo) */
        NOFLAGS                 /* not used */
};

/* Queue us up for the next tick. */
#define ENQUEUE(up) \
        do { \
        if((up)->ev.next != 0) { break; } /* WHOOPS! */ \
        (up)->ev.event_time = current_time + QUEUETICK; \
        TIMER_INSERT(timerqueue, &((up)->ev)); \
        } while(0)

/* Placeholder event handler---does nothing safely---soaks up lose tick. */
static void dummy_event_handler(peer)
        struct peer *peer;
{
#ifdef ARCRON_DEBUG
        if(debug) { printf("arc: dummy_event_handler() called.\n"); }
#endif
}

/*
Normal event handler.

Take first character off queue and send to clock if not a null.

Shift characters down and put a null on the end.

We assume that there is no parallelism so no race condition, but even
if there is nothing bad will happen except that we might send some bad
data to the clock once in a while.
*/
static void arc_event_handler(peer)
        struct peer *peer;
{
        struct refclockproc *pp = peer->procptr;
        register struct arcunit *up = (struct arcunit *)pp->unitptr;
        int i;
        char c;
#ifdef ARCRON_DEBUG
        if(debug > 2) { printf("arc: arc_event_handler() called.\n"); }
#endif

        c = up->cmdqueue[0];       /* Next char to be sent. */
        /* Shift down characters, shifting trailing \0 in at end. */
        for(i = 0; i < CMDQUEUELEN; ++i)
            { up->cmdqueue[i] = up->cmdqueue[i+1]; }

        /* Don't send '\0' characters. */
        if(c != '\0') {
            if(write(pp->io.fd, &c, 1) != 1) {
                syslog(LOG_NOTICE, "ARCRON: write to fd %d failed", pp->io.fd);
            }
#ifdef ARCRON_DEBUG
        else if(debug) { printf("arc: sent `%2.2x', fd %d.\n", c, pp->io.fd); }
#endif
        }
        ENQUEUE(up);                    /* Keep ticking... */
}

/*
 * arc_start - open the devices and initialize data for processing
 */
static int
arc_start(unit, peer)
        int unit;
        struct peer *peer;
{
        register struct arcunit *up;
        struct refclockproc *pp;
        int fd;
        char device[20];
#ifdef HAVE_TERMIOS
        struct termios arg;
#endif

        syslog(LOG_NOTICE, "ARCRON: %s: opening unit %d", arc_version, unit);
#ifdef ARCRON_DEBUG
        if(debug) {
            printf("arc: %s: attempt to open unit %d.\n", arc_version, unit);
            }
#endif

        /* Prevent a ridiculous device number causing overflow of device[]. */
        if((unit < 0) || (unit > 255)) { return(0); }

        /*
         * Open serial port. Use CLK line discipline, if available.
         */
        (void)sprintf(device, DEVICE, unit);
#ifdef TTYCLK
#ifdef ARCRON_DEBUG
        if(debug) { printf("arc: unit %d using refclock_open().\n", unit); }
#endif
        if (!(fd = refclock_open(device, SPEED, LDISC_CLK))) {
#ifdef DEBUG
            if(debug) { printf("arc: failed [TTYCLK] to open %s.\n", device); }
#endif
            return(0);
        }
#else
#ifdef ARCRON_DEBUG
        if(debug) { printf("arc: unit %d using open().\n", unit); }
#endif
        fd = open(device, OPEN_FLAGS);
        if(fd < 0) {
#ifdef DEBUG
            if(debug) { printf("arc: failed [open()] to open %s.\n", device); }
#endif
            return(0);
        }

        fcntl(fd, F_SETFL, 0); /* clear the descriptor flags */
#ifdef ARCRON_DEBUG
        if(debug)
            { printf("Opened RS232 port with file descriptor %d.\n", fd); }
#endif

#ifdef HAVE_TERMIOS

        arg.c_iflag = IGNBRK | ISTRIP;
        arg.c_oflag = 0;
        arg.c_cflag = B300 | CS8 | CREAD | CLOCAL | CSTOPB;
        arg.c_lflag = 0;
        arg.c_cc[VMIN] = 1;
        arg.c_cc[VTIME] = 0;

        tcsetattr(fd, TCSANOW, &arg);

#else

        syslog(LOG_ERR, "ARCRON: termios not supported in this driver");
        (void)close(fd);

        return 0;

#endif
#endif /* TTYCLK */

        up = (struct arcunit *) emalloc(sizeof(struct arcunit));
        if(!up) { (void) close(fd); return(0); }
        /* Set structure to all zeros... */
        memset((char *)up, 0, sizeof(struct arcunit));
        pp = peer->procptr;
        pp->io.clock_recv = arc_receive;
        pp->io.srcclock = (caddr_t)peer;
        pp->io.datalen = 0;
        pp->io.fd = fd;
        if(!io_addclock(&pp->io)) { (void) close(fd); free(up); return(0); }
        pp->unitptr = (caddr_t)up;

        /*
         * Initialize miscellaneous variables
         */
        peer->precision = PRECISION;
        peer->stratum = 2;              /* Default to stratum 2 not 0. */
        pp->clockdesc = DESCRIPTION;
        memcpy((char *)&pp->refid, REFID, 4);
        /* Spread out resyncs so that they should remain separated. */
        up->next_resync = current_time + INITIAL_RESYNC_DELAY + (67*unit)%1009;

#if 0 /* Not needed because of zeroing of arcunit structure... */
        up->resyncing = 0;              /* Not resyncing yet. */
        up->saved_flags = 0;            /* Default is all flags off. */
        /* Clear send buffer out... */
        {
        int i;
        for(i = CMDQUEUELEN; i >= 0; --i) { up->cmdqueue[i] = '\0'; }
        }
#endif

#ifdef ARCRON_KEEN
        up->quality = QUALITY_UNKNOWN;  /* Trust the clock immediately. */
#else
        up->quality = MIN_CLOCK_QUALITY;/* Don't trust the clock yet. */
#endif
        /* Set up event structure. */
        up->ev.peer = peer;
        up->ev.event_handler = arc_event_handler;
        ENQUEUE(up);                    /* Start ticking. */
        return(1);
}


/*
 * arc_shutdown - shut down the clock
 */
static void
arc_shutdown(unit, peer)
        int unit;
        struct peer *peer;
{
        register struct arcunit *up;
        struct refclockproc *pp;

        pp = peer->procptr;
        up = (struct arcunit *)pp->unitptr;
        up->ev.event_handler = dummy_event_handler;
        TIMER_DEQUEUE(&(up->ev));       /* Stop ticking. */
        io_closeclock(&pp->io);
        free(up);
}

/*
Compute space left in output buffer.
*/
static int space_left(up)
register struct arcunit *up;
{
    int spaceleft;
    /* Compute space left in buffer after any pending output. */
    for(spaceleft = 0; spaceleft < CMDQUEUELEN; ++spaceleft)
        { if(up->cmdqueue[CMDQUEUELEN - 1 - spaceleft] != '\0') { break; } }
    return(spaceleft);
}

/*
Send command by copying into command buffer as far forward as possible,
after any pending output.

Indicate an error by returning 0 if there is not space for the command.
*/
static int
send_slow(up, fd, s)
register struct arcunit *up;
int fd;
char *s;
{
    int sl = strlen(s);
    int spaceleft = space_left(up);
#ifdef ARCRON_DEBUG
    if(debug > 1) { printf("arc: spaceleft = %d.\n", spaceleft); }
#endif
    if(spaceleft < sl) { /* Should not normally happen... */
#ifdef ARCRON_DEBUG
        syslog(LOG_NOTICE, "ARCRON: send-buffer overrun (%d/%d)",
                           sl, spaceleft);
#endif
        return(0);                      /* FAILED! */
    }

    /* Copy in the command to be sent. */
    while(*s) { up->cmdqueue[CMDQUEUELEN - spaceleft--] = *s++; }

    return(1);
}


#ifdef ARCRON_OWN_FILTER
static int arc_refclock_process P((struct refclockproc *, int, int));
static int arc_refclock_sample P((l_fp *, struct refclockproc *, int, int));
static int arc_refclock_cmpl_fp P((const void *, const void *));
#endif

/* Macro indicating action we will take for different quality values. */
#define quality_action(q) \
        (((q) == QUALITY_UNKNOWN) ?         "UNKNOWN, will use clock anyway" : \
            (((q) < MIN_CLOCK_QUALITY_OK) ? "TOO POOR, will not use clock" : \
                                            "OK, will use clock"))

/*
 * arc_receive - receive data from the serial interface
 */
static void
arc_receive(rbufp)
        struct recvbuf *rbufp;
{
        register struct arcunit *up;
        struct refclockproc *pp;
        struct peer *peer;
        l_fp trtmp;
        char c;
        int i, n, wday, month, bst, status;
        int last_offset;

        /*
         * Initialize pointers and read the timecode and timestamp
         */
        peer = (struct peer *)rbufp->recv_srcclock;
        pp = peer->procptr;
        up = (struct arcunit *)pp->unitptr;


        /*
        If the command buffer is empty, and we are resyncing, insert a
        g\r quality request into it to poll for signal quality again.
        */
        if((up->resyncing) && (space_left(up) == CMDQUEUELEN)) {
#ifdef DEBUG
            if(debug > 1) { printf("arc: inserting signal-quality poll.\n"); }
#endif
            send_slow(up, pp->io.fd, "g\r");
        }

        /*
        The `last_offset' is the offset in lastcode[] of the last byte
        received, and which we assume actually received the input
        timestamp.

        (When we get round to using tty_clk and it is available, we
        assume that we will receive the whole timecode with the
        trailing \r, and that that \r will be timestamped.  But this
        assumption also works if receive the characters one-by-one.)
        */
        last_offset = pp->lencode+rbufp->recv_length - 1;

        /*
        We catch a timestamp iff:

          * The command code is `o' for a timestamp.

          * If ARCRON_MULTIPLE_SAMPLES is undefined then we must have
            exactly char in the buffer (the command code) so that we
            only sample the first character of the timecode as our
            `on-time' character.

          * The first character in the buffer is not the echoed `\r'
            from the `o` command (so if we are to timestamp an `\r' it
            must not be first in the receive buffer with lencode==1.
            (Even if we had other characters following it, we probably
            would have a premature timestamp on the '\r'.)

          * We have received at least one character (I cannot imagine
            how it could be otherwise, but anyway...).
        */
        c = rbufp->recv_buffer[0];
        if((pp->lastcode[0] == 'o') &&
#ifndef ARCRON_MULTIPLE_SAMPLES
           (pp->lencode == 1) &&
#endif
           ((pp->lencode != 1) || (c != '\r')) &&
           (last_offset >= 1)) {
            /* Note that the timestamp should be corrected if >1 char rcvd. */
            l_fp timestamp;
            timestamp = rbufp->recv_time;
#ifdef DEBUG
            if(debug) { /* Show \r as `R', other non-printing char as `?'. */
                printf("arc: stamp -->%c<-- (%d chars rcvd)\n",
                       ((c == '\r') ? 'R' : (isgraph(c) ? c : '?')),
                       rbufp->recv_length);
#endif
            }

            /*
            Now correct timestamp by offset of last byte received---we
            subtract from the receive time the delay implied by the
            extra characters received.

            Reject the input if the resulting code is too long, but
            allow for the trailing \r, normally not used but a good
            handle for tty_clk or somesuch kernel timestamper.
            */
            if(last_offset > LENARC) {
#ifdef ARCRON_DEBUG
                if(debug) {
                    printf("arc: input code too long (%d cf %d); rejected.\n",
                           last_offset, LENARC);
                }
#endif
                pp->lencode = 0;
                refclock_report(peer, CEVNT_BADREPLY);
                return;
            }

            L_SUBUF(&timestamp, charoffsets[last_offset]);
#ifdef ARCRON_DEBUG
            if(debug > 1) {
                printf(
"arc: %s%d char(s) rcvd, the last for lastcode[%d]; -%sms offset applied.\n",
                        ((rbufp->recv_length > 1) ? "*** " : ""),
                        rbufp->recv_length,
                        last_offset,
                        mfptoms((unsigned long)0,
                                charoffsets[last_offset],
                                1));
            }
#endif

#ifdef ARCRON_MULTIPLE_SAMPLES
            /*
            If taking multiple samples, capture the current adjusted
            sample iff:

              * No timestamp has yet been captured (it is zero), OR

              * This adjusted timestamp is earlier than the one already
                captured, on the grounds that this one suffered less
                delay in being delivered to us and is more accurate.

            */
            if(L_ISZERO(&(up->lastrec)) ||
               L_ISGEQ(&(up->lastrec), &timestamp))
#endif
                {
#ifdef ARCRON_DEBUG
                if(debug > 1) {
                    printf("arc: system timestamp captured.\n");
#ifdef ARCRON_MULTIPLE_SAMPLES
                    if(!L_ISZERO(&(up->lastrec))) {
                        l_fp diff;
                        diff = up->lastrec;
                        L_SUB(&diff, &timestamp);
                        printf("arc: adjusted timestamp by -%sms.\n",
                               mfptoms(diff.l_i, diff.l_f, 3));
                        }
#endif
                }
#endif
                up->lastrec = timestamp;
            }

        }

        /* Just in case we still have lots of rubbish in the buffer... */
        /* ...and to avoid the same timestamp being reused by mistake, */
        /* eg on receipt of the \r coming in on its own after the      */
        /* timecode.                                                   */
        if(pp->lencode >= LENARC) {
#ifdef ARCRON_DEBUG
            if(debug && (rbufp->recv_buffer[0] != '\r'))
                { printf("arc: rubbish in pp->lastcode[].\n"); }
#endif
            pp->lencode = 0;
            return;
        }

        /* Append input to code buffer, avoiding overflow. */
        for(i = 0; i < rbufp->recv_length; i++) {
            if(pp->lencode >= LENARC) { break; } /* Avoid overflow... */
            c = rbufp->recv_buffer[i];

            /* Drop trailing '\r's and drop `h' command echo totally. */
            if(c != '\r' && c != 'h') { pp->lastcode[pp->lencode++] = c; }

            /*
            If we've just put an `o' in the lastcode[0], clear the
            timestamp in anticipation of a timecode arriving soon.

            We would expect to get to process this before any of the
            timecode arrives.
            */
            if((c == 'o') && (pp->lencode == 1)) {
                L_CLR(&(up->lastrec));
#ifdef ARCRON_DEBUG
                if(debug > 1) { printf("arc: clearing timestamp.\n"); }
#endif
            }
        }

        /* Handle a quality message. */
        if(pp->lastcode[0] == 'g') {
            int r, q;
            if(pp->lencode < 3) { return; } /* Need more data... */
            r = (pp->lastcode[1] & 0x7f); /* Strip parity. */
            q = (pp->lastcode[2] & 0x7f); /* Strip parity. */
            if(((q & 0x70) != 0x30) || ((q & 0xf) > MAX_CLOCK_QUALITY) ||
               ((r & 0x70) != 0x30)) {
                /* Badly formatted response. */
#ifdef ARCRON_DEBUG
                if(debug) { printf("arc: bad `g' response %2x %2x.\n", r, q); }
#endif
                return;
            }
            if(r == '3') { /* Only use quality value whilst sync in progress. */
                up->quality = (q & 0xf);
#ifdef DEBUG
                if(debug) { printf("arc: signal quality %d.\n", up->quality); }
#endif
            } else if( /* (r == '2') && */ up->resyncing) {
#ifdef DEBUG
                if(debug)
                    {
                    printf("arc: sync finished, signal quality %d: %s\n",
                       up->quality,
                       quality_action(up->quality));
                    }
#endif
                syslog(LOG_NOTICE,
                       "ARCRON: sync finished, signal quality %d: %s",
                       up->quality,
                       quality_action(up->quality));
                up->resyncing = 0; /* Resync is over. */

#ifdef ARCRON_KEEN
                /* Clock quality dubious; resync earlier than usual. */
                if((up->quality == QUALITY_UNKNOWN) ||
                   (up->quality < MIN_CLOCK_QUALITY_OK))
                    { up->next_resync = current_time + RETRY_RESYNC_TIME; }
#endif
            }
            pp->lencode = 0;
            return;
        }

        /* Stop now if this is not a timecode message. */
        if(pp->lastcode[0] != 'o') {
            pp->lencode = 0;
            refclock_report(peer, CEVNT_BADREPLY);
            return;
        }

        /* If we don't have enough data, wait for more... */
        if(pp->lencode < LENARC) { return; }


        /* WE HAVE NOW COLLECTED ONE TIMESTAMP (phew)... */
#ifdef ARCRON_DEBUG
        if(debug > 1) { printf("arc: NOW HAVE TIMESTAMP...\n"); }
#endif

        /* But check that we actually captured a system timestamp on it. */
        if(L_ISZERO(&(up->lastrec))) {
#ifdef ARCRON_DEBUG
            if(debug) { printf("arc: FAILED TO GET SYSTEM TIMESTAMP\n"); }
#endif
            pp->lencode = 0;
            refclock_report(peer, CEVNT_BADREPLY);
            return;
        }
        /*
        Append a mark of the clock's received signal quality for the
        benefit of Derek Mulcahy's Tcl/Tk utility (we map the `unknown'
        quality value to `6' for his s/w) and terminate the string for
        sure.  This should not go off the buffer end.
        */
        pp->lastcode[pp->lencode] = ((up->quality == QUALITY_UNKNOWN) ?
                                                '6' : ('0' + up->quality));
        pp->lastcode[pp->lencode + 1] = '\0'; /* Terminate for printf(). */
        record_clock_stats(&peer->srcadr, pp->lastcode);

        /* We don't use the micro-/milli- second part... */
        pp->usec = 0;
        pp->msec = 0;

        n = sscanf(pp->lastcode, "o%2d%2d%2d%1d%2d%2d%2d%1d%1d",
            &pp->hour, &pp->minute, &pp->second,
            &wday, &pp->day, &month, &pp->year, &bst, &status);

        /* Validate format and numbers. */
        if(n != 9) {
#ifdef ARCRON_DEBUG
            /* Would expect to have caught major problems already... */
            if(debug) { printf("arc: badly formatted data.\n"); }
#endif
            refclock_report(peer, CEVNT_BADREPLY);
            return;
        }
        /*
        Validate received values at least enough to prevent internal
        array-bounds problems, etc.
        */
        if((pp->hour < 0) || (pp->hour > 23) ||
           (pp->minute < 0) || (pp->minute > 59) ||
           (pp->second < 0) || (pp->second > 60) /*Allow for leap seconds.*/ ||
           (wday < 1) || (wday > 7) ||
           (pp->day < 1) || (pp->day > 31) ||
           (month < 1) || (month > 12) ||
           (pp->year < 0) || (pp->year > 99)) {
            /* Data out of range. */
            refclock_report(peer, CEVNT_BADREPLY);
            return;
        }
        /* Check that BST/UTC bits are the complement of one another. */
        if(!(bst & 2) == !(bst & 4)) {
            refclock_report(peer, CEVNT_BADREPLY);
            return;
        }

        if(status & 0x8) { syslog(LOG_NOTICE, "ARCRON: battery low"); }

        /* Year-2000 alert! */
        /* Attempt to wrap 2-digit date into sensible window. */
        /* This code was written in 1997, so that is the window start. */
        if(pp->year < 97) { pp->year += 2000; }
        else /* if(pp->year < 100) */ { pp->year += 1900; }
        /*
        Attempt to do the right thing by screaming that the code will
        soon break when we get to the end of its useful life.  What a
        hero I am...  PLEASE FIX LEAP-YEAR AND WRAP CODE IN 209X!
        */
        if(pp->year >= 2090) {          /* This should get attention B^> */
            syslog(LOG_NOTICE,
"ARCRON: fix me!  EITHER YOUR DATE IS BADLY WRONG or else I will break soon!");
        }
#ifdef DEBUG
        if(debug) {
            printf("arc: n=%d %02d:%02d:%02d %02d/%02d/%04d %1d %1d\n",
                n,
                pp->hour, pp->minute, pp->second,
                pp->day, month, pp->year, bst, status);
        }
#endif

        /*
        The status value tested for is not strictly supported by the
        clock spec since the value of bit 2 (0x4) is claimed to be
        undefined for MSF, yet does seem to indicate if the last resync
        was successful or not.
        */
        pp->leap = LEAP_NOWARNING;
        status &= 0x7;
        if(status == 0x3) {
            pp->lasttime = current_time;
            if(status != up->status)
                { syslog(LOG_NOTICE, "ARCRON: signal acquired"); }
        } else {
            if(status != up->status) {
                syslog(LOG_NOTICE, "ARCRON: signal lost");
                pp->leap = LEAP_NOTINSYNC; /* MSF clock is free-running. */
                up->status = status;
                refclock_report(peer, CEVNT_FAULT);
                return;
            }
        }
        up->status = status;

        pp->day += moff[month - 1];

        /* Good 'til 1st March 2100 */
        if(((pp->year % 4) == 0) && month > 2) { pp->day++; }

        /* Convert to UTC if required */
        if(bst & 2) {
            pp->hour--;
            if (pp->hour < 0) {
                pp->hour = 23;
                pp->day--;
                /* If we try to wrap round the year (BST on 1st Jan), reject.*/
                if(pp->day < 0) {
                    refclock_report(peer, CEVNT_BADTIME);
                    return;
                }
            }
        }

        /* If clock signal quality is unknown, revert to default PRECISION...*/
        if(up->quality == QUALITY_UNKNOWN) { peer->precision = PRECISION; }
        /* ...else improve precision if flag3 is set... */
        else {
            peer->precision = ((pp->sloppyclockflag & CLK_FLAG3) ?
                        HIGHPRECISION : PRECISION);
        }

        /* Notice and log any change (eg from initial defaults) for flags. */
        if(up->saved_flags != pp->sloppyclockflag) {
#ifdef ARCRON_DEBUG
            syslog(LOG_NOTICE, "ARCRON: flags enabled: %s%s%s%s",
                    ((pp->sloppyclockflag & CLK_FLAG1) ? "1" : "."),
                    ((pp->sloppyclockflag & CLK_FLAG2) ? "2" : "."),
                    ((pp->sloppyclockflag & CLK_FLAG3) ? "3" : "."),
                    ((pp->sloppyclockflag & CLK_FLAG4) ? "4" : "."));
            /* Note effects of flags changing... */
            if(debug) {
                printf("arc: CHOSENSAMPLES(pp) = %d.\n", CHOSENSAMPLES(pp));
                printf("arc: NKEEP(pp) = %d.\n", NKEEP(pp));
                printf("arc: PRECISION = %d.\n", peer->precision);
            }
#endif
            up->saved_flags = pp->sloppyclockflag;
        }

        /* Note time of last believable timestamp. */
        pp->lastrec = up->lastrec;

#ifdef ARCRON_LEAPSECOND_KEEN
        /* Find out if a leap-second might just have happened...
           (ie is this the first hour of the first day of Jan or Jul?)
         */
        if((pp->hour == 0) &&
           (pp->day == 1) &&
           ((month == 1) || (month == 7))) {
            if(possible_leap >= 0) {
                /* A leap may have happened, and no resync has started yet...*/
                possible_leap = 1;
                }
        } else {
            /* Definitely not leap-second territory... */
            possible_leap = 0;
        }
#endif

        /*
         * Process the new sample in the median filter and determine the
         * reference clock offset and dispersion. We use lastrec as both
         * the reference time and receive time in order to avoid being
         * cute, like setting the reference time later than the receive
         * time, which may cause a paranoid protocol module to chuck out
         * the data.
         */
#ifdef ARCRON_OWN_FILTER
        if(!arc_refclock_process(pp, CHOSENSAMPLES(pp), NKEEP(pp)))
#else
        if(!refclock_process(pp, CHOSENSAMPLES(pp), NKEEP(pp)))
#endif
            {
            refclock_report(peer, CEVNT_BADTIME);
            if(debug) { printf("arc: sample rejected.\n"); }
            return;
            }
        trtmp = pp->lastrec;
        refclock_receive(peer, &pp->offset, 0, pp->dispersion,
            &trtmp, &pp->lastrec, pp->leap);
}


/* request_time() sends a time request to the clock with given peer. */
/* This automatically reports a fault if necessary. */
/* No data should be sent after this until arc_poll() returns. */
static  void    request_time    P((int, struct peer *));
static void
request_time(unit, peer)
        int unit;
        struct peer *peer;
{
        struct refclockproc *pp = peer->procptr;
        register struct arcunit *up = (struct arcunit *)pp->unitptr;
#ifdef DEBUG
        if(debug) { printf("arc: unit %d: requesting time.\n", unit); }
#endif
        if (!send_slow(up, pp->io.fd, "o\r")) {
#ifdef ARCRON_DEBUG
            syslog(LOG_NOTICE, "ARCRON: unit %d: problem sending", unit);
#endif
            refclock_report(peer, CEVNT_FAULT);
            return;
        }
        pp->polls++;
}

/*
 * arc_poll - called by the transmit procedure
 */
static void
arc_poll(unit, peer)
        int unit;
        struct peer *peer;
{
        register struct arcunit *up;
        struct refclockproc *pp;
        int resync_needed;              /* Should we start a resync? */

        pp = peer->procptr;
        up = (struct arcunit *)pp->unitptr;
        pp->lencode = 0;
        memset(pp->lastcode, 0, sizeof(pp->lastcode));

#if 0
        /* Flush input. */
        tcflush(pp->io.fd, TCIFLUSH);
#endif

        /* Resync if our next scheduled resync time is here or has passed. */
        resync_needed = (up->next_resync <= current_time);

#ifdef ARCRON_LEAPSECOND_KEEN
        /*
         Try to catch a potential leap-second insertion or deletion quickly.

         In addition to the normal NTP fun of clocks that don't report
         leap-seconds spooking their hosts, this clock does not even
         sample the radio sugnal the whole time, so may miss a
         leap-second insertion or deletion for up to a whole sample
         time.

         To try to minimise this effect, if in the first few minutes of
         the day immediately following a leap-second-insertion point
         (ie in the first hour of the first day of the first and sixth
         months), and if the last resync was in the previous day, and a
         resync is not already in progress, resync the clock
         immediately.

         */
        if((possible_leap > 0) &&       /* Must be 00:XX 01/0{1,7}/XXXX. */
           (!up->resyncing)) {          /* No resync in progress yet. */
           resync_needed = 1;
           possible_leap = -1;          /* Prevent multiple resyncs. */
           syslog(LOG_NOTICE,"ARCRON: unit %d: checking for leap second",unit);
           }
#endif

        /* Do a resync if required... */
        if(resync_needed) {
            /* First, reset quality value to `unknown' so we can detect */
            /* when a quality message has been responded to by this     */
            /* being set to some other value.                           */
            up->quality = QUALITY_UNKNOWN;

            /* Note that we are resyncing... */
            up->resyncing = 1;

            /* Now actually send the resync command and an immediate poll. */
#ifdef DEBUG
            if(debug) { printf("arc: sending resync command (h\\r).\n"); }
#endif
            syslog(LOG_NOTICE, "ARCRON: unit %d: sending resync command", unit);
            send_slow(up, pp->io.fd, "h\r");

            /* Schedule our next resync... */
            up->next_resync = current_time + DEFAULT_RESYNC_TIME;

            /* Drop through to request time if appropriate. */
        }

        /* If clock quality is too poor to trust, indicate a fault. */
        /* If quality is QUALITY_UNKNOWN and ARCRON_KEEN is defined,*/
        /* we'll cross our fingers and just hope that the thing     */
        /* synced so quickly we did not catch it---we'll            */
        /* double-check the clock is OK elsewhere.                  */
        if(
#ifdef ARCRON_KEEN
           (up->quality != QUALITY_UNKNOWN) &&
#else
           (up->quality == QUALITY_UNKNOWN) ||
#endif
           (up->quality < MIN_CLOCK_QUALITY_OK)) {
#ifdef DEBUG
            if(debug) {
                printf("arc: clock quality %d too poor.\n", up->quality);
            }
#endif
            refclock_report(peer, CEVNT_FAULT);
            return;
        }
        /* This is the normal case: request a timestamp. */
        request_time(unit, peer);
}





#ifdef ARCRON_OWN_FILTER

/* Very small fixes to the 3-5.90 ntp_refclock.c code. */

#include "ntp_unixtime.h" /* For TVUTOTSF, etc. */
#define REFCLOCKMAXDISPERSE (FP_SECOND/4) /* max sample dispersion */

/*
 * Compare two l_fp's - used with qsort()
 */
static int
arc_refclock_cmpl_fp(p1, p2)
        register const void *p1, *p2;   /* l_fp to compare */
{

        if (!L_ISGEQ((l_fp *)p1, (l_fp *)p2))
                return (-1);
        if (L_ISEQU((l_fp *)p1, (l_fp *)p2))
                return (0);
        return (1);
}

/*
 * refclock_process - process a pile of samples from the clock
 *
 * This routine converts the timecode in the form days, hours, minutes,
 * seconds, milliseconds/microseconds to internal timestamp format.
 * Further processing is then delegated to refclock sample
 */
static int
arc_refclock_process(pp, nstart, nskeep)
        struct refclockproc *pp; /* peer structure pointer */
        int nstart;             /* stages of median filter */
        int nskeep;             /* stages after outlyer trim */
{
        l_fp offset;

        /*
         * Compute the timecode timestamp from the days, hours, minutes,
         * seconds and milliseconds/microseconds of the timecode. Use
         * clocktime() for the aggregate seconds and the msec/usec for
         * the fraction, when present. Note that this code relies on the
         * filesystem time for the years and does not use the years of
         * the timecode.
         */
        if (!clocktime(pp->day, pp->hour, pp->minute, pp->second, GMT,
            pp->lastrec.l_ui, &pp->yearstart, &offset.l_ui))
                return (0);
        if (pp->usec) {
                TVUTOTSF(pp->usec, offset.l_uf);
        } else {
                MSUTOTSF(pp->msec, offset.l_uf);
        }

        L_ADD(&offset, &pp->fudgetime1);

        pp->lastref = offset;   /* save last reference time */

        /*
         * Include the configured fudgetime1 adjustment.
         */
        L_SUB(&offset, &pp->lastrec); /* form true offset */
#ifdef ARCRON_DEBUG
        if(debug > 1) { /* DHD addition. */
            printf("arc: raw offset %sms.\n",
                mfptoms(offset.l_i, offset.l_f, 2));
        }
#endif

        return arc_refclock_sample(&offset, pp, nstart, nskeep);
}

/*
 * refclock_sample - process a pile of samples from the clock
 *
 * This routine converts the timecode in the form days, hours, miinutes,
 * seconds, milliseconds/microseconds to internal timestamp format. It
 * then calculates the difference from the receive timestamp and
 * assembles the samples in a shift register. It implements a recursive
 * median filter to suppress spikes in the data, as well as determine a
 * rough dispersion estimate. A configuration constant time adjustment
 * fudgetime1 can be added to the final offset to compensate for various
 * systematic errors. The routine returns one if success and zero if
 * failure due to invalid timecode data or very noisy offsets.
 *
 * This interface is needed to allow for clocks (e. g. parse) that can
 * provide the correct offset including year information (though NTP
 * usually gives up on offsets greater than 1000 seconds).
 */
static int
arc_refclock_sample(sample_offset, pp, nstart, nskeep)
        l_fp *sample_offset;    /* input offset (offset! - not a time stamp)
                                   for filter machine */
        struct refclockproc *pp; /* peer structure pointer */
        int nstart;             /* stages of median filter */
        int nskeep;             /* stages after outlyer trim */
{
        int i, n;
        l_fp offset, median, lftmp;
        l_fp off[MAXSTAGE];
        u_fp disp;

        /*
         * Subtract the receive timestamp from the timecode timestamp
         * to form the raw offset. Insert in the median filter shift
         * register.
         */
        pp->nstages = nstart;
        offset = *sample_offset;

        i = ((int)(pp->coderecv)) % pp->nstages;

        pp->filter[i] = offset;
        if (pp->coderecv == 0)
                for (i = 1; (u_int) i < pp->nstages; i++)
                        pp->filter[i] = pp->filter[0];
        pp->coderecv++;

        /*
         * Copy the raw offsets and sort into ascending order
         */
        for (i = 0; (u_int) i < pp->nstages; i++)
                off[i] = pp->filter[i];
        qsort((char *)off, pp->nstages, sizeof(l_fp), arc_refclock_cmpl_fp);

        /*
         * Reject the furthest from the median of nstages samples until
         * nskeep samples remain.
         */
        i = 0;
        n = pp->nstages;
        while ((n - i) > nskeep) {
                lftmp = off[n - 1];
                median = off[(n + i) / 2];
                L_SUB(&lftmp, &median);
                L_SUB(&median, &off[i]);
                if (L_ISHIS(&median, &lftmp)) {
                        /* reject low end */
                        i++;
                } else {
                        /* reject high end */
                        n--;
                }
        }

        /*
         * Compute the dispersion based on the difference between the
         * extremes of the remaining offsets. Add to this the time since
         * the last clock update, which represents the dispersion
         * increase with time. We know that NTP_MAXSKEW is 16. If the
         * sum is greater than the allowed sample dispersion, bail out.
         * If the loop is unlocked, return the most recent offset;
         * otherwise, return the median offset.
         */
        lftmp = off[n - 1];
        L_SUB(&lftmp, &off[i]);
        disp = LFPTOFP(&lftmp) + current_time - pp->lasttime;
        if (disp > REFCLOCKMAXDISPERSE)
                return (0);

        pp->offset = off[(n + i) / 2];   /* Originally: pp->offset = offset; */
        pp->dispersion = disp;

        return (1);
}

#endif /* ARCRON_OWN_FILTER */

#endif
----------
X-Sun-Data-Type: default
X-Sun-Data-Description: default
X-Sun-Data-Name: driver27.html
X-Sun-Charset: us-ascii
X-Sun-Content-Lines: 425

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Strict//EN">
<html><head><title>
Arcron MSF Receiver
</title></head><body><h3>
Arcron MSF Receiver
</h3><hr>

<p><h4>Synopsis</h4>

<p>Address: 127.127.27.<var>u</var>
<br>Reference ID: MSF
<br>Driver ID: ARCRON_MSF
<br>Serial Port: <code>/dev/arc<var>u</var></code>; 300 baud, 8-bits, 2-stop,
no parity
<br>Features: <code>tty_clk</code>

<p><h4>Description</h4>

<p>This driver supports the Arcron MSF receiver, and would probably
also support the DCF77 variant of the same clock.  The clock reports
its ID as ``<code>MSFa</code>'' to indicate MSF as a source and the use
of the ARCRON driver.

<p>This documentation describes version V1.1 (1997/06/23) of the source
and has been tested (amongst others) against xntpd3-5.90 on Solaris-1 (SunOS 4.1.3_U1 on
an SS1 serving as a router and firewall) and against xntpd3-5.90 on
Solaris-2.5 (on a SS1+ and TurboSPARC 170MHz).  This code will probably
work, and show increased stability, reduced jitter and more efficiency
(fewer context switches) with the <code>tty_clk</code>
discipline/STREAMS module installed, but this has not been tested.  For
a to-do list see the comments at the start of the code.

<p>This code has been significantly slimmed down since the V1.0 version,
roughly halving the memory footprint of its code and data.

<p>This driver is designed to allow the unit to run from batteries as
designed, for something approaching the 2.5 years expected in the usual
stand-alone mode, but no battery-life measurements have been taken.

<p>Much of this code is originally from the other refclock driver files
with thanks.  The code was originally made to work with the clock
by <a href="mailto:derek@toybox.demon.co.uk">Derek Mulcahy</a>, with
modifications by <a href="mailto:d@hd.org">Damon Hart-Davis</a>.
Thanks also to <a href="mailto:lyndond@sentinet.co.uk">Lyndon David</a>
for some of the specifications of the clock.

<p>There is support for a Tcl/Tk monitor written by Derek Mulcahy
that examines the output stats; see the
<a href="http://www2.exnet.com/NTP/ARC/ARC.html">ARC Rugby MSF Receiver</a>
page for more details and the code.

<p>Look at the notes at the start of the code for further information;
some of the more important details follow.

<p>The driver interrogates the clock at each poll (ie every 64s by
default) for a timestamp.  The clock responds at the start of the next
second (with the start bit of the first byte being on-time).  The time
is in `local' format, including the daylight savings adjustment when it
is in effect.  The driver code converts the time back to UTC.

<p>The clock claims to be accurate to within about 20ms of the
MSF-broadcast time, and given the low data transmission speed from
clock to host, and the fact that the clock is not in continuous sync
with MSF, it seems sensible to set the `precision' of this clock to -5
or -4, -4 being used in this code, which builds in a reported
dispersion of over 63ms (ie says ``This clock is not very good.'').
You can improve the reported precision to -4 (and thus reduce the base
dispersion to about 31ms) by setting the fudge <code>flag3</code> to
<code>1</code>.

<p>Even a busy and slow IP link can yield lower dispersions than this
from polls of primary time servers on the Internet, which reinforces
the idea that this clock should be used as a backup in case of problems
with such an IP link, or in the unfortunate event of failure of more
accurate sources such as GPS.

<p>By default this clock reports itself to be at stratum 2 rather than
the usual stratum 0 for a refclock, because it is not really suited to
be used as other than a backup source.  The stratum reported can be
changed with the <code>fudge</code> directive to be whatever you like.
After careful monitoring of your clock, and appropriate choice of the
<code>time1</code> fudge factor to remove systematic errors in the
clock's reported time, you might fudge the clock to stratum 1 to allow
a stratum-2 secondary server to sync to it.

<p>The driver code arranges to resync the clock to MSF at intervals of
a little less than an hour (deliberately avoiding the same time each
hour to avoid any systematic problems with the signal or host).  Whilst
resyncing, the driver supplements the normal polls for time from the
clock with polls for the reception signal quality reported by the
clock.  If the signal quality is too low (0--2 out of a range of 0--5),
we chose not to trust the clock until the next resync (which we bring
forward by about half an hour).  If we don't catch the resync, and so
don't know the signal quality, we do trust the clock (because this
would generally be when the signal is very good and a resync happens
quickly), but we still bring the next resync forward and reduce the
reported precision (and thus increase reported dispersion).

<p>If we force resyncs to MSF too often we will needlessly exhaust the
batteries the unit runs from.  During clock resync this driver tries to
take enough time samples to avoid <code>xntpd</code> losing sync in
case this clock is the current peer.  By default the clock would only
resync to MSF about once per day, which would almost certainly not be
acceptable for NTP purposes.

<p>The driver does not force an immediate resync of the clock to MSF
when it starts up to avoid excessive battery drain in case
<code>xntpd</code> is going to be repeatedly restarted for any reason,
and also to allow enough samples of the clock to be taken for
<code>xntpd</code> to sync immediately to this clock (and not remain
unsynchronised or to sync briefly to another configured peer, only to
hop back in a few poll times, causing unnecessary disturbance).  This
behaviour should not cause problems because the driver will not accept
the timestamps from the clock if the status flag delivered with the
time code indicates that the last resync attempt was unsuccessful, so
the initial timestamps will be close to reality, even if with up to a
day's clock drift in the worst case (the clock by default resyncs to
MSF once per day).

<p>The clock has a peculiar RS232 arrangement where the transmit lines
are powered from the receive lines, presumably to minimise battery
drain.  This arrangement has two consequences:
<ul>
<li>Your RS232 interface must drive both +ve and -ve
<li>You must (in theory) wait for an echo and a further 10ms between
    characters
</ul>
This driver, running on standard Sun hardware, seems to work fine; note
the use of the <code>send_slow()</code> routine to queue up command
characters to be sent once every two seconds.

<p>Three commands are sent to the clock by this driver.  Each command
consists of a single letter (of which only the bottom four bits are
significant), followed by a CR (ASCII 13).  Each character sent to the
clock should be followed by a delay to allow the unit to echo the
character, and then by a further 10ms.  Following the echo of the
command string, there may be a response (ie in the cae of the
<code>g</code> and <code>o</code> commands below), which in the case of
the <code>o</code> command may be delayed by up to 1 second so as the
start bit of the first byte of the response can arrive on time.

The commands and their responses are:
<dl>

<dt><code>g</code> CR
<dd>Request for signal quality.  Answer only valid during (late part
of) resync to MSF signal.  The response consists of two characters as
follows:
    <ol>
    <li><dl compact>
        <dt>bit 7</dt> <dd> parity
        <dt>bit 6</dt> <dd> always 0
        <dt>bit 5</dt> <dd> always 1
        <dt>bit 4</dt> <dd> always 1
        <dt>bit 3</dt> <dd> always 0
        <dt>bit 2</dt> <dd> always 0
        <dt>bit 1</dt> <dd> always 1
        <dt>bit 0</dt> <dd> = 0 if no reception attempt at the moment,
                       = 1 if reception attempt (ie resync) in progress
        </dl>
    <li><dl compact>
        <dt>bit 7</dt> <dd> parity
        <dt>bit 6</dt> <dd> always 0
        <dt>bit 5</dt> <dd> always 1
        <dt>bit 4</dt> <dd> always 1
        <dt>bit 3</dt> <dd> always 0
        <dt>bit 2--0</dt> <dd> reception signal quality in the range 0--5
                        (very poor to very good); if in the range 0--2 no
                        successful reception is to be expected.  The reported
                        value drops to zero when not resyncing, ie when first
                        returned byte is not `3'.
        </dl>
    </ol>

<dt><code>h</code> CR
<dd>Request to resync to MSF.  Can take up from about 30s to 360s.
Drains batteries so should not be used excessively.  After this the
clock time and date should be correct and the phase within 20ms of time
as transmitted from Rugby MSF (remember to allow for propagation
time).  By default the clock resyncs once per day shortly after 2am
(presumably to catch transitions to/from daylight saving time
quickly).  With this driver code we resync at least once per hour to
minimise clock wander.

<dt><code>o</code> CR
<dd>Request timestamp.  Start bit of first byte of response is on-time,
so may be delayed up to 1 second.  Note that when the BST mode is in
effect the time is GMT/UTC +0100, ie an hour ahead of UTC to reflect
local time in the UK.  The response data is as follows:
    <ol>
    <li>hours tens (hours range from 00 to 23)
    <li>hours units
    <li>minutes tens (minutes range from 00 to 59)
    <li>minutes units
    <li>seconds tens (seconds presumed to range from 00 to 60 to allow for leap second)
    <li>seconds units
    <li>day of week 1 (Monday) to 7 (Sunday)
    <li>day of month tens (day ranges from 01 to 31)
    <li>day of month units
    <li>month tens (months range from 01 to 12)
    <li>month units
    <li>year tens (years range from 00 to 99)
    <li>year units
    <li>BST/UTC status <dl compact>
        <dt>bit 7</dt> <dd> parity
        <dt>bit 6</dt> <dd> always 0
        <dt>bit 5</dt> <dd> always 1
        <dt>bit 4</dt> <dd> always 1
        <dt>bit 3</dt> <dd> always 0
        <dt>bit 2</dt> <dd> = 1 if UTC is in effect (reverse of bit 1)
        <dt>bit 1</dt> <dd> = 1 if BST is in effect (reverse of bit 2)
        <dt>bit 0</dt> <dd> = 1 if BST/UTC change pending
        </dl>
    <li>clock status<dl compact>
        <dt>bit 7</dt> <dd> parity
        <dt>bit 6</dt> <dd> always 0
        <dt>bit 5</dt> <dd> always 1
        <dt>bit 4</dt> <dd> always 1
        <dt>bit 3</dt> <dd> = 1 if low battery is detected
        <dt>bit 2</dt> <dd> = 1 if last resync failed (though officially
                                undefined for the MSF clock)
        <dt>bit 1</dt> <dd> = 1 if at least one reception attempt since
                                0230 for the MSF clock was successful
                                (0300 for the DCF77 clock)
        <dt>bit 0</dt> <dd> = 1 if the clock has valid time---reset to zero
                                when clock is reset (eg at power-up), and
                                set to 1 after first successful resync attempt.
        </dl>
    </ol>
The driver only accepts time from the clock if the bottom three bits of
the status byte are <code>011</code>.  The leap-year logic for
computing day-in-year is only valid until 2099, and the clock will
ignore stamps from the clock that claim BST is in effect in the first
hour of each year.  If the UK parliament decides to move us to
+0100/+0200 time as opposed to the current +0000/+0100 time, it is not
clear what effect that will have on the time broadcast by MSF, and
therefore on this driver's usefulness.

</dl>

A typical <code>ntp.conf</code> configuration file for this driver
might be:
<pre>
# hostname(n) means we expect (n) to be the stratum at which hostname runs.

#------------------------------------------------------------------------------
# SYNCHRONISATION PARTNERS
# ========================

# Our betters...
server 127.127.27.0 # ARCRON MSF radio clock(1).
# Fudge stratum and other features as required.
# ADJUST time1 VALUE FOR YOUR HOST, CLOCK AND LOCATION!
fudge 127.127.27.0 stratum 1 time1 0.016 flag3 1 flag4 1

peer 11.22.33.9 # tick(1--2).
peer 11.22.33.4 # tock(3), boot/NFS server.

# This shouldn't get swept away unless left untouched for a long time.
driftfile /var/tmp/ntp.drift

#------------------------------------------------------------------------------
# RESTRICTIONS
# ============

# By default, don't trust and don't allow modifications.  Ignore in fact.
restrict default ignore notrust nomodify

# Allow others in our subnet to check us out...
restrict 11.22.33.0 mask 255.255.255.0 nomodify notrust

# Trust our peers for time.  Don't trust others in case they are insane.
restrict 127.127.27.0 nomodify
restrict 11.22.33.4 nomodify
restrict 11.22.33.9 nomodify

# Allow anything from the local host.
restrict 127.0.0.1
</pre>

There are a few <code>#define</code>s in the code that you might wish
to play with:
<dl>
<dt><code>ARCRON_KEEN</code></dt>
<dd>With this defined, the code is relatively trusting of the clock,
and assumes that you will have the clock as one of a few time sources,
so will bend over backwards to use the time from the clock when
available and avoid <code>xntpd</code> dropping sync from the clock
where possible.  You may wish to undefine this, especially if you have
better sources of time or your reception is ropey.  However, there are
many checks built in even with this flag defined.

<dt><code>ARCRON_OWN_FILTER</code></dt>
<dd>When defined, the code uses its own median-filter code rather than
that available in <code>ntp_refclock.c</code> since the latter seems to
have a minor bug, at least in version 3-5.90.  If this bug goes away
this flag should be turned off to avoid duplication of code.  (The bug,
if that's what it is, causes the last raw offset to be used rather than
the median offset.)

<p>Without this defined (and without
<code>ARCRON_MULTIPLE_SAMPLES</code> below) a typical set of offsets
reported and used to drive the clock-filter algorithm is (oldest
last):
<pre>
filtoffset=  -4.32  -34.82   -0.78    0.89    2.76    4.58   -3.92   -2.17
</pre>
Look at that spike!

<p>With this defined a typical set of offsets is:
<pre>
filtoffset=  -7.06   -7.06   -2.91   -2.91   -2.91   -1.27   -9.54   -6.70
</pre>
with the repeated values being some evidence of outlyers being discarded.

<dt><code>ARCRON_MULTIPLE_SAMPLES</code></dt>
<dd>When is defined, we regard each character in the returned timecode
as at a known delay from the start of the second, and use the smallest
(most negative) offset implied by any such character, ie with the
smallest kernel-induced display, and use that.  This helps to reduce
jitter and spikes.

<dt><code>ARCRON_LEAPSECOND_KEEN</code></dt>
<dd>When is defined, we try to do a resync to MSF as soon as possible
in the first hour of the morning of the first day of the first and
seventh months, ie just after a leap-second insertion or deletion would
happen if it is going to.  This should help compensate for the fact
that this clock does not continuously sample MSF, which compounds the
fact that MSF itself gives no warning of an impending leap-second
event.  This code did not seem functional at the leap-second insertion
of 30th June 1997 so is by default disabled.

<dt><code>PRECISION</code></dt>
<dd>Currently set to <code>-4</code>, but you may wish to set it to
<code>-5</code> if you are more conservative, or to <code>-6</code> if
you have particularly good experience with the clock and you live on
the edge.  Note that the <code>flag3</code> fudge value will improve
the reported dispersion one notch if clock signal quality is known
good.  So maybe just leave this alone. B^)

<dt><code>NSAMPLES</code></dt>
<dd>Should be at least 3 to help smooth out sampling jitters.  Can be
more, but if made too long can make <code>xntpd</code> overshoot on
clock corrections and can hold onto bad samples longer than you would
like.  With this set to 4 and <code>NKEEP</code> set to 3 this allows
the occasional bad sample (in my experience less than 1 value in 10) to
be dropped.  (Note that there seems to be some sort of `beat' effect in
the offset with a periodicity of about 7 samples as of this writing
(1997/05/11) still under investigation; a filter of approximately this
length should be able to almost completely suppress this effect.)  Note
that if the fudge-factor <code>flag3</code> is set to 1, a larger
<code>NSAMPLES</code> is used.

</dl>

<p><h4>Monitor Data</h4>

<p>Each timecode is written to the <code>clockstats</code> file with a
signal quality value appended (`0'--`5' as reported by the clock, or
`6' for unknown).

<p>Each resync and result (plus gaining or losing MSF sync) is logged
to the system log at level <code>LOG_NOTICE</code>; note that each
resync drains the unit's batteries, so the syslog entry seems justified.

<p>Syslog entries are of the form:
<pre>
May 10 10:15:24 oolong xntpd[615]: ARCRON: unit 0: sending resync command
May 10 10:17:32 oolong xntpd[615]: ARCRON: sync finished, signal quality 5: OK, will use clock
May 10 11:13:01 oolong xntpd[615]: ARCRON: unit 0: sending resync command
May 10 11:14:06 oolong xntpd[615]: ARCRON: sync finished, signal quality -1: UNKNOWN, will use clock anyway
May 10 11:41:49 oolong xntpd[615]: ARCRON: unit 0: sending resync command
May 10 11:43:57 oolong xntpd[615]: ARCRON: sync finished, signal quality 5: OK, will use clock
May 10 12:39:26 oolong xntpd[615]: ARCRON: unit 0: sending resync command
May 10 12:41:34 oolong xntpd[615]: ARCRON: sync finished, signal quality 3: OK, will use clock
</pre>

<p><h4>Fudge Factors</h4>

<dl>

<dt><code>time1 <i>time</i></code>
<dd>Specifies the time offset calibration factor, in seconds and
    fraction, with default 0.0.  On a Sun SparcStation 1 running SunOS
    4.1.3_U1, with the receiver in London, a value of 0.020 (20ms)
    seems to be appropriate.

<p><dt><code>time2 <i>time</i></code>
<dd>Not currently used by this driver.

<p><dt><code>stratum <i>number</i></code>
<dd>Specifies the driver stratum, in decimal from 0 to 15, with default
    0.  It is suggested that the clock be fudged to stratum 1 so this
    it is used a backup time source rather than a primary when more
    accurate sources are available.

<p><dt><code>refid <i>string</i></code>
<dd>Specifies the driver reference identifier, an ASCII string from one
    to four characters, with default <code>MSFa</code>.

<p><dt><code>flag1 0 | 1</code>
<dd>Not currently used by this driver.

<p><dt><code>flag2 0 | 1</code>
<dd>Not currently used by this driver.

<p><dt><code>flag3 0 | 1</code>
<dd>If set to 1, better precision is reported (and thus lower dispersion)
    while clock's received signal quality is known to be good.

<p><dt><code>flag4 0 | 1</code>
<dd>If set to 1, a longer-than-normal (8-stage rather than 4-stage)
    median filter is used, to provide some extra smoothing of clock
    output and reduction in jitter, at the cost of extra clock
    overshoot.  Probably not advisable unless the server using this
    clock has other sources it can use to help mitigate the overshoot.

</dl>

<p><h4>Additional Information</h4>

<p><a href="refclock.html"> Reference Clock Drivers</a>
<p><a href="http://www2.exnet.com/NTP/ARC/ARC.html">ARC Rugby MSF Receiver</a> page

<hr><address>Damon Hart-Davis (d@hd.org)</address></body></html>
