[BACK]Return to tty_nmea.c CVS log [TXT][DIR] Up to [local] / sys / kern

Annotation of sys/kern/tty_nmea.c, Revision 1.1

1.1     ! nbrk        1: /*     $OpenBSD: tty_nmea.c,v 1.21 2007/03/22 16:55:31 deraadt Exp $ */
        !             2:
        !             3: /*
        !             4:  * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org>
        !             5:  *
        !             6:  * Permission to use, copy, modify, and distribute this software for any
        !             7:  * purpose with or without fee is hereby granted, provided that the above
        !             8:  * copyright notice and this permission notice appear in all copies.
        !             9:  *
        !            10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
        !            11:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
        !            12:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
        !            13:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
        !            14:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
        !            15:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
        !            16:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
        !            17:  */
        !            18:
        !            19: /* A tty line discipline to decode NMEA 0183 data to get the time. */
        !            20:
        !            21: #include <sys/param.h>
        !            22: #include <sys/systm.h>
        !            23: #include <sys/queue.h>
        !            24: #include <sys/proc.h>
        !            25: #include <sys/malloc.h>
        !            26: #include <sys/sensors.h>
        !            27: #include <sys/tty.h>
        !            28: #include <sys/conf.h>
        !            29: #include <sys/time.h>
        !            30:
        !            31: #ifdef NMEA_DEBUG
        !            32: #define DPRINTFN(n, x) do { if (nmeadebug > (n)) printf x; } while (0)
        !            33: int nmeadebug = 0;
        !            34: #else
        !            35: #define DPRINTFN(n, x)
        !            36: #endif
        !            37: #define DPRINTF(x)     DPRINTFN(0, x)
        !            38:
        !            39: int    nmeaopen(dev_t, struct tty *);
        !            40: int    nmeaclose(struct tty *, int);
        !            41: int    nmeainput(int, struct tty *);
        !            42: void   nmeaattach(int);
        !            43:
        !            44: #define NMEAMAX        82
        !            45: #define MAXFLDS        32
        !            46:
        !            47: int nmea_count;        /* this is wrong, it should really be a SLIST */
        !            48:
        !            49: struct nmea {
        !            50:        char                    cbuf[NMEAMAX];  /* receive buffer */
        !            51:        struct ksensor          time;           /* the timedelta sensor */
        !            52:        struct ksensordev       timedev;
        !            53:        struct timespec         ts;             /* current timestamp */
        !            54:        struct timespec         lts;            /* timestamp of last '$' seen */
        !            55:        int64_t                 gap;            /* gap between two sentences */
        !            56: #ifdef NMEA_DEBUG
        !            57:        int                     gapno;
        !            58: #endif
        !            59:        int64_t                 last;           /* last time rcvd */
        !            60:        int                     sync;           /* if 1, waiting for '$' */
        !            61:        int                     pos;            /* positon in rcv buffer */
        !            62:        int                     no_pps;         /* no PPS although requested */
        !            63:        char                    mode;           /* GPS mode */
        !            64: };
        !            65:
        !            66: /* NMEA decoding */
        !            67: void   nmea_scan(struct nmea *, struct tty *);
        !            68: void   nmea_gprmc(struct nmea *, struct tty *, char *fld[], int fldcnt);
        !            69:
        !            70: /* date and time conversion */
        !            71: int    nmea_date_to_nano(char *s, int64_t *nano);
        !            72: int    nmea_time_to_nano(char *s, int64_t *nano);
        !            73:
        !            74: void
        !            75: nmeaattach(int dummy)
        !            76: {
        !            77: }
        !            78:
        !            79: int
        !            80: nmeaopen(dev_t dev, struct tty *tp)
        !            81: {
        !            82:        struct proc *p = curproc;
        !            83:        struct nmea *np;
        !            84:        int error;
        !            85:
        !            86:        if (tp->t_line == NMEADISC)
        !            87:                return ENODEV;
        !            88:        if ((error = suser(p, 0)) != 0)
        !            89:                return error;
        !            90:        np = malloc(sizeof(struct nmea), M_DEVBUF, M_WAITOK);
        !            91:        bzero(np, sizeof(*np));
        !            92:        snprintf(np->timedev.xname, sizeof(np->timedev.xname), "nmea%d",
        !            93:            nmea_count++);
        !            94:        np->time.status = SENSOR_S_UNKNOWN;
        !            95:        np->time.type = SENSOR_TIMEDELTA;
        !            96:        np->time.flags = SENSOR_FINVALID;
        !            97:        sensor_attach(&np->timedev, &np->time);
        !            98:        np->sync = 1;
        !            99:        tp->t_sc = (caddr_t)np;
        !           100:
        !           101:        error = linesw[TTYDISC].l_open(dev, tp);
        !           102:        if (error) {
        !           103:                free(np, M_DEVBUF);
        !           104:                tp->t_sc = NULL;
        !           105:        } else
        !           106:                sensordev_install(&np->timedev);
        !           107:        return error;
        !           108: }
        !           109:
        !           110: int
        !           111: nmeaclose(struct tty *tp, int flags)
        !           112: {
        !           113:        struct nmea *np = (struct nmea *)tp->t_sc;
        !           114:
        !           115:        tp->t_line = TTYDISC;   /* switch back to termios */
        !           116:        sensordev_deinstall(&np->timedev);
        !           117:        free(np, M_DEVBUF);
        !           118:        tp->t_sc = NULL;
        !           119:        nmea_count--;
        !           120:        return linesw[TTYDISC].l_close(tp, flags);
        !           121: }
        !           122:
        !           123: /* collect NMEA sentence from tty */
        !           124: int
        !           125: nmeainput(int c, struct tty *tp)
        !           126: {
        !           127:        struct nmea *np = (struct nmea *)tp->t_sc;
        !           128:        struct timespec ts;
        !           129:        int64_t gap;
        !           130:        long tmin, tmax;
        !           131:
        !           132:        switch (c) {
        !           133:        case '$':
        !           134:                nanotime(&ts);
        !           135:                np->pos = np->sync = 0;
        !           136:                gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) -
        !           137:                    (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec);
        !           138:
        !           139:                np->lts.tv_sec = ts.tv_sec;
        !           140:                np->lts.tv_nsec = ts.tv_nsec;
        !           141:
        !           142:                if (gap <= np->gap)
        !           143:                        break;
        !           144:
        !           145:                np->ts.tv_sec = ts.tv_sec;
        !           146:                np->ts.tv_nsec = ts.tv_nsec;
        !           147:
        !           148: #ifdef NMEA_DEBUG
        !           149:                if (nmeadebug > 0) {
        !           150:                        linesw[TTYDISC].l_rint('[', tp);
        !           151:                        linesw[TTYDISC].l_rint('0' + np->gapno++, tp);
        !           152:                        linesw[TTYDISC].l_rint(']', tp);
        !           153:                }
        !           154: #endif
        !           155:                np->gap = gap;
        !           156:
        !           157:                /*
        !           158:                 * If a tty timestamp is available, make sure its value is
        !           159:                 * reasonable by comparing against the timestamp just taken.
        !           160:                 * If they differ by more than 2 seconds, assume no PPS signal
        !           161:                 * is present, note the fact, and keep using the timestamp
        !           162:                 * value.  When this happens, the sensor state is set to
        !           163:                 * CRITICAL later when the GPRMC sentence is decoded.
        !           164:                 */
        !           165:                if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
        !           166:                    TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
        !           167:                        tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
        !           168:                        tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
        !           169:                        if (tmax - tmin > 1)
        !           170:                                np->no_pps = 1;
        !           171:                        else {
        !           172:                                np->ts.tv_sec = tp->t_tv.tv_sec;
        !           173:                                np->ts.tv_nsec = tp->t_tv.tv_usec *
        !           174:                                    1000L;
        !           175:                                np->no_pps = 0;
        !           176:                        }
        !           177:                }
        !           178:                break;
        !           179:        case '\r':
        !           180:        case '\n':
        !           181:                if (!np->sync) {
        !           182:                        np->cbuf[np->pos] = '\0';
        !           183:                        nmea_scan(np, tp);
        !           184:                        np->sync = 1;
        !           185:                }
        !           186:                break;
        !           187:        default:
        !           188:                if (!np->sync && np->pos < (NMEAMAX - 1))
        !           189:                        np->cbuf[np->pos++] = c;
        !           190:                break;
        !           191:        }
        !           192:        /* pass data to termios */
        !           193:        return linesw[TTYDISC].l_rint(c, tp);
        !           194: }
        !           195:
        !           196: /* Scan the NMEA sentence just received */
        !           197: void
        !           198: nmea_scan(struct nmea *np, struct tty *tp)
        !           199: {
        !           200:        int fldcnt = 0, cksum = 0, msgcksum, n;
        !           201:        char *fld[MAXFLDS], *cs;
        !           202:
        !           203:        /* split into fields and calculate the checksum */
        !           204:        fld[fldcnt++] = &np->cbuf[0];   /* message type */
        !           205:        for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
        !           206:                switch (np->cbuf[n]) {
        !           207:                case '*':
        !           208:                        np->cbuf[n] = '\0';
        !           209:                        cs = &np->cbuf[n + 1];
        !           210:                        break;
        !           211:                case ',':
        !           212:                        if (fldcnt < MAXFLDS) {
        !           213:                                cksum ^= np->cbuf[n];
        !           214:                                np->cbuf[n] = '\0';
        !           215:                                fld[fldcnt++] = &np->cbuf[n + 1];
        !           216:                        } else {
        !           217:                                DPRINTF(("nr of fields in %s sentence exceeds "
        !           218:                                    "maximum of %d\n", fld[0], MAXFLDS));
        !           219:                                return;
        !           220:                        }
        !           221:                        break;
        !           222:                default:
        !           223:                        cksum ^= np->cbuf[n];
        !           224:                }
        !           225:        }
        !           226:
        !           227:        /* if we have a checksum, verify it */
        !           228:        if (cs != NULL) {
        !           229:                msgcksum = 0;
        !           230:                while (*cs) {
        !           231:                        if ((*cs >= '0' && *cs <= '9') ||
        !           232:                            (*cs >= 'A' && *cs <= 'F')) {
        !           233:                                if (msgcksum)
        !           234:                                        msgcksum <<= 4;
        !           235:                                if (*cs >= '0' && *cs<= '9')
        !           236:                                        msgcksum += *cs - '0';
        !           237:                                else if (*cs >= 'A' && *cs <= 'F')
        !           238:                                        msgcksum += 10 + *cs - 'A';
        !           239:                                cs++;
        !           240:                        } else {
        !           241:                                DPRINTF(("bad char %c in checksum\n", *cs));
        !           242:                                return;
        !           243:                        }
        !           244:                }
        !           245:                if (msgcksum != cksum) {
        !           246:                        DPRINTF(("checksum mismatch\n"));
        !           247:                        return;
        !           248:                }
        !           249:        }
        !           250:
        !           251:        /* check message type */
        !           252:        if (!strcmp(fld[0], "GPRMC"))
        !           253:                nmea_gprmc(np, tp, fld, fldcnt);
        !           254: }
        !           255:
        !           256: /* Decode the recommended minimum specific GPS/TRANSIT data */
        !           257: void
        !           258: nmea_gprmc(struct nmea *np, struct tty *tp, char *fld[], int fldcnt)
        !           259: {
        !           260:        int64_t date_nano, time_nano, nmea_now;
        !           261:
        !           262:        if (fldcnt != 12 && fldcnt != 13) {
        !           263:                DPRINTF(("gprmc: field count mismatch, %d\n", fldcnt));
        !           264:                return;
        !           265:        }
        !           266:        if (nmea_time_to_nano(fld[1], &time_nano)) {
        !           267:                DPRINTF(("gprmc: illegal time, %s\n", fld[1]));
        !           268:                return;
        !           269:        }
        !           270:        if (nmea_date_to_nano(fld[9], &date_nano)) {
        !           271:                DPRINTF(("gprmc: illegal date, %s\n", fld[9]));
        !           272:                return;
        !           273:        }
        !           274:        nmea_now = date_nano + time_nano;
        !           275:        if (nmea_now <= np->last) {
        !           276:                DPRINTF(("gprmc: time not monotonically increasing\n"));
        !           277:                return;
        !           278:        }
        !           279:        np->last = nmea_now;
        !           280:        np->gap = 0LL;
        !           281: #ifdef NMEA_DEBUG
        !           282:        np->gapno = 0;
        !           283:        if (nmeadebug > 0) {
        !           284:                linesw[TTYDISC].l_rint('[', tp);
        !           285:                linesw[TTYDISC].l_rint('C', tp);
        !           286:                linesw[TTYDISC].l_rint(']', tp);
        !           287:        }
        !           288: #endif
        !           289:
        !           290:        np->time.value = np->ts.tv_sec * 1000000000LL +
        !           291:            np->ts.tv_nsec - nmea_now;
        !           292:        np->time.tv.tv_sec = np->ts.tv_sec;
        !           293:        np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
        !           294:        if (np->time.status == SENSOR_S_UNKNOWN) {
        !           295:                np->time.status = SENSOR_S_OK;
        !           296:                np->time.flags &= ~SENSOR_FINVALID;
        !           297:                if (fldcnt != 13)
        !           298:                        strlcpy(np->time.desc, "GPS", sizeof(np->time.desc));
        !           299:        }
        !           300:        if (fldcnt == 13 && *fld[12] != np->mode) {
        !           301:                np->mode = *fld[12];
        !           302:                switch (np->mode) {
        !           303:                case 'S':
        !           304:                        strlcpy(np->time.desc, "GPS simulated",
        !           305:                            sizeof(np->time.desc));
        !           306:                        break;
        !           307:                case 'E':
        !           308:                        strlcpy(np->time.desc, "GPS estimated",
        !           309:                            sizeof(np->time.desc));
        !           310:                        break;
        !           311:                case 'A':
        !           312:                        strlcpy(np->time.desc, "GPS autonomous",
        !           313:                            sizeof(np->time.desc));
        !           314:                        break;
        !           315:                case 'D':
        !           316:                        strlcpy(np->time.desc, "GPS differential",
        !           317:                            sizeof(np->time.desc));
        !           318:                        break;
        !           319:                case 'N':
        !           320:                        strlcpy(np->time.desc, "GPS not valid",
        !           321:                            sizeof(np->time.desc));
        !           322:                        break;
        !           323:                default:
        !           324:                        strlcpy(np->time.desc, "GPS unknown",
        !           325:                            sizeof(np->time.desc));
        !           326:                        DPRINTF(("gprmc: unknown mode '%c'\n", np->mode));
        !           327:                }
        !           328:        }
        !           329:        switch (*fld[2]) {
        !           330:        case 'A':
        !           331:                np->time.status = SENSOR_S_OK;
        !           332:                break;
        !           333:        case 'V':
        !           334:                np->time.status = SENSOR_S_WARN;
        !           335:                break;
        !           336:        default:
        !           337:                DPRINTF(("gprmc: unknown warning indication\n"));
        !           338:        }
        !           339:
        !           340:        /*
        !           341:         * If tty timestamping is requested, but not PPS signal is present, set
        !           342:         * the sensor state to CRITICAL.
        !           343:         */
        !           344:        if (np->no_pps)
        !           345:                np->time.status = SENSOR_S_CRIT;
        !           346: }
        !           347:
        !           348: /*
        !           349:  * Convert a NMEA 0183 formatted date string to seconds since the epoch.
        !           350:  * The string must be of the form DDMMYY.
        !           351:  * Return 0 on success, -1 if illegal characters are encountered.
        !           352:  */
        !           353: int
        !           354: nmea_date_to_nano(char *s, int64_t *nano)
        !           355: {
        !           356:        struct clock_ymdhms ymd;
        !           357:        time_t secs;
        !           358:        char *p;
        !           359:        int n;
        !           360:
        !           361:        /* make sure the input contains only numbers and is six digits long */
        !           362:        for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++)
        !           363:                ;
        !           364:        if (n != 6 || (*p != '\0'))
        !           365:                return -1;
        !           366:
        !           367:        ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0');
        !           368:        ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0');
        !           369:        ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0');
        !           370:        ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0;
        !           371:
        !           372:        secs = clock_ymdhms_to_secs(&ymd);
        !           373:        *nano = secs * 1000000000LL;
        !           374:        return 0;
        !           375: }
        !           376:
        !           377: /*
        !           378:  * Convert NMEA 0183 formatted time string to nanoseconds since midnight.
        !           379:  * The string must be of the form HHMMSS[.[sss]] (e.g. 143724 or 143723.615).
        !           380:  * Return 0 on success, -1 if illegal characters are encountered.
        !           381:  */
        !           382: int
        !           383: nmea_time_to_nano(char *s, int64_t *nano)
        !           384: {
        !           385:        long fac = 36000L, div = 6L, secs = 0L, frac = 0L;
        !           386:        char ul = '2';
        !           387:        int n;
        !           388:
        !           389:        for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) {
        !           390:                secs += (*s - '0') * fac;
        !           391:                div = 16 - div;
        !           392:                fac /= div;
        !           393:                switch (n) {
        !           394:                case 0:
        !           395:                        if (*s <= '1')
        !           396:                                ul = '9';
        !           397:                        else
        !           398:                                ul = '3';
        !           399:                        break;
        !           400:                case 1:
        !           401:                case 3:
        !           402:                        ul = '5';
        !           403:                        break;
        !           404:                case 2:
        !           405:                case 4:
        !           406:                        ul = '9';
        !           407:                        break;
        !           408:                }
        !           409:        }
        !           410:        if (fac)
        !           411:                return -1;
        !           412:
        !           413:        /* Handle the fractions of a second, up to a maximum of 6 digits. */
        !           414:        div = 1L;
        !           415:        if (*s == '.') {
        !           416:                for (++s; div < 1000000 && *s && *s >= '0' && *s <= '9'; s++) {
        !           417:                        frac *= 10;
        !           418:                        frac += (*s - '0');
        !           419:                        div *= 10;
        !           420:                }
        !           421:        }
        !           422:
        !           423:        if (*s != '\0')
        !           424:                return -1;
        !           425:
        !           426:        *nano = secs * 1000000000LL + (int64_t)frac * (1000000000 / div);
        !           427:        return 0;
        !           428: }

CVSweb