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