PPSCLOCK 1.3 BETA Release

Please send bugs and comments to ppsclock@ee.lbl.gov.

     Note: This version of the release includes a minor change to call
     the kernel routine hardpps() at each positive-going edge of the PPS
     signal. This is used with the precision kernel time modifications
     described elsewhere in the kernel.tar.Z distribution. It currently
     lives on louie.udel.edu in the pub/ntp directory. This code is
     enabled only if the PPS_SYNC symbol is defined. This is normally
     the case when this routine is being compiled with the kernel
     modifications. Do not define this symbol in any other case.

This is SunOS 4 tty STREAMS module that can be used to interface the
one-pulse-per-second output of an accurate external clock (e.g., a GPS
receiver) to NTP.  This module records the value of the Unix clock each
time the PPS pulse happens.  The recorded value can then be read by an
application using a CIOGETEV ioctl.

We have been running this module with xntp3, SunOS 4.1.2 and a Magnavox
MX4200 GPS receiver (a very nice, relatively inexpensive GPS box for use
with NTP) and have been happy with the results. The time from when the
GPS sends the PPS pulse to when this code grabs the Unix time is an
almost constant 15s on a SparcStation 1+ and 9us on a SparcStation 2
(there is conditionally compiled code in the driver that may help if you
want to measure this interval yourself -- see below).

Since the GPS is typically connected via an RS-232 serial port & since
the SparcStation has too few serial ports, this module gets the PPS
signal via the same port used to get the GPS data. We do this by
connecting the GPS PPS output to `Carrier Detect' (RS-232 pin 8) on
serial cable between the GPS & the Sparc. Since `Carrier Detect'
transitions cause a high priority zs `service' interrupt on the Sparc
(which is separate from the zs `data' interrupt used to collect ascii
data from the GPS), this is a cheap way to turn the PPS pulse into a
Sparc interrupt. However, since `Carrier Detect' now has a non-standard
meaning on the clock serial line, *YOU MUST SET "SOFT CARRIER" ON THE
CLOCK SERIAL PORT* (i.e., the port you push this streams module onto).
But note that this module changes the meaning of the `Carrier Detect'
interrupt *only* on the port(s) it's pushed on; other serial ports
continue to work as they always have & the special interpretation of
`Carrier Detect' will go away whenever this steams module is popped
(e.g., if xntpd exits).

In order to use the PPS pulse to correct both the Unix clock offset and
frequency, the time between when the external clock signals PPS and when
this module records the Unix clock should be as small as possible and
should be repeatable.  The totally braindead AT&T STREAMS code makes it
very difficult to achieve these objectives: The allocb/wput overhead
adds about 700us of latency on a SS-1+ and the worthless stream
'scheduler' combined with doing everything at a relatively low interrupt
priority adds about +-400us of jitter.  So, to get a high quality time
stamp, this module uses STREAMS *only* to get the ioctl request. It gets
the PPS signal by avoiding STREAMS code entirely and inserting itself
directly into the hardware interrupt service path for the PPS pulse.  It
does this by being *very* chummy with Sun's `zs' serial driver -- it
basically steals the modem control interrupt from the zs.  So *THIS IS
NOT A GENERIC STREAMS MODULE*. It is designed to work with Sun's zs
driver under SunOS 4. Expect it to break under Solaris-2.  Expect it to
not run on anything but a Sun.

Also note that for our GPS clock, `1 second' seems to occur at the
leading edge of the pulse so that's when we grab the timestamp.  If your
clock indicates PPS on the trailing edge you should change the line:

     if ((s0 & ZSRR0_CD) != 0) {

in ppsclock_intr to:

     if ((s0 & ZSRR0_CD) == 0) {

to grab the time stamp on the trailing edge of the pulse.

One final note about running this code on a Sun Sparcstation 1; we have
found that you need revision 1.3 or higher proms with the Sun 4/60; this
is because 1.2 and earlier roms listen to BOTH the A and B serial ports
while selftesting; if you have something like a GPS receiver talking
once a second at 4800 baud to a serial port, even if it's not the
console serial port, the system will see a "break" and abort booting up.
This makes it impossible to automatically reboot after a crash.

Measuring Performance
---------------------

We have frequently found the need for some non-invasive means to measure
internal system performance numbers such as interrupt latencies.  The
following trick works pretty well: The SparcStation has a front panel
LED that the system turns on at boot time then never changes.  There is
a register (the 'auxio' register) that the kernel can write to change
the state of this LED & it costs almost exactly 1us to turn the LED off
then on again.  So, for example, you can measure the interrupt latency
to when we grab unix time stamp by attaching a scope probe to the LED
(it's easiest to stick the probe into the LED's connector on the
motherboard) then trigger the scope off the appropriate edge of the PPS
pulse from the GPS. If you want to measure the amount of time some
system routine takes, you can turn the LED off before calling the
routine and on afterwards so the pulse width on the scope is a direct
measure of the routine's execution time.  If you want to play with this,
there's code in ppsclock.c (conditional on the define PPSCLOCKLED) that
will toggle the LED in the hardware interrupt handler for the PPS intr.
The time from whichever edge of the PPS pulse you chose to stamp to when
the LED goes off is the interrupt latency & the LED off time is a
measure of the cost to call Sun's routine uniqtime (which reads the Unix
time to 1us accuracy).

By the way, if you try this & think the cost of calling Sun's `uniqtime'
is appalling (42-85us/call -- we thought this was appalling) this
distribution includes a replacement for it, microtime.s, that runs 10
times faster than Sun's code (~3us/call) & gives you a more accurate
time.  (If you install this, it will improve anything that gets high-res
unix kernel time, including the gettimeofday system call that xntp
uses).

