src/sys/kern/tty_msts.c

423 lines
10 KiB
C

/* $OpenBSD: tty_msts.c,v 1.21 2018/02/19 08:59:52 mpi 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/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)
void mstsattach(int);
#define MSTSMAX 32
#define MAXFLDS 4
#ifdef MSTS_DEBUG
#define TRUSTTIME 30
#else
#define TRUSTTIME (10 * 60) /* 10 minutes */
#endif
int msts_count, msts_nxid;
struct msts {
char cbuf[MSTSMAX]; /* receive buffer */
struct ksensor time; /* the timedelta sensor */
struct ksensor signal; /* signal status */
struct ksensordev timedev;
struct timespec ts; /* current timestamp */
struct timespec lts; /* timestamp of last <STX> */
struct timeout msts_tout; /* invalidate sensor */
int64_t gap; /* gap between two sentences */
int64_t last; /* last time rcvd */
int sync; /* if 1, waiting for <STX> */
int pos; /* position in rcv buffer */
int no_pps; /* no PPS although requested */
};
/* 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)
{
struct msts *np;
int error;
DPRINTF(("mstsopen\n"));
if (tp->t_line == MSTSDISC)
return ENODEV;
if ((error = suser(p)) != 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_nxid++);
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->signal.type = SENSOR_PERCENT;
np->signal.status = SENSOR_S_UNKNOWN;
np->signal.value = 100000LL;
strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc));
sensor_attach(&np->timedev, &np->signal);
np->sync = 1;
tp->t_sc = (caddr_t)np;
error = linesw[TTYDISC].l_open(dev, tp, p);
if (error) {
free(np, M_DEVBUF, sizeof(*np));
tp->t_sc = NULL;
} else {
sensordev_install(&np->timedev);
timeout_set(&np->msts_tout, msts_timeout, np);
}
return error;
}
int
mstsclose(struct tty *tp, int flags, struct proc *p)
{
struct msts *np = (struct msts *)tp->t_sc;
tp->t_line = TTYDISC; /* switch back to termios */
timeout_del(&np->msts_tout);
sensordev_deinstall(&np->timedev);
free(np, M_DEVBUF, sizeof(*np));
tp->t_sc = NULL;
msts_count--;
if (msts_count == 0)
msts_nxid = 0;
return linesw[TTYDISC].l_close(tp, flags, p);
}
/* 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];
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 sentence exceeds "
"maximum of %d\n", 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;
int jumped = 0;
if (fldcnt != MAXFLDS) {
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"));
jumped = 1;
}
np->last = msts_now;
np->gap = 0LL;
#ifdef MSTS_DEBUG
if (np->time.status == SENSOR_S_UNKNOWN) {
np->time.status = SENSOR_S_OK;
timeout_add_sec(&np->msts_tout, TRUSTTIME);
}
#endif
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;
strlcpy(np->time.desc, "MSTS", sizeof(np->time.desc));
}
/*
* only update the timeout if the clock reports the time a valid,
* the status is reported in fld[3][0] and fld[3][1] as follows:
* fld[3][0] == '#' critical
* fld[3][0] == ' ' && fld[3][1] == '*' warning
* fld[3][0] == ' ' && fld[3][1] == ' ' ok
*/
if (fld[3][0] == ' ' && fld[3][1] == ' ') {
np->time.status = SENSOR_S_OK;
np->signal.status = SENSOR_S_OK;
} else
np->signal.status = SENSOR_S_WARN;
if (jumped)
np->time.status = SENSOR_S_WARN;
if (np->time.status == SENSOR_S_OK)
timeout_add_sec(&np->msts_tout, TRUSTTIME);
/*
* If tty timestamping is requested, but no 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 the epoch.
* 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;
}
/*
* Degrade the sensor state if we received no MSTS string for more than
* TRUSTTIME seconds.
*/
void
msts_timeout(void *xnp)
{
struct msts *np = xnp;
if (np->time.status == SENSOR_S_OK) {
np->time.status = SENSOR_S_WARN;
/*
* further degrade in TRUSTTIME seconds if no new valid MSTS
* strings are received.
*/
timeout_add_sec(&np->msts_tout, TRUSTTIME);
} else
np->time.status = SENSOR_S_CRIT;
}