Annotation of embedaddon/ntp/ntpd/refclock_heath.c, revision 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>