summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorMarc Balmer <mbalmer@cvs.openbsd.org>2008-01-05 17:33:29 +0000
committerMarc Balmer <mbalmer@cvs.openbsd.org>2008-01-05 17:33:29 +0000
commit2a0eec3d922ea9fda272a0a239ac6eb30cadc40b (patch)
treed48c394d5a9db8c4f953389e4ff44df8005b4fbb /sys
parentf1d430b3cf60f8e6d4f5810bcde9bc17bd36ac8c (diff)
Add support for the Meinberg Standard Time String format that all Meinberg
radio clocks can transmit over serial ports. This is implemented as a line discipline similar to nmea(4) and provides a timedelta sensor. See http://www.meinberg.de/english/specs/timestr.htm for details on the MSTS format. ldattach(8) is extended to support the "msts" line discipline and two stopbits (which some radio clocks, e.g. the C51 use). Do a "make includes" before your next system build. Initially from Maurice Janssen based on nmea(4). "go for it" deraadt
Diffstat (limited to 'sys')
-rw-r--r--sys/conf/GENERIC3
-rw-r--r--sys/conf/files5
-rw-r--r--sys/kern/tty_conf.c17
-rw-r--r--sys/kern/tty_msts.c373
-rw-r--r--sys/sys/ttycom.h3
5 files changed, 397 insertions, 4 deletions
diff --git a/sys/conf/GENERIC b/sys/conf/GENERIC
index e2adc760388..ba185874a69 100644
--- a/sys/conf/GENERIC
+++ b/sys/conf/GENERIC
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.138 2007/11/28 12:25:39 deraadt Exp $
+# $OpenBSD: GENERIC,v 1.139 2008/01/05 17:33:28 mbalmer Exp $
#
# Machine-independent option; used by all architectures for their
# GENERIC kernel
@@ -75,6 +75,7 @@ pseudo-device enc 1 # option IPSEC needs the encapsulation interface
pseudo-device pty 16 # initial number of pseudo-terminals
pseudo-device nmea 1 # NMEA 0183 line discipline
+pseudo-device msts 1 # MSTS line discipline
pseudo-device vnd 4 # paging to files
pseudo-device ccd 4 # concatenated disk devices
pseudo-device ksyms 1 # kernel symbols device
diff --git a/sys/conf/files b/sys/conf/files
index 9557c8d0d4f..a93a1fd6881 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1,4 +1,4 @@
-# $OpenBSD: files,v 1.421 2007/11/28 23:37:34 oga Exp $
+# $OpenBSD: files,v 1.422 2008/01/05 17:33:28 mbalmer Exp $
# $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
@@ -454,6 +454,8 @@ file dev/ramdisk.c rd needs-flag
pseudo-device pty: tty
pseudo-device nmea: tty
+pseudo-device msts: tty
+
pseudo-device loop: ifnet
pseudo-device sl: ifnet
pseudo-device ppp: ifnet
@@ -687,6 +689,7 @@ file kern/tty.c
file kern/tty_conf.c
file kern/tty_pty.c pty needs-count
file kern/tty_nmea.c nmea needs-flag
+file kern/tty_msts.c msts needs-flag
file kern/tty_subr.c
file kern/tty_tty.c
file kern/uipc_domain.c
diff --git a/sys/kern/tty_conf.c b/sys/kern/tty_conf.c
index a47b52239c5..783fa7bdcb4 100644
--- a/sys/kern/tty_conf.c
+++ b/sys/kern/tty_conf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tty_conf.c,v 1.11 2006/08/03 16:13:24 mbalmer Exp $ */
+/* $OpenBSD: tty_conf.c,v 1.12 2008/01/05 17:33:28 mbalmer Exp $ */
/* $NetBSD: tty_conf.c,v 1.18 1996/05/19 17:17:55 jonathan Exp $ */
/*-
@@ -91,6 +91,13 @@ int nmeaclose(struct tty *, int);
int nmeainput(int, struct tty *);
#endif
+#include "msts.h"
+#if NMSTS > 0
+int mstsopen(dev_t, struct tty *);
+int mstsclose(struct tty *, int);
+int mstsinput(int, struct tty *);
+#endif
+
struct linesw linesw[] =
{
{ ttyopen, ttylclose, ttread, ttwrite, nullioctl,
@@ -142,6 +149,14 @@ struct linesw linesw[] =
{ ttynodisc, ttyerrclose, ttyerrio, ttyerrio, nullioctl,
ttyerrinput, ttyerrstart, nullmodem },
#endif
+
+#if NMSTS > 0
+ { mstsopen, mstsclose, ttread, ttwrite, nullioctl,
+ mstsinput, ttstart, ttymodem }, /* 8- MSTSDISC */
+#else
+ { ttynodisc, ttyerrclose, ttyerrio, ttyerrio, nullioctl,
+ ttyerrinput, ttyerrstart, nullmodem },
+#endif
};
int nlinesw = sizeof (linesw) / sizeof (linesw[0]);
diff --git a/sys/kern/tty_msts.c b/sys/kern/tty_msts.c
new file mode 100644
index 00000000000..128c3cc3746
--- /dev/null
+++ b/sys/kern/tty_msts.c
@@ -0,0 +1,373 @@
+/* $OpenBSD: tty_msts.c,v 1.1 2008/01/05 17:33:28 mbalmer Exp $ */
+
+/*
+ * Copyright (c) 2008 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.
+ */
+
+/*
+ * A tty line discipline to decode the Meinberg Standard Time String
+ * to get the time (http://www.meinberg.de/english/specs/timestr.htm).
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/queue.h>
+#include <sys/proc.h>
+#include <sys/malloc.h>
+#include <sys/sensors.h>
+#include <sys/tty.h>
+#include <sys/conf.h>
+#include <sys/time.h>
+
+#ifdef MSTS_DEBUG
+#define DPRINTFN(n, x) do { if (mstsdebug > (n)) printf x; } while (0)
+int mstsdebug = 0;
+#else
+#define DPRINTFN(n, x)
+#endif
+#define DPRINTF(x) DPRINTFN(0, x)
+
+int mstsopen(dev_t, struct tty *);
+int mstsclose(struct tty *, int);
+int mstsinput(int, struct tty *);
+void mstsattach(int);
+
+#define MSTSMAX 32
+#define MAXFLDS 4
+
+int msts_count; /* this is wrong, it should really be a SLIST */
+
+struct msts {
+ char cbuf[MSTSMAX]; /* receive buffer */
+ struct ksensor time; /* the timedelta sensor */
+ struct ksensordev timedev;
+ struct timespec ts; /* current timestamp */
+ struct timespec lts; /* timestamp of last <STX> */
+ int64_t gap; /* gap between two sentences */
+ int64_t last; /* last time rcvd */
+ int sync; /* if 1, waiting for <STX> */
+ int pos; /* positon in rcv buffer */
+ int no_pps; /* no PPS although requested */
+ char mode; /* GPS mode */
+};
+
+/* MSTS decoding */
+void msts_scan(struct msts *, struct tty *);
+void msts_decode(struct msts *, struct tty *, char *fld[], int fldcnt);
+
+/* date and time conversion */
+int msts_date_to_nano(char *s, int64_t *nano);
+int msts_time_to_nano(char *s, int64_t *nano);
+
+/* degrade the timedelta sensor */
+void msts_timeout(void *);
+
+void
+mstsattach(int dummy)
+{
+}
+
+int
+mstsopen(dev_t dev, struct tty *tp)
+{
+ struct proc *p = curproc;
+ struct msts *np;
+ int error;
+
+ DPRINTF(("mstsopen\n"));
+ if (tp->t_line == MSTSDISC)
+ return ENODEV;
+ if ((error = suser(p, 0)) != 0)
+ return error;
+ np = malloc(sizeof(struct msts), M_DEVBUF, M_WAITOK|M_ZERO);
+ snprintf(np->timedev.xname, sizeof(np->timedev.xname), "msts%d",
+ msts_count++);
+ np->time.status = SENSOR_S_UNKNOWN;
+ np->time.type = SENSOR_TIMEDELTA;
+#ifndef MSTS_DEBUG
+ np->time.flags = SENSOR_FINVALID;
+#endif
+ sensor_attach(&np->timedev, &np->time);
+ np->sync = 1;
+ tp->t_sc = (caddr_t)np;
+
+ error = linesw[TTYDISC].l_open(dev, tp);
+ if (error) {
+ free(np, M_DEVBUF);
+ tp->t_sc = NULL;
+ } else
+ sensordev_install(&np->timedev);
+ return error;
+}
+
+int
+mstsclose(struct tty *tp, int flags)
+{
+ struct msts *np = (struct msts *)tp->t_sc;
+
+ tp->t_line = TTYDISC; /* switch back to termios */
+ sensordev_deinstall(&np->timedev);
+ free(np, M_DEVBUF);
+ tp->t_sc = NULL;
+ msts_count--;
+ return linesw[TTYDISC].l_close(tp, flags);
+}
+
+/* collect MSTS sentence from tty */
+int
+mstsinput(int c, struct tty *tp)
+{
+ struct msts *np = (struct msts *)tp->t_sc;
+ struct timespec ts;
+ int64_t gap;
+ long tmin, tmax;
+
+ switch (c) {
+ case 2: /* ASCII <STX> */
+ nanotime(&ts);
+ np->pos = np->sync = 0;
+ gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) -
+ (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec);
+
+ np->lts.tv_sec = ts.tv_sec;
+ np->lts.tv_nsec = ts.tv_nsec;
+
+ if (gap <= np->gap)
+ break;
+
+ np->ts.tv_sec = ts.tv_sec;
+ np->ts.tv_nsec = ts.tv_nsec;
+ np->gap = gap;
+
+ /*
+ * If a tty timestamp is available, make sure its value is
+ * reasonable by comparing against the timestamp just taken.
+ * If they differ by more than 2 seconds, assume no PPS signal
+ * is present, note the fact, and keep using the timestamp
+ * value. When this happens, the sensor state is set to
+ * CRITICAL later when the MSTS sentence is decoded.
+ */
+ if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
+ TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
+ tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
+ tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
+ if (tmax - tmin > 1)
+ np->no_pps = 1;
+ else {
+ np->ts.tv_sec = tp->t_tv.tv_sec;
+ np->ts.tv_nsec = tp->t_tv.tv_usec *
+ 1000L;
+ np->no_pps = 0;
+ }
+ }
+ break;
+ case 3: /* ASCII <ETX> */
+ if (!np->sync) {
+ np->cbuf[np->pos] = '\0';
+ msts_scan(np, tp);
+ np->sync = 1;
+ }
+ break;
+ default:
+ if (!np->sync && np->pos < (MSTSMAX - 1))
+ np->cbuf[np->pos++] = c;
+ break;
+ }
+ /* pass data to termios */
+ return linesw[TTYDISC].l_rint(c, tp);
+}
+
+/* Scan the MSTS sentence just received */
+void
+msts_scan(struct msts *np, struct tty *tp)
+{
+ int fldcnt = 0, n;
+ char *fld[MAXFLDS], *cs;
+
+ /* split into fields */
+ fld[fldcnt++] = &np->cbuf[0]; /* message type */
+ for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
+ switch (np->cbuf[n]) {
+ case 3: /* ASCII <ETX> */
+ np->cbuf[n] = '\0';
+ cs = &np->cbuf[n + 1];
+ break;
+ case ';':
+ if (fldcnt < MAXFLDS) {
+ np->cbuf[n] = '\0';
+ fld[fldcnt++] = &np->cbuf[n + 1];
+ } else {
+ DPRINTF(("nr of fields in %s sentence exceeds "
+ "maximum of %d\n", fld[0], MAXFLDS));
+ return;
+ }
+ break;
+ }
+ }
+ msts_decode(np, tp, fld, fldcnt);
+}
+
+/* Decode the time string */
+void
+msts_decode(struct msts *np, struct tty *tp, char *fld[], int fldcnt)
+{
+ int64_t date_nano, time_nano, msts_now;
+
+ if (fldcnt != 4) {
+ DPRINTF(("msts: field count mismatch, %d\n", fldcnt));
+ return;
+ }
+ if (msts_time_to_nano(fld[2], &time_nano)) {
+ DPRINTF(("msts: illegal time, %s\n", fld[2]));
+ return;
+ }
+ if (msts_date_to_nano(fld[0], &date_nano)) {
+ DPRINTF(("msts: illegal date, %s\n", fld[0]));
+ return;
+ }
+ msts_now = date_nano + time_nano;
+ if ( fld[3][2] == ' ' ) /* received time in CET */
+ msts_now = msts_now - 3600 * 1000000000LL;
+ if ( fld[3][2] == 'S' ) /* received time in CEST */
+ msts_now = msts_now - 2 * 3600 * 1000000000LL;
+ if (msts_now <= np->last) {
+ DPRINTF(("msts: time not monotonically increasing\n"));
+ return;
+ }
+ np->last = msts_now;
+ np->gap = 0LL;
+
+ np->time.value = np->ts.tv_sec * 1000000000LL +
+ np->ts.tv_nsec - msts_now;
+ np->time.tv.tv_sec = np->ts.tv_sec;
+ np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
+ if (np->time.status == SENSOR_S_UNKNOWN) {
+ np->time.status = SENSOR_S_OK;
+ np->time.flags &= ~SENSOR_FINVALID;
+ if (fldcnt != 13)
+ strlcpy(np->time.desc, "MSTS", sizeof(np->time.desc));
+ }
+ if (fld[3][0] == '#')
+ np->time.status = SENSOR_S_CRIT;
+ else if (fld[3][0] == ' ' && fld[3][1] == '*')
+ np->time.status = SENSOR_S_WARN;
+ else if (fld[3][0] == ' ' && fld[3][1] == ' ')
+ np->time.status = SENSOR_S_OK;
+ else
+ DPRINTF(("msts: unknown clock status indication\n"));
+
+ /*
+ * If tty timestamping is requested, but not PPS signal is present, set
+ * the sensor state to CRITICAL.
+ */
+ if (np->no_pps)
+ np->time.status = SENSOR_S_CRIT;
+}
+
+/*
+ * Convert date field from MSTS to nanoseconds since midnight.
+ * The string must be of the form D:DD.MM.YY .
+ * Return 0 on success, -1 if illegal characters are encountered.
+ */
+int
+msts_date_to_nano(char *s, int64_t *nano)
+{
+ struct clock_ymdhms ymd;
+ time_t secs;
+ char *p;
+ int n;
+
+ if (s[0] != 'D' || s[1] != ':' || s[4] != '.' || s[7] != '.')
+ return -1;
+
+ /* shift numbers to DDMMYY */
+ s[0]=s[2];
+ s[1]=s[3];
+ s[2]=s[5];
+ s[3]=s[6];
+ s[4]=s[8];
+ s[5]=s[9];
+ s[6]='\0';
+
+ /* make sure the input contains only numbers and is six digits long */
+ for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++)
+ ;
+ if (n != 6 || (*p != '\0'))
+ return -1;
+
+ ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0');
+ ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0');
+ ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0');
+ ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0;
+
+ secs = clock_ymdhms_to_secs(&ymd);
+ *nano = secs * 1000000000LL;
+ return 0;
+}
+
+/*
+ * Convert time field from MSTS to nanoseconds since midnight.
+ * The string must be of the form U:HH.MM.SS .
+ * Return 0 on success, -1 if illegal characters are encountered.
+ */
+int
+msts_time_to_nano(char *s, int64_t *nano)
+{
+ long fac = 36000L, div = 6L, secs = 0L;
+ char ul = '2';
+ int n;
+
+ if (s[0] != 'U' || s[1] != ':' || s[4] != '.' || s[7] != '.')
+ return -1;
+
+ /* shift numbers to HHMMSS */
+ s[0]=s[2];
+ s[1]=s[3];
+ s[2]=s[5];
+ s[3]=s[6];
+ s[4]=s[8];
+ s[5]=s[9];
+ s[6]='\0';
+
+ for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) {
+ secs += (*s - '0') * fac;
+ div = 16 - div;
+ fac /= div;
+ switch (n) {
+ case 0:
+ if (*s <= '1')
+ ul = '9';
+ else
+ ul = '3';
+ break;
+ case 1:
+ case 3:
+ ul = '5';
+ break;
+ case 2:
+ case 4:
+ ul = '9';
+ break;
+ }
+ }
+ if (fac)
+ return -1;
+
+ if (*s != '\0')
+ return -1;
+
+ *nano = secs * 1000000000LL;
+ return 0;
+}
diff --git a/sys/sys/ttycom.h b/sys/sys/ttycom.h
index 0d8e4ba4d10..cf8857f948d 100644
--- a/sys/sys/ttycom.h
+++ b/sys/sys/ttycom.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ttycom.h,v 1.8 2006/06/01 20:10:28 mbalmer Exp $ */
+/* $OpenBSD: ttycom.h,v 1.9 2008/01/05 17:33:28 mbalmer Exp $ */
/* $NetBSD: ttycom.h,v 1.4 1996/05/19 17:17:53 jonathan Exp $ */
/*-
@@ -143,5 +143,6 @@ struct tstamps {
#define PPPDISC 5 /* ppp discipline */
#define STRIPDISC 6 /* metricom wireless IP discipline */
#define NMEADISC 7 /* NMEA0183 discipline */
+#define MSTSDISC 8 /* Meinberg time string discipline */
#endif /* !_SYS_TTYCOM_H_ */