INSTALLATION
------------

These are some (sketchy) notes on installation of the SunOS 4 pps clock
streams module.

This directory contains:

README         this file

RELEASE        version of this release

CHANGES        description of differences between releases

magnavox.ps    PostScript schematic (Magnavox rs422/rs232 converter)

b-and-b.ps     PostScript schematic (B&B rs422/rs232 converter)

Makefile       compilation rules
ppstest        ppsclock test program (works with Magnavox MX4200 GPS --
               you can probably use this as sample code illustrating the
               use of this streams module if you have some other kind of
               clock).

sys            SunOS 4 kernel modules

sys/genassym   genassym program for object-only (non-source) sites

KERNEL CONFIGURATION
--------------------

1.   Copy sys/sundev/ppsclock.c to /sys/sundev.

2.   Copy sys/sys/ppsclock.h to /sys/sys (and /usr/include/sys if it is
     separate directory).

3.   If you want to use the fast microtime module, copy

     sys/sun4c/microtime.s to /sys/sun4c.

4.   Use sys/sun/str_conf.c.patch to patch /sys/sun/str_conf.c.
     Alternately, manually install the following lines in the
     appropriate places:

     #include "zs.h"
     [...]
     #if   NZS > 0
     #include <sys/time.h>
     #include "sys/ppsclock.h"
     extern struct streamtab ppsclockinfo;
     #endif
     [...]
     #if   NZS > 0
          { PPSCLOCKSTR,  &ppsclockinfo },
     #endif

5.   Use sun4c/conf/files.patch to patch /sys/sun4c/conf/files.
     Alternately, manually install the following line in the appropriate
     place:

     sundev/ppsclock.c   optional zs device-driver

     In addition, if you want to use the fast microtime module, use
     sun4c/conf/files.microtime.patch to patch /sys/sun4c/conf/files.
     Alternately, manually install the following line in the appropriate
     place:

     sun4c/microtime.s   standard

     If you are using SunOS 4.1.3 and wish to support the sun4m
     architecture, apply the corresponding patches from the sun4m tree.

6.   If you want to use the fast microtime module, and have full SunOS 4
     source, use /sys/os/kern_clock.c.patch to patch
     /sys/os/kern_clock.c. Alternately, edit /sys/os/kern_clock.c and
     bracket the code for uniqtime() with the following two lines:

     #if !defined(sun4c) && !defined(sun4m)
     #endif

     If you DO NOT have full SunOS 4 source but still want to use the
     fast microtime, change the symbol table entry in for _uniqtime to
     _Uniqtime as follows:

     hell 1 % cd /sys/sun4c/OBJ    # or /sys/sun4m
     hell 2 % mv kern_clock.o kern_clock.o.virgin
     hell 2 % cp kern_clock.o.virgin kern_clock.o
     hell 3 % chmod +w kern_clock.o
     hell 5 % strings -o -a kern_clock.o | grep -w _uniqtime
     7892 _uniqtime
     hell 6 % adb -w kern_clock.o
     ?m 0 0xffffffff 0
     0t7892?s
     _dk_ndrive+0x12cc:       _uniqtime
     .?x
     _dk_ndrive+0x12cc:       5f75
     .?w5f55
     _dk_ndrive+0x12cc:       0x5f75    =    0x5f55
     .?s
     _dk_ndrive+0x12cc:       _Uniqtime
     ^D

7.   If you DO NOT have full SunOS 4 source, you are missing the source
     to genassym. Copy the "mini" genassym source from
     genassym/genassym.c to /sys/sun4c (and into /sys/sun4m if you have
     SunOS 4.1.3 and wish to support the sun4m architecture).

     Next, use sun4c/conf/Makefile.src.patch to patch
     /sys/sun4c/conf/Makefile.src (and possibly
     /sys/sun4m/conf/Makefile.src).

     Alternately, manually install the following rules in the prototype
     Makefile(s).

     assym.s: ${MACHINE}/genassym.c
     ${CC} -E ${CPPOPTS} ${MACHINE}/genassym.c > ./a.out.c
     cc ${COPTS} ./a.out.c
     ./a.out >assym.s
     rm -f ./a.out ./a.out.c

8.   Config, build, and boot the new kernel.

9.   If you have an MX4200, build the test program in the ppstest
     directory and run it.  It waits for a message at 4800 baud and then
     prints both the message and the time stamp of the last "carrier
     detect" transition.
