Annotation of embedaddon/ntp/ntpd/refclock_heath.c, revision 1.1.1.1

1.1       misho       1: /*
                      2:  * refclock_heath - clock driver for Heath GC-1000
                      3:  * (but no longer the GC-1001 Model II, which apparently never worked)
                      4:  */
                      5: 
                      6: #ifdef HAVE_CONFIG_H
                      7: # include <config.h>
                      8: #endif
                      9: 
                     10: #if defined(REFCLOCK) && defined(CLOCK_HEATH)
                     11: 
                     12: #include "ntpd.h"
                     13: #include "ntp_io.h"
                     14: #include "ntp_refclock.h"
                     15: #include "ntp_stdlib.h"
                     16: 
                     17: #include <stdio.h>
                     18: #include <ctype.h>
                     19: 
                     20: #ifdef HAVE_SYS_IOCTL_H
                     21: # include <sys/ioctl.h>
                     22: #endif /* not HAVE_SYS_IOCTL_H */
                     23: 
                     24: /*
                     25:  * This driver supports the Heath GC-1000 Most Accurate Clock, with
                     26:  * RS232C Output Accessory. This is a WWV/WWVH receiver somewhat less
                     27:  * robust than other supported receivers. Its claimed accuracy is 100 ms
                     28:  * when actually synchronized to the broadcast signal, but this doesn't
                     29:  * happen even most of the time, due to propagation conditions, ambient
                     30:  * noise sources, etc. When not synchronized, the accuracy is at the
                     31:  * whim of the internal clock oscillator, which can wander into the
                     32:  * sunset without warning. Since the indicated precision is 100 ms,
                     33:  * expect a host synchronized only to this thing to wander to and fro,
                     34:  * occasionally being rudely stepped when the offset exceeds the default
                     35:  * clock_max of 128 ms. 
                     36:  *
                     37:  * There were two GC-1000 versions supported by this driver. The original
                     38:  * GC-1000 with RS-232 output first appeared in 1983, but dissapeared
                     39:  * from the market a few years later. The GC-1001 II with RS-232 output
                     40:  * first appeared circa 1990, but apparently is no longer manufactured.
                     41:  * The two models differ considerably, both in interface and commands.
                     42:  * The GC-1000 has a pseudo-bipolar timecode output triggered by a RTS
                     43:  * transition. The timecode includes both the day of year and time of
                     44:  * day. The GC-1001 II has a true bipolar output and a complement of
                     45:  * single character commands. The timecode includes only the time of
                     46:  * day.
                     47:  *
                     48:  * The GC-1001 II was apparently never tested and, based on a Coverity
                     49:  * scan, apparently never worked [Bug 689].  Related code has been disabled.
                     50:  *
                     51:  * GC-1000
                     52:  *
                     53:  * The internal DIPswitches should be set to operate in MANUAL mode. The
                     54:  * external DIPswitches should be set to GMT and 24-hour format.
                     55:  *
                     56:  * In MANUAL mode the clock responds to a rising edge of the request to
                     57:  * send (RTS) modem control line by sending the timecode. Therefore, it
                     58:  * is necessary that the operating system implement the TIOCMBIC and
                     59:  * TIOCMBIS ioctl system calls and TIOCM_RTS control bit. Present
                     60:  * restrictions require the use of a POSIX-compatible programming
                     61:  * interface, although other interfaces may work as well.
                     62:  *
                     63:  * A simple hardware modification to the clock can be made which
                     64:  * prevents the clock hearing the request to send (RTS) if the HI SPEC
                     65:  * lamp is out. Route the HISPEC signal to the tone decoder board pin
                     66:  * 19, from the display, pin 19. Isolate pin 19 of the decoder board
                     67:  * first, but maintain connection with pin 10. Also isolate pin 38 of
                     68:  * the CPU on the tone board, and use half an added 7400 to gate the
                     69:  * original signal to pin 38 with that from pin 19.
                     70:  *
                     71:  * The clock message consists of 23 ASCII printing characters in the
                     72:  * following format:
                     73:  *
                     74:  * hh:mm:ss.f AM  dd/mm/yr<cr>
                     75:  *
                     76:  *     hh:mm:ss.f = hours, minutes, seconds
                     77:  *     f = deciseconds ('?' when out of spec)
                     78:  *     AM/PM/bb = blank in 24-hour mode
                     79:  *     dd/mm/yr = day, month, year
                     80:  *
                     81:  * The alarm condition is indicated by '?', rather than a digit, at f.
                     82:  * Note that 0?:??:??.? is displayed before synchronization is first
                     83:  * established and hh:mm:ss.? once synchronization is established and
                     84:  * then lost again for about a day.
                     85:  *
                     86:  * GC-1001 II
                     87:  *
                     88:  * Commands consist of a single letter and are case sensitive. When
                     89:  * enterred in lower case, a description of the action performed is
                     90:  * displayed. When enterred in upper case the action is performed.
                     91:  * Following is a summary of descriptions as displayed by the clock:
                     92:  *
                     93:  * The clock responds with a command The 'A' command returns an ASCII
                     94:  * local time string:  HH:MM:SS.T xx<CR>, where
                     95:  *
                     96:  *     HH = hours
                     97:  *     MM = minutes
                     98:  *     SS = seconds
                     99:  *     T = tenths-of-seconds
                    100:  *     xx = 'AM', 'PM', or '  '
                    101:  *     <CR> = carriage return
                    102:  *
                    103:  * The 'D' command returns 24 pairs of bytes containing the variable
                    104:  * divisor value at the end of each of the previous 24 hours. This
                    105:  * allows the timebase trimming process to be observed.  UTC hour 00 is
                    106:  * always returned first. The first byte of each pair is the high byte
                    107:  * of (variable divisor * 16); the second byte is the low byte of
                    108:  * (variable divisor * 16). For example, the byte pair 3C 10 would be
                    109:  * returned for a divisor of 03C1 hex (961 decimal).
                    110:  *
                    111:  * The 'I' command returns:  | TH | TL | ER | DH | DL | U1 | I1 | I2 | ,
                    112:  * where
                    113:  *
                    114:  *     TH = minutes since timebase last trimmed (high byte)
                    115:  *     TL = minutes since timebase last trimmed (low byte)
                    116:  *     ER = last accumulated error in 1.25 ms increments
                    117:  *     DH = high byte of (current variable divisor * 16)
                    118:  *     DL = low byte of (current variable divisor * 16)
                    119:  *     U1 = UT1 offset (/.1 s):  | + | 4 | 2 | 1 | 0 | 0 | 0 | 0 |
                    120:  *     I1 = information byte 1:  | W | C | D | I | U | T | Z | 1 | ,
                    121:  *          where
                    122:  *
                    123:  *             W = set by WWV(H)
                    124:  *             C = CAPTURE LED on
                    125:  *             D = TRIM DN LED on
                    126:  *             I = HI SPEC LED on
                    127:  *             U = TRIM UP LED on
                    128:  *             T = DST switch on
                    129:  *             Z = UTC switch on
                    130:  *             1 = UT1 switch on
                    131:  *
                    132:  *     I2 = information byte 2:  | 8 | 8 | 4 | 2 | 1 | D | d | S | ,
                    133:  *          where
                    134:  *
                    135:  *             8, 8, 4, 2, 1 = TIME ZONE switch settings
                    136:  *             D = DST bit (#55) in last-received frame
                    137:  *             d = DST bit (#2) in last-received frame
                    138:  *             S = clock is in simulation mode
                    139:  *
                    140:  * The 'P' command returns 24 bytes containing the number of frames
                    141:  * received without error during UTC hours 00 through 23, providing an
                    142:  * indication of hourly propagation.  These bytes are updated each hour
                    143:  * to reflect the previous 24 hour period.  UTC hour 00 is always
                    144:  * returned first.
                    145:  *
                    146:  * The 'T' command returns the UTC time:  | HH | MM | SS | T0 | , where
                    147:  *     HH = tens-of-hours and hours (packed BCD)
                    148:  *     MM = tens-of-minutes and minutes (packed BCD)
                    149:  *     SS = tens-of-seconds and seconds (packed BCD)
                    150:  *     T = tenths-of-seconds (BCD)
                    151:  *
                    152:  * Fudge Factors
                    153:  *
                    154:  * A fudge time1 value of .04 s appears to center the clock offset
                    155:  * residuals. The fudge time2 parameter is the local time offset east of
                    156:  * Greenwich, which depends on DST. Sorry about that, but the clock
                    157:  * gives no hint on what the DIPswitches say.
                    158:  */
                    159: 
                    160: /*
                    161:  * Interface definitions
                    162:  */
                    163: #define        DEVICE          "/dev/heath%d" /* device name and unit */
                    164: #define        PRECISION       (-4)    /* precision assumed (about 100 ms) */
                    165: #define        REFID           "WWV\0" /* reference ID */
                    166: #define        DESCRIPTION     "Heath GC-1000 Most Accurate Clock" /* WRU */
                    167: 
                    168: #define LENHEATH1      23      /* min timecode length */
                    169: #if 0  /* BUG 689 */
                    170: #define LENHEATH2      13      /* min timecode length */
                    171: #endif
                    172: 
                    173: /*
                    174:  * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
                    175:  * leap.
                    176:  */
                    177: static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                    178: static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                    179: 
                    180: /*
                    181:  * Baud rate table. The GC-1000 supports 1200, 2400 and 4800; the
                    182:  * GC-1001 II supports only 9600.
                    183:  */
                    184: static int speed[] = {B1200, B2400, B4800, B9600};
                    185: 
                    186: /*
                    187:  * Function prototypes
                    188:  */
                    189: static int     heath_start     (int, struct peer *);
                    190: static void    heath_shutdown  (int, struct peer *);
                    191: static void    heath_receive   (struct recvbuf *);
                    192: static void    heath_poll      (int, struct peer *);
                    193: 
                    194: /*
                    195:  * Transfer vector
                    196:  */
                    197: struct refclock refclock_heath = {
                    198:        heath_start,            /* start up driver */
                    199:        heath_shutdown,         /* shut down driver */
                    200:        heath_poll,             /* transmit poll message */
                    201:        noentry,                /* not used (old heath_control) */
                    202:        noentry,                /* initialize driver */
                    203:        noentry,                /* not used (old heath_buginfo) */
                    204:        NOFLAGS                 /* not used */
                    205: };
                    206: 
                    207: 
                    208: /*
                    209:  * heath_start - open the devices and initialize data for processing
                    210:  */
                    211: static int
                    212: heath_start(
                    213:        int unit,
                    214:        struct peer *peer
                    215:        )
                    216: {
                    217:        struct refclockproc *pp;
                    218:        int fd;
                    219:        char device[20];
                    220: 
                    221:        /*
                    222:         * Open serial port
                    223:         */
                    224:        snprintf(device, sizeof(device), DEVICE, unit);
                    225:        if (!(fd = refclock_open(device, speed[peer->ttl & 0x3],
                    226:            LDISC_REMOTE)))
                    227:                return (0);
                    228:        pp = peer->procptr;
                    229:        pp->io.clock_recv = heath_receive;
                    230:        pp->io.srcclock = (caddr_t)peer;
                    231:        pp->io.datalen = 0;
                    232:        pp->io.fd = fd;
                    233:        if (!io_addclock(&pp->io)) {
                    234:                close(fd);
                    235:                pp->io.fd = -1;
                    236:                return (0);
                    237:        }
                    238: 
                    239:        /*
                    240:         * Initialize miscellaneous variables
                    241:         */
                    242:        peer->precision = PRECISION;
                    243:        peer->burst = NSTAGE;
                    244:        pp->clockdesc = DESCRIPTION;
                    245:        memcpy(&pp->refid, REFID, 4);
                    246:        return (1);
                    247: }
                    248: 
                    249: 
                    250: /*
                    251:  * heath_shutdown - shut down the clock
                    252:  */
                    253: static void
                    254: heath_shutdown(
                    255:        int unit,
                    256:        struct peer *peer
                    257:        )
                    258: {
                    259:        struct refclockproc *pp;
                    260: 
                    261:        pp = peer->procptr;
                    262:        if (-1 != pp->io.fd)
                    263:                io_closeclock(&pp->io);
                    264: }
                    265: 
                    266: 
                    267: /*
                    268:  * heath_receive - receive data from the serial interface
                    269:  */
                    270: static void
                    271: heath_receive(
                    272:        struct recvbuf *rbufp
                    273:        )
                    274: {
                    275:        struct refclockproc *pp;
                    276:        struct peer *peer;
                    277:        l_fp trtmp;
                    278:        int month, day;
                    279:        int i;
                    280:        char dsec, a[5];
                    281: 
                    282:        /*
                    283:         * Initialize pointers and read the timecode and timestamp
                    284:         */
                    285:        peer = (struct peer *)rbufp->recv_srcclock;
                    286:        pp = peer->procptr;
                    287:        pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX,
                    288:            &trtmp);
                    289: 
                    290:        /*
                    291:         * We get down to business, check the timecode format and decode
                    292:         * its contents. If the timecode has invalid length or is not in
                    293:         * proper format, we declare bad format and exit.
                    294:         */
                    295:        switch (pp->lencode) {
                    296: 
                    297:        /*
                    298:         * GC-1000 timecode format: "hh:mm:ss.f AM  mm/dd/yy"
                    299:         * GC-1001 II timecode format: "hh:mm:ss.f   "
                    300:         */
                    301:        case LENHEATH1:
                    302:                if (sscanf(pp->a_lastcode,
                    303:                    "%2d:%2d:%2d.%c%5c%2d/%2d/%2d", &pp->hour,
                    304:                    &pp->minute, &pp->second, &dsec, a, &month, &day,
                    305:                    &pp->year) != 8) {
                    306:                        refclock_report(peer, CEVNT_BADREPLY);
                    307:                        return;
                    308:                }
                    309:                break;
                    310: 
                    311: #if 0  /* BUG 689 */
                    312:        /*
                    313:         * GC-1001 II timecode format: "hh:mm:ss.f   "
                    314:         */
                    315:        case LENHEATH2:
                    316:                if (sscanf(pp->a_lastcode, "%2d:%2d:%2d.%c", &pp->hour,
                    317:                    &pp->minute, &pp->second, &dsec) != 4) {
                    318:                        refclock_report(peer, CEVNT_BADREPLY);
                    319:                        return;
                    320:                } else {
                    321:                        struct tm *tm_time_p;
                    322:                        time_t     now;
                    323: 
                    324:                        time(&now);     /* we should grab 'now' earlier */
                    325:                        tm_time_p = gmtime(&now);
                    326:                        /*
                    327:                         * There is a window of time around midnight
                    328:                         * where this will Do The Wrong Thing.
                    329:                         */
                    330:                        if (tm_time_p) {
                    331:                                month = tm_time_p->tm_mon + 1;
                    332:                                day = tm_time_p->tm_mday;
                    333:                        } else {
                    334:                                refclock_report(peer, CEVNT_FAULT);
                    335:                                return;
                    336:                        }
                    337:                }
                    338:                break;
                    339: #endif
                    340: 
                    341:        default:
                    342:                refclock_report(peer, CEVNT_BADREPLY);
                    343:                return;
                    344:        }
                    345: 
                    346:        /*
                    347:         * We determine the day of the year from the DIPswitches. This
                    348:         * should be fixed, since somebody might forget to set them.
                    349:         * Someday this hazard will be fixed by a fiendish scheme that
                    350:         * looks at the timecode and year the radio shows, then computes
                    351:         * the residue of the seconds mod the seconds in a leap cycle.
                    352:         * If in the third year of that cycle and the third and later
                    353:         * months of that year, add one to the day. Then, correct the
                    354:         * timecode accordingly. Icky pooh. This bit of nonsense could
                    355:         * be avoided if the engineers had been required to write a
                    356:         * device driver before finalizing the timecode format.
                    357:         */
                    358:        if (month < 1 || month > 12 || day < 1) {
                    359:                refclock_report(peer, CEVNT_BADTIME);
                    360:                return;
                    361:        }
                    362:        if (pp->year % 4) {
                    363:                if (day > day1tab[month - 1]) {
                    364:                        refclock_report(peer, CEVNT_BADTIME);
                    365:                        return;
                    366:                }
                    367:                for (i = 0; i < month - 1; i++)
                    368:                    day += day1tab[i];
                    369:        } else {
                    370:                if (day > day2tab[month - 1]) {
                    371:                        refclock_report(peer, CEVNT_BADTIME);
                    372:                        return;
                    373:                }
                    374:                for (i = 0; i < month - 1; i++)
                    375:                    day += day2tab[i];
                    376:        }
                    377:        pp->day = day;
                    378: 
                    379:        /*
                    380:         * Determine synchronization and last update
                    381:         */
                    382:        if (!isdigit((int)dsec))
                    383:                pp->leap = LEAP_NOTINSYNC;
                    384:        else {
                    385:                pp->nsec = (dsec - '0') * 100000000;
                    386:                pp->leap = LEAP_NOWARNING;
                    387:        }
                    388:        if (!refclock_process(pp))
                    389:                refclock_report(peer, CEVNT_BADTIME);
                    390: }
                    391: 
                    392: 
                    393: /*
                    394:  * heath_poll - called by the transmit procedure
                    395:  */
                    396: static void
                    397: heath_poll(
                    398:        int unit,
                    399:        struct peer *peer
                    400:        )
                    401: {
                    402:        struct refclockproc *pp;
                    403:        int bits = TIOCM_RTS;
                    404: 
                    405:        /*
                    406:         * At each poll we check for timeout and toggle the RTS modem
                    407:         * control line, then take a timestamp. Presumably, this is the
                    408:         * event the radio captures to generate the timecode.
                    409:         * Apparently, the radio takes about a second to make up its
                    410:         * mind to send a timecode, so the receive timestamp is
                    411:         * worthless.
                    412:         */
                    413:        pp = peer->procptr;
                    414: 
                    415:        /*
                    416:         * We toggle the RTS modem control lead (GC-1000) and sent a T
                    417:         * (GC-1001 II) to kick a timecode loose from the radio. This
                    418:         * code works only for POSIX and SYSV interfaces. With bsd you
                    419:         * are on your own. We take a timestamp between the up and down
                    420:         * edges to lengthen the pulse, which should be about 50 usec on
                    421:         * a Sun IPC. With hotshot CPUs, the pulse might get too short.
                    422:         * Later.
                    423:         *
                    424:         * Bug 689: Even though we no longer support the GC-1001 II,
                    425:         * I'm leaving the 'T' write in for timing purposes.
                    426:         */
                    427:        if (ioctl(pp->io.fd, TIOCMBIC, (char *)&bits) < 0)
                    428:                refclock_report(peer, CEVNT_FAULT);
                    429:        get_systime(&pp->lastrec);
                    430:        if (write(pp->io.fd, "T", 1) != 1)
                    431:                refclock_report(peer, CEVNT_FAULT);
                    432:        ioctl(pp->io.fd, TIOCMBIS, (char *)&bits);
                    433:        if (peer->burst > 0)
                    434:                return;
                    435:        if (pp->coderecv == pp->codeproc) {
                    436:                refclock_report(peer, CEVNT_TIMEOUT);
                    437:                return;
                    438:        }
                    439:        pp->lastref = pp->lastrec;
                    440:        refclock_receive(peer);
                    441:        record_clock_stats(&peer->srcadr, pp->a_lastcode);
                    442: #ifdef DEBUG
                    443:        if (debug)
                    444:            printf("heath: timecode %d %s\n", pp->lencode,
                    445:                   pp->a_lastcode);
                    446: #endif
                    447:        peer->burst = MAXSTAGE;
                    448:        pp->polls++;
                    449: }
                    450: 
                    451: #else
                    452: int refclock_heath_bs;
                    453: #endif /* REFCLOCK */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>