diff options
author | Marc Balmer <mbalmer@cvs.openbsd.org> | 2008-01-05 17:33:29 +0000 |
---|---|---|
committer | Marc Balmer <mbalmer@cvs.openbsd.org> | 2008-01-05 17:33:29 +0000 |
commit | 2a0eec3d922ea9fda272a0a239ac6eb30cadc40b (patch) | |
tree | d48c394d5a9db8c4f953389e4ff44df8005b4fbb /sys | |
parent | f1d430b3cf60f8e6d4f5810bcde9bc17bd36ac8c (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/GENERIC | 3 | ||||
-rw-r--r-- | sys/conf/files | 5 | ||||
-rw-r--r-- | sys/kern/tty_conf.c | 17 | ||||
-rw-r--r-- | sys/kern/tty_msts.c | 373 | ||||
-rw-r--r-- | sys/sys/ttycom.h | 3 |
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_ */ |