summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorMarc Balmer <mbalmer@cvs.openbsd.org>2006-06-01 20:10:29 +0000
committerMarc Balmer <mbalmer@cvs.openbsd.org>2006-06-01 20:10:29 +0000
commit2f9088c66c46330f1e7e62bfa1a371ea7173c663 (patch)
treebebb5800f68000d396ad58f871fe416b8b6d2409 /sys
parent3867065e49643137d0ce04043776039654c3ae36 (diff)
Add basic NMEA0183 support as a tty line discipline. The line discipline
decodes NMEA messages completely transparent to userland applications, i.e. userland can still use the NMEA stream and talk to the device. If data is received, a timedelta sensor suitable for ntpd is created. The timestamp is not very precise at the moment, use of this is experimental at best. "get it in or we see how well the swiss people can swim" deraadt@
Diffstat (limited to 'sys')
-rw-r--r--sys/conf/files6
-rw-r--r--sys/kern/tty_conf.c17
-rw-r--r--sys/kern/tty_nmea.c405
-rw-r--r--sys/sys/ttycom.h3
4 files changed, 428 insertions, 3 deletions
diff --git a/sys/conf/files b/sys/conf/files
index 1ce78a06430..07e885ed356 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1,4 +1,4 @@
-# $OpenBSD: files,v 1.374 2006/05/28 17:21:14 uwe Exp $
+# $OpenBSD: files,v 1.375 2006/06/01 20:10:28 mbalmer Exp $
# $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
@@ -935,3 +935,7 @@ file net/pfkey.c key | ipsec | tcp_signature
file net/pfkeyv2.c key | ipsec | tcp_signature
file net/pfkeyv2_parsemessage.c key | ipsec | tcp_signature
file net/pfkeyv2_convert.c key | ipsec | tcp_signature
+
+# NMEA0183 support
+pseudo-device nmea: tty
+file kern/tty_nmea.c nmea needs-flag
diff --git a/sys/kern/tty_conf.c b/sys/kern/tty_conf.c
index e5ab5c36215..5ddcc090f39 100644
--- a/sys/kern/tty_conf.c
+++ b/sys/kern/tty_conf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tty_conf.c,v 1.9 2005/12/21 12:43:49 jsg Exp $ */
+/* $OpenBSD: tty_conf.c,v 1.10 2006/06/01 20:10:28 mbalmer Exp $ */
/* $NetBSD: tty_conf.c,v 1.18 1996/05/19 17:17:55 jonathan Exp $ */
/*-
@@ -94,6 +94,13 @@ int stripinput(int c, struct tty *tp);
int stripstart(struct tty *tp);
#endif
+#include "nmea.h"
+#if NNMEA > 0
+int nmeaopen(dev_t, struct tty *);
+int nmeaclose(struct tty *, int);
+int nmeainput(int, struct tty *);
+#endif
+
struct linesw linesw[] =
{
{ ttyopen, ttylclose, ttread, ttwrite, nullioctl,
@@ -141,6 +148,14 @@ struct linesw linesw[] =
{ ttynodisc, ttyerrclose, ttyerrio, ttyerrio, nullioctl,
ttyerrinput, ttyerrstart, nullmodem },
#endif
+
+#if NNMEA > 0
+ { nmeaopen, nmeaclose, ttread, ttwrite, nullioctl,
+ nmeainput, ttstart, ttymodem }, /* 7- NMEADISC */
+#else
+ { ttynodisc, ttyerrclose, ttyerrio, ttyerrio, nullioctl,
+ ttyerrinput, ttyerrstart, nullmodem },
+#endif
};
int nlinesw = sizeof (linesw) / sizeof (linesw[0]);
diff --git a/sys/kern/tty_nmea.c b/sys/kern/tty_nmea.c
new file mode 100644
index 00000000000..a6215bf3b31
--- /dev/null
+++ b/sys/kern/tty_nmea.c
@@ -0,0 +1,405 @@
+/* $OpenBSD $*/
+
+/*
+ * Copyright (c) 2006 Marc Balmer <mbalmer@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* line discipline to decode NMEA0183 data */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/queue.h>
+#include <sys/malloc.h>
+#include <sys/sensors.h>
+#include <sys/tty.h>
+#include <sys/conf.h>
+
+#include <dev/clock_subr.h> /* clock_subr not avail on all arches */
+
+#ifdef NMEA_DEBUG
+#define DPRINTFN(n, x) do { if (nmeadebug > (n)) printf x; } while (0)
+int nmeadebug = 0;
+#else
+#define DPRINTFN(n, x)
+#endif
+#define DPRINTF(x) DPRINTFN(0, x)
+
+int nmeaopen(dev_t, struct tty *);
+int nmeaclose(struct tty *, int);
+int nmeainput(int, struct tty *);
+void nmeaattach(int);
+
+#define NMEAMAX 82
+#define MAXFLDS 12
+
+/* NMEA talker identifiers */
+
+#define TI_UNK 0
+#define TI_GPS 1
+#define TI_LORC 2
+
+/* NMEA message types */
+
+#define MSG_RMC 5393731 /* recommended minimum sentence C */
+#define MSG_GGA 4671297
+#define MSG_GSA 4674369 /* satellites active */
+#define MSG_GSV 4674390 /* satellites in view */
+#define MSG_VTG 5657671 /* velocity, direction, speed */
+
+int nmea_count = 0;
+time_t nmea_last = 0;
+
+struct nmea {
+ char cbuf[NMEAMAX];
+ struct sensor time;
+ struct timeval tv; /* soft timestamp */
+ struct timespec ts;
+ int state; /* state we're in */
+ int pos;
+ int fpos[MAXFLDS];
+ int flds; /* expect nr of fields */
+ int fldcnt; /* actual count of fields */
+ int cksum; /* calculated checksum */
+ int msgcksum; /* received cksum */
+ int ti; /* talker identifier */
+ int msg; /* NMEA msg type */
+};
+
+/* NMEA protocol state machine */
+
+void nmea_hdlr(struct nmea *, int c);
+
+/* NMEA decoding */
+
+void nmea_decode(struct nmea *);
+void nmea_rmc(struct nmea *);
+
+/* helper functions */
+
+int nmea_atoi(char *, int *);
+void nmea_bufadd(struct nmea *, int);
+
+enum states {
+ S_SYNC = 0,
+ S_TI_1,
+ S_TI_2,
+ S_MSG,
+ S_DATA,
+ S_CKSUM
+};
+
+int
+nmeaopen(dev_t dev, struct tty *tp)
+{
+ struct nmea *np;
+
+ if (tp->t_line != NMEADISC) {
+ np = malloc(sizeof(struct nmea), M_WAITOK, M_DEVBUF);
+ tp->t_sc = (caddr_t)np;
+
+ snprintf(np->time.device, sizeof(np->time.device), "nmea%d",
+ nmea_count++);
+ np->time.status = SENSOR_S_UNKNOWN;
+ np->time.type = SENSOR_TIMEDELTA;
+ np->time.value = 0LL;
+ np->time.rfact = 0;
+ np->time.flags = 0;
+ np->state = S_SYNC;
+ }
+
+ return linesw[0].l_open(dev, tp);
+}
+
+int
+nmeaclose(struct tty *tp, int flags)
+{
+ struct nmea *np = (struct nmea *)tp->t_sc;
+
+ tp->t_line = 0; /* switch back to termios */
+ if (np->time.status != SENSOR_S_UNKNOWN)
+ sensor_del(&np->time);
+ free(np, M_DEVBUF);
+ nmea_count--;
+ return linesw[0].l_close(tp, flags);
+}
+
+/* scan input from tty for NMEA telegrams */
+int
+nmeainput(int c, struct tty *tp)
+{
+ struct nmea *np = (struct nmea *)tp->t_sc;
+
+ nmea_hdlr(np, c);
+
+ /* pass data to termios */
+ return linesw[0].l_rint(c, tp);
+}
+
+void
+nmeaattach(int dummy)
+{
+}
+
+/* NMEA state machine */
+void
+nmea_hdlr(struct nmea *np, int c)
+{
+ switch (np->state) {
+ case S_SYNC:
+ switch (c) {
+ case '$':
+ /* timestamp and delta refs now */
+ microtime(&np->tv);
+ nanotime(&np->ts);
+ np->pos = 0;
+ np->fldcnt = 0;
+ np->flds = 0;
+ np->cbuf[np->pos++] = c;
+ np->cksum = 0;
+ np->msgcksum = -1;
+ np->ti = TI_UNK;
+ np->state = S_TI_1;
+ }
+ break;
+ case S_TI_1:
+ nmea_bufadd(np, c);
+ np->state = S_TI_2;
+ switch (c) {
+ case 'G':
+ np->ti = TI_GPS;
+ break;
+ case 'L':
+ np->ti = TI_LORC;
+ break;
+ default:
+ np->state = S_SYNC;
+ }
+ break;
+ case S_TI_2:
+ nmea_bufadd(np, c);
+ np->state = S_SYNC;
+ switch (c) {
+ case 'P':
+ if (np->ti == TI_GPS)
+ np->state = S_MSG;
+ break;
+ case 'C':
+ if (np->ti == TI_LORC)
+ np->state = S_MSG;
+ break;
+ }
+ break;
+ case S_MSG:
+ nmea_bufadd(np, c);
+ if (np->pos == 6) {
+ np->msg = (np->cbuf[3] << 16) + (np->cbuf[4] << 8) +
+ np->cbuf[5];
+ switch (np->msg) {
+ case MSG_RMC:
+ np->flds = 12;
+ np->state = S_DATA;
+ break;
+ default:
+ np->state = S_SYNC;
+ }
+ }
+ break;
+ case S_DATA:
+ switch (c) {
+ case '\n':
+ np->cbuf[np->pos] = '\0';
+ nmea_decode(np);
+ np->state = S_SYNC;
+ break;
+ case '*':
+ np->cbuf[np->pos++] = c;
+ np->msgcksum = 0;
+ np->state = S_CKSUM;
+ break;
+ case ',':
+ np->fpos[np->fldcnt++] = np->pos + 1;
+ default:
+ if (np->pos < NMEAMAX)
+ nmea_bufadd(np, c);
+ else
+ np->state = S_SYNC;
+ }
+ break;
+ case S_CKSUM:
+ switch (c) {
+ case '\r':
+ case '\n':
+ np->cbuf[np->pos] = '\0';
+ nmea_decode(np);
+ np->state = S_SYNC;
+ break;
+ default:
+ if (np->pos < NMEAMAX && ((c >= '0' && c<= '9') ||
+ (c >= 'A' && c <= 'F'))) {
+ np->cbuf[np->pos++] = c;
+ if (np->msgcksum)
+ np->msgcksum <<= 4;
+ if (c >= '0' && c<= '9')
+ np->msgcksum += c - '0';
+ else if (c >= 'A' && c <= 'F')
+ np->msgcksum += 10 + c - 'A';
+ } else
+ np->state = S_SYNC;
+ }
+ break;
+ }
+}
+
+/* add a character to the buffer and update the checksum */
+void
+nmea_bufadd(struct nmea *np, int c)
+{
+ np->cbuf[np->pos++] = c;
+ np->cksum ^= c;
+}
+
+/*
+ * convert a string to a number, stop at the first non-numerical
+ * character or the end of the string. any non-numerical character
+ * following the number other than ',' is considered an error.
+ */
+int
+nmea_atoi(char *s, int *num)
+{
+ int n;
+
+ for (n = 0; *s && *s >= '0' && *s <= '9'; s++) {
+ if (n)
+ n *= 10;
+ n += *s - '0';
+ }
+
+ if (*s && *s != ',') /* no numeric character */
+ return (-1);
+
+ *num = n;
+ return (0);
+}
+
+void
+nmea_decode(struct nmea *np)
+{
+ switch (np->msg) {
+ case MSG_RMC:
+ nmea_rmc(np);
+ break;
+ }
+}
+
+/* Decode the minimum recommended nav info sentence (RMC) */
+void
+nmea_rmc(struct nmea *np)
+{
+ struct clock_ymdhms ymdhms;
+ time_t nmea_now;
+ int n;
+
+ if (np->fldcnt != np->flds) {
+ DPRINTF("field count mismatch\n");
+ return;
+ }
+ if (np->msgcksum >= 0 && np->cksum != np->msgcksum) {
+ DPRINTF("checksum error");
+ return;
+ }
+ np->cbuf[13] = '\0';
+ if (nmea_atoi(&np->cbuf[11], &n)) {
+ DPRINTF("error in sec\n");
+ return;
+ }
+ ymdhms.dt_sec = n;
+ np->cbuf[11] = 0;
+ if (nmea_atoi(&np->cbuf[9], &n)) {
+ DPRINTF("error in min\n");
+ return;
+ }
+ ymdhms.dt_min = n;
+ np->cbuf[9] = 0;
+ if (nmea_atoi(&np->cbuf[7], &n)) {
+ DPRINTF("error in hour\n");
+ return;
+ }
+ ymdhms.dt_hour = n;
+ if (nmea_atoi(&np->cbuf[np->fpos[8] + 4], &n)) {
+ DPRINTF("error in year\n");
+ return;
+ }
+ ymdhms.dt_year = 2000 + n;
+ np->cbuf[np->fpos[8] + 4] = '\0';
+ if (nmea_atoi(&np->cbuf[np->fpos[8] + 2], &n)) {
+ DPRINTF("error in month\n");
+ return;
+ }
+ ymdhms.dt_mon = n;
+ np->cbuf[np->fpos[8] + 2] = '\0';
+ if (nmea_atoi(&np->cbuf[np->fpos[8]], &n)) {
+ DPRINTF("error in day\n");
+ return;
+ }
+ ymdhms.dt_day = n;
+ nmea_now = clock_ymdhms_to_secs(&ymdhms);
+ if (nmea_now <= nmea_last) {
+ DPRINTF("time not monotonically increasing\n");
+ return;
+ }
+ nmea_last = nmea_now;
+ np->time.value = (np->ts.tv_sec - nmea_now)
+ * 1000000000 + np->ts.tv_nsec;
+ np->time.tv.tv_sec = np->tv.tv_sec;
+ np->time.tv.tv_usec = np->tv.tv_usec;
+ if (np->time.status == SENSOR_S_UNKNOWN) {
+ strlcpy(np->time.desc, np->ti == TI_GPS ? "GPS GPS" :
+ "LORC Loran-C", sizeof(np->time.desc));
+ switch (np->cbuf[np->fpos[11]]) {
+ case 'S':
+ strlcat(np->time.desc, " simulated",
+ sizeof(np->time.desc));
+ break;
+ case 'E':
+ strlcat(np->time.desc, " estimated",
+ sizeof(np->time.desc));
+ break;
+ case 'A':
+ strlcat(np->time.desc, " autonomous",
+ sizeof(np->time.desc));
+ break;
+ case 'D':
+ strlcat(np->time.desc, " differential",
+ sizeof(np->time.desc));
+ break;
+ case 'N':
+ strlcat(np->time.desc, " not valid",
+ sizeof(np->time.desc));
+ break;
+ }
+ np->time.status = SENSOR_S_OK;
+ sensor_add(&np->time);
+ }
+ switch (np->cbuf[np->fpos[1]]) {
+ case 'A':
+ np->time.status = SENSOR_S_OK;
+ break;
+ case 'V':
+ np->time.status = SENSOR_S_WARN;
+ break;
+ default:
+ DPRINTF("unknown warning indication\n");
+ }
+}
diff --git a/sys/sys/ttycom.h b/sys/sys/ttycom.h
index ad0f3579d1e..0d8e4ba4d10 100644
--- a/sys/sys/ttycom.h
+++ b/sys/sys/ttycom.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ttycom.h,v 1.7 2006/04/27 19:30:28 deraadt Exp $ */
+/* $OpenBSD: ttycom.h,v 1.8 2006/06/01 20:10:28 mbalmer Exp $ */
/* $NetBSD: ttycom.h,v 1.4 1996/05/19 17:17:53 jonathan Exp $ */
/*-
@@ -142,5 +142,6 @@ struct tstamps {
#define SLIPDISC 4 /* serial IP discipline */
#define PPPDISC 5 /* ppp discipline */
#define STRIPDISC 6 /* metricom wireless IP discipline */
+#define NMEADISC 7 /* NMEA0183 discipline */
#endif /* !_SYS_TTYCOM_H_ */