531 lines
13 KiB
C
531 lines
13 KiB
C
/* $OpenBSD: tty_endrun.c,v 1.8 2018/02/19 08:59:52 mpi Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2008 Marc Balmer <mbalmer@openbsd.org>
|
|
* Copyright (c) 2009 Kevin Steves <stevesk@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 EndRun Technologies native
|
|
* time-of-day message.
|
|
* http://www.endruntechnologies.com/
|
|
*/
|
|
|
|
/*
|
|
* EndRun Format:
|
|
*
|
|
* T YYYY DDD HH:MM:SS zZZ m<CR><LF>
|
|
*
|
|
* T is the Time Figure of Merit (TFOM) character (described below).
|
|
* This is the on-time character, transmitted during the first
|
|
* millisecond of each second.
|
|
*
|
|
* YYYY is the year
|
|
* DDD is the day-of-year
|
|
* : is the colon character (0x3A)
|
|
* HH is the hour of the day
|
|
* MM is the minute of the hour
|
|
* SS is the second of the minute
|
|
* z is the sign of the offset to UTC, + implies time is ahead of UTC.
|
|
* ZZ is the magnitude of the offset to UTC in units of half-hours.
|
|
* Non-zero only when the Timemode is Local.
|
|
* m is the Timemode character and is one of:
|
|
* G = GPS
|
|
* L = Local
|
|
* U = UTC
|
|
* <CR> is the ASCII carriage return character (0x0D)
|
|
* <LF> is the ASCII line feed character (0x0A)
|
|
*/
|
|
|
|
#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 ENDRUN_DEBUG
|
|
#define DPRINTFN(n, x) do { if (endrundebug > (n)) printf x; } while (0)
|
|
int endrundebug = 0;
|
|
#else
|
|
#define DPRINTFN(n, x)
|
|
#endif
|
|
#define DPRINTF(x) DPRINTFN(0, x)
|
|
|
|
void endrunattach(int);
|
|
|
|
#define ENDRUNLEN 27 /* strlen("6 2009 018 20:41:17 +00 U\r\n") */
|
|
#define NUMFLDS 6
|
|
#ifdef ENDRUN_DEBUG
|
|
#define TRUSTTIME 30
|
|
#else
|
|
#define TRUSTTIME (10 * 60) /* 10 minutes */
|
|
#endif
|
|
|
|
int endrun_count, endrun_nxid;
|
|
|
|
struct endrun {
|
|
char cbuf[ENDRUNLEN]; /* 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 TFOM */
|
|
struct timeout endrun_tout; /* invalidate sensor */
|
|
int64_t gap; /* gap between two sentences */
|
|
int64_t last; /* last time rcvd */
|
|
#define SYNC_SCAN 1 /* scanning for '\n' */
|
|
#define SYNC_EOL 2 /* '\n' seen, next char TFOM */
|
|
int sync;
|
|
int pos; /* position in rcv buffer */
|
|
int no_pps; /* no PPS although requested */
|
|
#ifdef ENDRUN_DEBUG
|
|
char tfom;
|
|
#endif
|
|
};
|
|
|
|
/* EndRun decoding */
|
|
void endrun_scan(struct endrun *, struct tty *);
|
|
void endrun_decode(struct endrun *, struct tty *, char *fld[], int fldcnt);
|
|
|
|
/* date and time conversion */
|
|
int endrun_atoi(char *s, int len);
|
|
int endrun_date_to_nano(char *s1, char *s2, int64_t *nano);
|
|
int endrun_time_to_nano(char *s, int64_t *nano);
|
|
int endrun_offset_to_nano(char *s, int64_t *nano);
|
|
|
|
/* degrade the timedelta sensor */
|
|
void endrun_timeout(void *);
|
|
|
|
void
|
|
endrunattach(int dummy)
|
|
{
|
|
}
|
|
|
|
int
|
|
endrunopen(dev_t dev, struct tty *tp, struct proc *p)
|
|
{
|
|
struct endrun *np;
|
|
int error;
|
|
|
|
DPRINTF(("endrunopen\n"));
|
|
if (tp->t_line == ENDRUNDISC)
|
|
return ENODEV;
|
|
if ((error = suser(p)) != 0)
|
|
return error;
|
|
np = malloc(sizeof(struct endrun), M_DEVBUF, M_WAITOK|M_ZERO);
|
|
snprintf(np->timedev.xname, sizeof(np->timedev.xname), "endrun%d",
|
|
endrun_nxid++);
|
|
endrun_count++;
|
|
np->time.status = SENSOR_S_UNKNOWN;
|
|
np->time.type = SENSOR_TIMEDELTA;
|
|
#ifndef ENDRUN_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 = SYNC_SCAN;
|
|
#ifdef ENDRUN_DEBUG
|
|
np->tfom = '0';
|
|
#endif
|
|
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->endrun_tout, endrun_timeout, np);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
endrunclose(struct tty *tp, int flags, struct proc *p)
|
|
{
|
|
struct endrun *np = (struct endrun *)tp->t_sc;
|
|
|
|
DPRINTF(("endrunclose\n"));
|
|
tp->t_line = TTYDISC; /* switch back to termios */
|
|
timeout_del(&np->endrun_tout);
|
|
sensordev_deinstall(&np->timedev);
|
|
free(np, M_DEVBUF, sizeof(*np));
|
|
tp->t_sc = NULL;
|
|
endrun_count--;
|
|
if (endrun_count == 0)
|
|
endrun_nxid = 0;
|
|
return linesw[TTYDISC].l_close(tp, flags, p);
|
|
}
|
|
|
|
/* collect EndRun sentence from tty */
|
|
int
|
|
endruninput(int c, struct tty *tp)
|
|
{
|
|
struct endrun *np = (struct endrun *)tp->t_sc;
|
|
struct timespec ts;
|
|
int64_t gap;
|
|
long tmin, tmax;
|
|
|
|
if (np->sync == SYNC_EOL) {
|
|
nanotime(&ts);
|
|
np->pos = 0;
|
|
np->sync = SYNC_SCAN;
|
|
np->cbuf[np->pos++] = c; /* TFOM char */
|
|
|
|
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)
|
|
goto nogap;
|
|
|
|
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 EndRun 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;
|
|
}
|
|
}
|
|
} else if (c == '\n') {
|
|
if (np->pos == ENDRUNLEN - 1) {
|
|
/* don't copy '\n' into cbuf */
|
|
np->cbuf[np->pos] = '\0';
|
|
endrun_scan(np, tp);
|
|
}
|
|
np->sync = SYNC_EOL;
|
|
} else {
|
|
if (np->pos < ENDRUNLEN - 1)
|
|
np->cbuf[np->pos++] = c;
|
|
}
|
|
|
|
nogap:
|
|
/* pass data to termios */
|
|
return linesw[TTYDISC].l_rint(c, tp);
|
|
}
|
|
|
|
/* Scan the EndRun sentence just received */
|
|
void
|
|
endrun_scan(struct endrun *np, struct tty *tp)
|
|
{
|
|
int fldcnt = 0, n;
|
|
char *fld[NUMFLDS], *cs;
|
|
|
|
DPRINTFN(1, ("%s\n", np->cbuf));
|
|
/* split into fields */
|
|
fld[fldcnt++] = &np->cbuf[0];
|
|
for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
|
|
switch (np->cbuf[n]) {
|
|
case '\r':
|
|
np->cbuf[n] = '\0';
|
|
cs = &np->cbuf[n + 1];
|
|
break;
|
|
case ' ':
|
|
if (fldcnt < NUMFLDS) {
|
|
np->cbuf[n] = '\0';
|
|
fld[fldcnt++] = &np->cbuf[n + 1];
|
|
} else {
|
|
DPRINTF(("endrun: nr of fields in sentence "
|
|
"exceeds expected: %d\n", NUMFLDS));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
endrun_decode(np, tp, fld, fldcnt);
|
|
}
|
|
|
|
/* Decode the time string */
|
|
void
|
|
endrun_decode(struct endrun *np, struct tty *tp, char *fld[], int fldcnt)
|
|
{
|
|
int64_t date_nano, time_nano, offset_nano, endrun_now;
|
|
char tfom;
|
|
int jumped = 0;
|
|
|
|
if (fldcnt != NUMFLDS) {
|
|
DPRINTF(("endrun: field count mismatch, %d\n", fldcnt));
|
|
return;
|
|
}
|
|
if (endrun_time_to_nano(fld[3], &time_nano) == -1) {
|
|
DPRINTF(("endrun: illegal time, %s\n", fld[3]));
|
|
return;
|
|
}
|
|
if (endrun_date_to_nano(fld[1], fld[2], &date_nano) == -1) {
|
|
DPRINTF(("endrun: illegal date, %s %s\n", fld[1], fld[2]));
|
|
return;
|
|
}
|
|
offset_nano = 0;
|
|
/* only parse offset when timemode is local */
|
|
if (fld[5][0] == 'L' &&
|
|
endrun_offset_to_nano(fld[4], &offset_nano) == -1) {
|
|
DPRINTF(("endrun: illegal offset, %s\n", fld[4]));
|
|
return;
|
|
}
|
|
|
|
endrun_now = date_nano + time_nano + offset_nano;
|
|
if (endrun_now <= np->last) {
|
|
DPRINTF(("endrun: time not monotonically increasing "
|
|
"last %lld now %lld\n",
|
|
(long long)np->last, (long long)endrun_now));
|
|
jumped = 1;
|
|
}
|
|
np->last = endrun_now;
|
|
np->gap = 0LL;
|
|
#ifdef ENDRUN_DEBUG
|
|
if (np->time.status == SENSOR_S_UNKNOWN) {
|
|
np->time.status = SENSOR_S_OK;
|
|
timeout_add_sec(&np->endrun_tout, TRUSTTIME);
|
|
}
|
|
#endif
|
|
|
|
np->time.value = np->ts.tv_sec * 1000000000LL +
|
|
np->ts.tv_nsec - endrun_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, "EndRun", sizeof(np->time.desc));
|
|
}
|
|
/*
|
|
* Only update the timeout if the clock reports the time as valid.
|
|
*
|
|
* Time Figure Of Merit (TFOM) values:
|
|
*
|
|
* 6 - time error is < 100 us
|
|
* 7 - time error is < 1 ms
|
|
* 8 - time error is < 10 ms
|
|
* 9 - time error is > 10 ms,
|
|
* unsynchronized state if never locked to CDMA
|
|
*/
|
|
|
|
switch (tfom = fld[0][0]) {
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
np->time.status = SENSOR_S_OK;
|
|
np->signal.status = SENSOR_S_OK;
|
|
break;
|
|
case '9':
|
|
np->signal.status = SENSOR_S_WARN;
|
|
break;
|
|
default:
|
|
DPRINTF(("endrun: invalid TFOM: '%c'\n", tfom));
|
|
np->signal.status = SENSOR_S_CRIT;
|
|
break;
|
|
}
|
|
|
|
#ifdef ENDRUN_DEBUG
|
|
if (np->tfom != tfom) {
|
|
DPRINTF(("endrun: TFOM changed from %c to %c\n",
|
|
np->tfom, tfom));
|
|
np->tfom = tfom;
|
|
}
|
|
#endif
|
|
if (jumped)
|
|
np->time.status = SENSOR_S_WARN;
|
|
if (np->time.status == SENSOR_S_OK)
|
|
timeout_add_sec(&np->endrun_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;
|
|
}
|
|
|
|
int
|
|
endrun_atoi(char *s, int len)
|
|
{
|
|
int n;
|
|
char *p;
|
|
|
|
/* make sure the input contains only numbers */
|
|
for (n = 0, p = s; n < len && *p && *p >= '0' && *p <= '9'; n++, p++)
|
|
;
|
|
if (n != len || *p != '\0')
|
|
return -1;
|
|
|
|
for (n = 0; *s; s++)
|
|
n = n * 10 + *s - '0';
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Convert date fields from EndRun to nanoseconds since the epoch.
|
|
* The year string must be of the form YYYY .
|
|
* The day of year string must be of the form DDD .
|
|
* Return 0 on success, -1 if illegal characters are encountered.
|
|
*/
|
|
int
|
|
endrun_date_to_nano(char *y, char *doy, int64_t *nano)
|
|
{
|
|
struct clock_ymdhms clock;
|
|
time_t secs;
|
|
int n, i;
|
|
int year_days = 365;
|
|
int month_days[] = {
|
|
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
|
};
|
|
|
|
#define FEBRUARY 2
|
|
|
|
#define LEAPYEAR(x) \
|
|
((x) % 4 == 0 && \
|
|
(x) % 100 != 0) || \
|
|
(x) % 400 == 0
|
|
|
|
if ((n = endrun_atoi(y, 4)) == -1)
|
|
return -1;
|
|
clock.dt_year = n;
|
|
|
|
if (LEAPYEAR(n)) {
|
|
month_days[FEBRUARY]++;
|
|
year_days++;
|
|
}
|
|
|
|
if ((n = endrun_atoi(doy, 3)) == -1 || n == 0 || n > year_days)
|
|
return -1;
|
|
|
|
/* convert day of year to month, day */
|
|
for (i = 1; n > month_days[i]; i++) {
|
|
n -= month_days[i];
|
|
}
|
|
clock.dt_mon = i;
|
|
clock.dt_day = n;
|
|
|
|
DPRINTFN(1, ("mm/dd %d/%d\n", i, n));
|
|
|
|
clock.dt_hour = clock.dt_min = clock.dt_sec = 0;
|
|
|
|
secs = clock_ymdhms_to_secs(&clock);
|
|
*nano = secs * 1000000000LL;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Convert time field from EndRun to nanoseconds since midnight.
|
|
* The string must be of the form HH:MM:SS .
|
|
* Return 0 on success, -1 if illegal characters are encountered.
|
|
*/
|
|
int
|
|
endrun_time_to_nano(char *s, int64_t *nano)
|
|
{
|
|
struct clock_ymdhms clock;
|
|
time_t secs;
|
|
int n;
|
|
|
|
if (s[2] != ':' || s[5] != ':')
|
|
return -1;
|
|
|
|
s[2] = '\0';
|
|
s[5] = '\0';
|
|
|
|
if ((n = endrun_atoi(&s[0], 2)) == -1 || n > 23)
|
|
return -1;
|
|
clock.dt_hour = n;
|
|
if ((n = endrun_atoi(&s[3], 2)) == -1 || n > 59)
|
|
return -1;
|
|
clock.dt_min = n;
|
|
if ((n = endrun_atoi(&s[6], 2)) == -1 || n > 60)
|
|
return -1;
|
|
clock.dt_sec = n;
|
|
|
|
DPRINTFN(1, ("hh:mm:ss %d:%d:%d\n", (int)clock.dt_hour,
|
|
(int)clock.dt_min,
|
|
(int)clock.dt_sec));
|
|
secs = clock.dt_hour * 3600
|
|
+ clock.dt_min * 60
|
|
+ clock.dt_sec;
|
|
|
|
DPRINTFN(1, ("secs %lu\n", (unsigned long)secs));
|
|
|
|
*nano = secs * 1000000000LL;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
endrun_offset_to_nano(char *s, int64_t *nano)
|
|
{
|
|
time_t secs;
|
|
int n;
|
|
|
|
if (!(s[0] == '+' || s[0] == '-'))
|
|
return -1;
|
|
|
|
if ((n = endrun_atoi(&s[1], 2)) == -1)
|
|
return -1;
|
|
secs = n * 30 * 60;
|
|
|
|
*nano = secs * 1000000000LL;
|
|
if (s[0] == '+')
|
|
*nano = -*nano;
|
|
|
|
DPRINTFN(1, ("offset secs %lu nanosecs %lld\n",
|
|
(unsigned long)secs, (long long)*nano));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Degrade the sensor state if we received no EndRun string for more than
|
|
* TRUSTTIME seconds.
|
|
*/
|
|
void
|
|
endrun_timeout(void *xnp)
|
|
{
|
|
struct endrun *np = xnp;
|
|
|
|
if (np->time.status == SENSOR_S_OK) {
|
|
np->time.status = SENSOR_S_WARN;
|
|
/*
|
|
* further degrade in TRUSTTIME seconds if no new valid EndRun
|
|
* strings are received.
|
|
*/
|
|
timeout_add_sec(&np->endrun_tout, TRUSTTIME);
|
|
} else
|
|
np->time.status = SENSOR_S_CRIT;
|
|
}
|