Annotation of sys/kern/tty_nmea.c, Revision 1.1.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